Move items between two list boxes shiny - shiny

Is there an easy way to create something like this in shiny?

RStudio is currently working on the sortable package: RStudio/sortable
Beware that it's currently in development (tagged as experimental), so major changes are possible and it's only accessble through GitHub
# install.packages("remotes")
# remotes::install_github("rstudio/sortable")
library(shiny)
library(sortable)
ui <- fluidPage(
fluidRow(
column(
width = 12,
bucket_list(
header = "Drag the items in any desired bucket",
group_name = "bucket_list_group",
add_rank_list(
text = "Drag from here",
labels = c("Ant", "Cat", "Eagle", "Giraffe", "Bear", "Frog","Dog"),
input_id = "rank_list_1"
),
add_rank_list(
text = "to here",
labels = NULL,
input_id = "rank_list_2"
)
)
)
)
)
shinyApp(ui, function(input,output) {})
This results in:

Related

Update editable DataTable Output in RShiny

I am new to the world of RShiny and i think reactivity is bit complex to understand. I am trying to make a datatable output based on row and column condition given as per user selectinput dropdown buttons. My DataTable is editable and i want to store the updated table after user edited the cells of the datatable in a new variable but i am stuck.
I tried couple of chunks suggested on stackoverflow but none of them worked for me mostly using proxytable or reactivity. I want to store the datatable after i hit proceed button. Any help would be much appreciated.
Here is my code:
library(shiny)
library(shinyWidgets)
library(shinythemes)
library(DT)
library(data.table)
#ui
shinyApp(
ui = fluidPage(
theme = shinythemes::shinytheme("flatly"),
titlePanel("Trial"),
sidebarLayout(
shiny::sidebarPanel(
#to take multiple user input
shiny::textAreaInput(
"text_input",
label = "Write input"
),
#to slect the columns to be added
shinyWidgets::pickerInput(
inputId = "somevalue",
label = "Columns to add",
choices = colnames(df),
options = list(`actions-box` = TRUE),
multiple = TRUE
),
#action button tot show the table
shinyWidgets::actionBttn(
"show_table",
label = "Show",
size = "sm",
color = "default",
block = TRUE
), width = 2
),
mainPanel(
shiny::tabsetPanel(type = "tabs",
shiny::tabPanel("Table", DT::dataTableOutput("table")),
actionBttn("proceed","proceed")
),width = 10
)
)
),
server = function(input, output,session) {
#to add reactivity to the show button
df_filter <- reactive({
text_input <- trimws(strsplit(input$text_input, ",")[[1]])
df_filter <- df[df$make %chin% text_input, input$somevalue]
})%>% shiny::bindEvent(input$show_table)
#to output hte dt table with the filters
output$table <- DT::renderDT({
DT::datatable(df_filter(),
editable = 'cell',
options = list(scrollX = TRUE , lengthChange = FALSE, autoWidth = TRUE)
# editable = list(target = "row", disable = list(columns = c(2, 4, 5))))
)
})%>% shiny::bindEvent(df_filter())
}
)

Shiny output elements not displaying in bs4Dash with nested modules

EDIT: The author of the bs4Dash package, David Granjon, recently provided an answer to the question asked in the Github issue referenced below and closed it.
My question is very likely related to this issue in bs4Dash github repo, but no answer was provided there.
The full reproducible codes are at the end of the question
My goal
I am making a modularized Shiny application and am attempting to do it with the bs4Dash package. This is what the application looks like:
Picture 1
The end application has several sections (I only made the Introduction for this example) and each section contains at least one bs4TabCard. The tabcard in the picture above has one uiOutput and one rhandsontableOutput element, which are rendered in the server function. Note that these are both ***Output elements. In the reproducible code for Picture 1 (which you can find at the end of the question), I do not use any module. However, my goal is to use several modules because the application has the potential to become quite large. For this simple example, I try to use two modules: one module for each section (i.e. each bs4TabItem) and one module for each tabcard. This means that the two modules will invariably be nested: the tabcard module will be inside the section module.
Picture 2
The issue
The issue is that when I implement the modules, the ***Output elements are not displayed:
Picture 3
The surprising thing is that ***Input elements are displayed. I made a third module containing a numericInput only and placed it in the second tab of the tabcard. The picture below shows that the numericInput is displayed with no problem:
Picture 4
I did my homework
In this issue, a similar problem is reported, but there has not been any solution offered and my digging around proved unsuccessful. It seems that there is a problem when an output element is placed deep inside several nested containers in bs4Dash.
The reproducible code
Reproducible code for picture 1
library(shiny)
library(bs4Dash)
library(rhandsontable)
shiny::shinyApp(
ui = bs4DashPage(
old_school = FALSE,
sidebar_min = TRUE,
sidebar_collapsed = FALSE,
controlbar_collapsed = FALSE,
controlbar_overlay = TRUE,
title = "Basic Dashboard",
navbar = bs4DashNavbar(),
sidebar = bs4DashSidebar(
sidebarMenu(
bs4Dash::menuItem(
text = "Introduction",
tabName = "tab-introduction",
icon = ""
)
)
),
controlbar = bs4DashControlbar(),
footer = bs4DashFooter(),
body = bs4DashBody(
bs4TabItems(
bs4TabItem(
tabName = "tab-introduction",
bs4TabCard(
id = "tabcard", title = "Tab Card", side = "right",
bs4TabPanel(
tabName = "Tab 1",
uiOutput("ui"),
rHandsontableOutput("hot")
),
bs4TabPanel(
tabName = "Tab 2",
p("Hey")
)
)
)
)
)
),
server = function(input, output) {
output$hot <- renderRHandsontable({ rhandsontable(mtcars[1:10, 1:3]) })
output$ui <- renderUI({
numericInput("num_ui", label = "Num In", value = 15)
})
}
)
Reproducible code for Picture 3 and Picture 4
library(shiny)
library(bs4Dash)
library(rhandsontable)
# Tabcard module ----------------------------------------------------------
mod_tabcard_ui <- function(id){
ns <- NS(id)
bs4TabCard(
id = ns("tabcard"), title = "Tab Card", side = "right",
bs4TabPanel(
tabName = "Tab 1",
uiOutput(ns("ui")),
rHandsontableOutput(ns("hot"))
),
bs4TabPanel(
tabName = "Tab 2",
mod_numinput_ui(ns("num"))
)
)
}
mod_tabcard_server <- function(input, output, session){
output$hot <- renderRHandsontable({ rhandsontable(mtcars[1:10, 1:3]) })
output$ui <- renderUI({
numericInput(session$ns("num_ui"), label = "Num In", value = 15)
})
callModule(mod_numinput_server, "num")
}
# Numeric input module ----------------------------------------------------
mod_numinput_ui <- function(id){
ns <- NS(id)
numericInput(ns("num"), "Num In", 0, 0, 10)
}
mod_numinput_server <- function(input, output, server){
return(reactive({input$num}))
}
# Section module ----------------------------------------------------------
mod_section_ui <- function(id){
ns <- NS(id)
mod_tabcard_ui(id = "tabcard")
}
mod_section_server <- function(input, output, session){
callModule(mod_tabcard_server, id = "tabcard")
}
# The app -----------------------------------------------------------------
shiny::shinyApp(
ui = bs4DashPage(
old_school = FALSE,
sidebar_min = TRUE,
sidebar_collapsed = FALSE,
controlbar_collapsed = FALSE,
controlbar_overlay = TRUE,
title = "Basic Dashboard",
navbar = bs4DashNavbar(),
sidebar = bs4DashSidebar(
sidebarMenu(
bs4Dash::menuItem(
text = "Introduction",
tabName = "tab-introduction",
icon = ""
)
)
),
controlbar = bs4DashControlbar(),
footer = bs4DashFooter(),
body = bs4DashBody(
bs4TabItems(
bs4TabItem(
tabName = "tab-introduction",
mod_section_ui(id = "mod")
)
)
)
),
server = function(input, output) {
callModule(mod_section_server, id = "mod")
}
)
you are missing a namespace in the mod_section_ui module. It should be:
mod_section_ui <- function(id){
ns <- NS(id)
mod_tabcard_ui(id = ns("tabcard"))
}

Async: Display progress when actionButton is hit and disable other operations for the same user but allow concurrent users

Below is a sample code which takes two inputs: 1) input file and 2) input number of rows. Upon clicking the "Analyze" button the output from the server command return to the "Table" in "Results" tabset. This is a simple example where the command will be executed quickly and switches to the "Results" tabsetpanel.
The below withProgress code only shows the progress bar for the set time and disappears and then the actual code is executed. I would like to show a "Status Message" or "Progress Bar" when the "Analyze" is hit and show as long as the command is run. As long as the progress bar is running the current user (other users can use the app) cannot perform any action from the side bar. Because in the real app, sidebar has more menuItems which does similar tasks like this and each task has a Analyze button. If the user is allowed to browse to sidebar pages and hit Analyze then the app will have overload of performing multiple tasks. Ideally the progress bar functionality should we used with multiple actionButtons.
I read the blogs about async but unable to put right code in the right place. any help is appreciated with a bounty!!
library(shiny)
library(shinydashboard)
sidebar <- dashboardSidebar(width = 200,
sidebarMenu(id = "tabs",
menuItem(
"File", tabName = "tab1", icon = icon("fas fa-file")
)))
body <- tabItem(tabName = "tab1",
h2("Input File"),
fluidRow(
tabPanel(
"Upload file",
value = "upload_file",
fileInput(
inputId = "uploadFile",
label = "Upload Input file",
multiple = FALSE,
accept = c(".txt")
),
checkboxInput('header', label = 'Header', TRUE)
),
box(
title = "Filter X rows",
width = 7,
status = "info",
tabsetPanel(
id = "input_tab",
tabPanel(
"Parameters",
numericInput(
"nrows",
label = "Entire number of rows",
value = 5,
max = 10
),
actionButton("run", "Analyze")
),
tabPanel(
"Results",
value = "results",
navbarPage(NULL,
tabPanel(
"Table", DT::dataTableOutput("res_table"),
icon = icon("table")
)),
downloadButton("downList", "Download")
)
)
)
))
ui <-
shinyUI(dashboardPage(
dashboardHeader(title = "TestApp", titleWidth = 150),
sidebar,dashboardBody(tabItems(body))
))
server <- function(input, output, session) {
file_rows <- reactiveVal()
observeEvent(input$run, {
withProgress(session, min = 1, max = 15, {
setProgress(message = 'Analysis in progress',
detail = 'This may take a while...')
for (i in 1:15) {
setProgress(value = i)
Sys.sleep(0.5)
}
})
system(paste(
"cat",
input$uploadFile$datapath,
"|",
paste0("head -", input$nrows) ,
">",
"out.txt"
),
intern = TRUE)
head_rows <- read.delim("out.txt")
file_rows(head_rows)
})
observeEvent(file_rows(), {
updateTabsetPanel(session, "input_tab", "results")
output$res_table <-
DT::renderDataTable(DT::datatable(
file_rows(),
options = list(
searching = TRUE,
pageLength = 10,
rownames(NULL),
scrollX = T
)
))
})
output$downList <- downloadHandler(
filename = function() {
paste0("output", ".txt")
}, content = function(file) {
write.table(file_rows(), file, row.names = FALSE)
}
)
}
shinyApp(ui = ui, server = server)
Here is a solution based on the (absolutely under-star-ed) library(ipc).
I came across this library due to a question of #Dean Attali, where Joe Cheng mentioned it.
The quick-start guide of the ipc-package gives an example of what you are asking for: AsyncProgress.
Furthermore it provides an example on how to kill a future using AsyncInterruptor.
However, I haven't been able to test it yet.
I worked around the cancel-problem by using #Dean Attali's great package shinyjs to simply start a new session and ignore the old Future (You might be able to improve this, by using AsyncInterruptor).
But nevertheless, I gave your code a Future, dropped your system() cmd because I'm currently running R on Windows and found a way to disable (tribute to #Dean Attali) the analyze button session-wise by giving it session-dependant names:
library(shiny)
library(shinydashboard)
library(ipc)
library(promises)
library(future)
library(shinyjs)
library(datasets)
library(V8)
plan(multiprocess)
header <- dashboardHeader(title = "TestApp", titleWidth = 150)
sidebar <- dashboardSidebar(width = 200,
sidebarMenu(id = "tabs",
menuItem(
"File", tabName = "tab1", icon = icon("fas fa-file")
)))
body <- dashboardBody(useShinyjs(),
fluidRow(column(
12, tabItem(
tabName = "tab1",
h2("Input File"),
textOutput("shiny_session"),
tabPanel(
"Upload file",
value = "upload_file",
fileInput(
inputId = "uploadFile",
label = "Upload Input file",
multiple = FALSE,
accept = c(".txt")
),
checkboxInput('header', label = 'Header', TRUE)
),
box(
title = "Filter X rows",
width = 7,
status = "info",
tabsetPanel(
id = "input_tab",
tabPanel(
"Parameters",
numericInput(
"nrows",
label = "Entire number of rows",
value = 5,
max = 10
),
column(1, uiOutput("sessionRun")),
column(1, uiOutput("sessionCancel"))
),
tabPanel(
"Results",
value = "results",
navbarPage(NULL,
tabPanel(
"Table", DT::dataTableOutput("res_table"),
icon = icon("table")
)),
downloadButton("downList", "Download")
)
)
)
)
)))
ui <- shinyUI(dashboardPage(
header = header,
sidebar = sidebar,
body = body,
title = "TestApp"
))
server <- function(input, output, session) {
output$shiny_session <-
renderText(paste("Shiny session:", session$token))
file_rows <- reactiveVal()
run_btn_id <- paste0("run_", session$token)
cancel_btn_id <- paste0("cancel_", session$token)
output$sessionRun <- renderUI({
actionButton(run_btn_id, "Analyze")
})
output$sessionCancel <- renderUI({
actionButton(cancel_btn_id, "Cancel")
})
paste("Shiny session:", session$token)
observeEvent(input[[run_btn_id]], {
file_rows(NULL)
shinyjs::disable(id = run_btn_id)
progress <- AsyncProgress$new(message = 'Analysis in progress',
detail = 'This may take a while...')
row_cnt <- isolate(input$nrows)
get_header <- isolate(input$header)
future({
fileCon <- file("out.txt", "w+", blocking = TRUE)
linesCnt <- nrow(iris)
for (i in seq(linesCnt)) {
Sys.sleep(0.1)
progress$inc(1 / linesCnt)
writeLines(as.character(iris$Species)[i],
con = fileCon,
sep = "\n")
}
close(fileCon)
head_rows <- read.delim("out.txt", nrows = row_cnt, header=get_header)
progress$close() # Close the progress bar
return(head_rows)
}) %...>% file_rows
return(NULL) # Return something other than the future so we don't block the UI
})
observeEvent(input[[cancel_btn_id]],{
session$reload()
})
observeEvent(file_rows(), {
shinyjs::enable(id = run_btn_id)
updateTabsetPanel(session, "input_tab", "results")
output$res_table <-
DT::renderDataTable(DT::datatable(
req(file_rows()),
options = list(
searching = TRUE,
pageLength = 10,
rownames(NULL),
scrollX = T
)
))
})
output$downList <- downloadHandler(
filename = function() {
paste0("output", ".txt")
},
content = function(file) {
write.table(file_rows(), file, row.names = FALSE)
}
)
}
shinyApp(ui = ui, server = server)
App running:
This question has been answered on a different forum
For future reference, if anyone comes across this question, here's the full answer (I did not come up with this answer, it's by Joe Cheng)
This seems to be the main piece of code you're asking about:
observeEvent(input$run, {
withProgress(session, min = 1, max = 15, {
setProgress(message = 'Analysis in progress',
detail = 'This may take a while...')
for (i in 1:15) {
setProgress(value = i)
Sys.sleep(0.5)
}
})
system(paste(
"cat",
input$uploadFile$datapath,
"|",
paste0("head -", input$nrows) ,
">",
"out.txt"
),
intern = TRUE)
head_rows <- read.delim("out.txt")
file_rows(head_rows)
})
With futures/promises, you need to clearly decide what operations happen inside of the Shiny process, and what operations happen in the future process. In this case, here are the steps that we want to happen, in order:
Show progress message (Shiny process)
Read reactives: input$uploadFile$datapath, input$nrows (Shiny)
Write all but the last nrows to out.txt (future process)
Read out.txt (Could be either, let's say future)
Dismiss progress (Shiny)
Assign result to file_rows (Shiny)
Here's what that looks like:
observeEvent(input$run, {
prog <- Progress$new(session)
prog$set(message = "Analysis in progress",
detail = "This may take a while...",
value = NULL)
path <- input$uploadFile$datapath
nrows <- input$nrows
future({
readLines(path) %>% head(-nrows) %>% writeLines("out.txt")
read.delim("out.txt")
}) %...>%
file_rows() %>%
finally(~prog$close())
})
As long as the future/promise pipeline is the last expression in the observeEvent (which it is in this case, as file_rows() and finally(...) are part of the pipeline) then Shiny will hold off on processing any messages on behalf of the user.
There are two things this solution doesn't address.
Progress messages take a step back; not only are we forced to use the Progress$new() syntax instead of the cleaner withProgress(), but we lost the ability to report on the progress percentage. You can try the new ipc package for a solution to that problem.
This doesn't stop the user from clicking around in the UI; it won't do anything while the async operation is executing, but when the operation is done those interactions will have accumulated in a queue and will be handled in the order that they arrived. If you'd like to actually disable the UI entirely so that they're not able to do anything at all, there's not currently a built-in way to do that in Shiny. Although come to think of it, you might try replacing the use of Progress with showModal(modalDialog(title = "Analysis in progress", "This may take a while...", footer=NULL)); I think that will at least stop mouse clicks.

Popup list of selectInput is hiding behind navBarPage

Please consider below ShinyApp with navBarPage & selectInput.
shinyApp(
ui = fluidPage(
selectInput("variable", "Variable:",
c("Cylinders" = "cyl",
"Transmission" = "am",
"Gears" = "gear")),
navbarPage(title = "",
tabPanel("Scene 01",
fluidRow(tableOutput("data"))
),
tabPanel("Scene 02", fluidRow()))
),
server = function(input, output) {
output$data <- renderTable({
mtcars[, c("mpg", input$variable), drop = FALSE]
}, rownames = TRUE)
}
)
As you see, when the popup-baloon of selectInput opens (i.e. when User clicks on the drop-down icon of selectInput), it hides behind the strip of navBarPage. Is there any way to bring that popup-baloon forward, instead of hiding behind the navBarPage srip.
Appreciate for your help.
Thanks,
You can use css to make the z-index of selectinput dropdown more than that of nav-bar header using the following tag:
tags$div(tags$style(HTML( ".selectize-dropdown, .selectize-dropdown.form-control{z-index:10000;}")))
In your app it would be as follows:
shinyApp(
ui = fluidPage(
tags$div(tags$style(HTML( ".selectize-dropdown, .selectize-dropdown.form-control{z-index:10000;}"))),
selectInput("variable", "Variable:",
c("Cylinders" = "cyl",
"Transmission" = "am",
"Gears" = "gear")),
navbarPage(title = "",
tabPanel("Scene 01",
fluidRow(tableOutput("data"))
),
tabPanel("Scene 02", fluidRow()))
),
server = function(input, output) {
output$data <- renderTable({
mtcars[, c("mpg", input$variable), drop = FALSE]
}, rownames = TRUE)
}
)
You will get something like this:
Hope it helps!

R shiny ColVis and datatable search

I have TableTools and ColVis working together, but, as it was explained in another post (R shiny DataTables ColVis behavior), when clicking the Show/hide columns button, the list mixes up with the values in the table underneath, and I cannot make the list disappear.
In that post is mentioned that shiny atm is not compatible with the current data.table version, and I'd like to know if there's any other solution around. Here's my code:
ui.R
library(shiny)
library(shinythemes)
library(ggplot2)
addResourcePath('datatables','\\Users\\Ser\\Downloads\\DataTables-1.10.7\\DataTables-1.10.7\\media')
addResourcePath('tabletools','\\Users\\Ser\\Downloads\\TableTools-2.2.4\\TableTools-2.2.4')
shinyUI(fluidPage(theme = shinytheme("Journal"),
tags$head(
tags$style(HTML("
#import url('//fonts.googleapis.com/css?family=Lobster|Cabin:400,700');
"))
),
headerPanel(
h1("List Manager",
style = "font-family: 'Lobster', cursive;
font-weight: 500; line-height: 1.1;
color: #ad1d28;")),
sidebarLayout(
sidebarPanel(
#File Upload Manager
fileInput('file1', 'Choose file to upload'),
tagList(
singleton(tags$head(tags$script(src='//cdnjs.cloudflare.com/ajax/libs/datatables/1.10.7/js/jquery.dataTables.min.js',type='text/javascript'))),
singleton(tags$head(tags$script(src='//cdnjs.cloudflare.com/ajax/libs/datatables-tabletools/2.1.5/js/TableTools.min.js',type='text/javascript'))),
singleton(tags$head(tags$script(src='//cdnjs.cloudflare.com/ajax/libs/datatables-tabletools/2.1.5/js/ZeroClipboard.min.js',type='text/javascript'))),
singleton(tags$head(tags$link(href='//cdnjs.cloudflare.com/ajax/libs/datatables-tabletools/2.1.5/css/TableTools.min.css',rel='stylesheet',type='text/css'))),
singleton(tags$head(tags$script(src='//cdn.datatables.net/colvis/1.1.0/js/dataTables.colVis.min.js',type='text/javascript'))),
singleton(tags$script(HTML("if (window.innerHeight < 400) alert('Screen too small');")))
)),
mainPanel(
dataTableOutput("mytable"))
)))
server.R
shinyServer(function(input, output) {
output$mytable = renderDataTable({
inFile <- input$file1
if (is.null(inFile))
return(NULL)
read.table(inFile$datapath, header=TRUE, sep='')
}, options = list(
"dom" = 'TC<"clear">lfrtip',
"colVis" = list(
"activate"="click",
"align"="right"),
"oTableTools" = list(
"sSwfPath" = "//cdnjs.cloudflare.com/ajax/libs/datatables-tabletools/2.1.5/swf/copy_csv_xls.swf",
"aButtons" = list(
"copy",
"print",
list("sExtends" = "collection",
"sButtonText" = "Save",
"aButtons" = c("csv","xls")
)
)
)
)
)
})
I also have another question: I'd like to search "<" or ">" values in the searchboxes at the bottom of the table, but I can't make it work. I don't know if I have to add anything to the code so it can be done (such as "regex" or similar).
You may try the DT package instead of hacking the JS libraries by yourself. Here is a minimal example:
library(shiny)
library(DT)
library(shinythemes)
shinyApp(
ui = fluidPage(
theme = shinytheme('journal'),
fluidRow(column(12, DT::dataTableOutput('foo')))
),
server = function(input, output) {
output$foo = DT::renderDataTable(
iris,
filter = 'bottom',
extensions = list(ColVis = list(activate= "click", align = "right")),
options = list(dom = 'C<"clear">lfrtip')
)
}
)
See http://rstudio.github.io/DT/extensions.html for more info on DataTables extensions.