I'm trying to write a small shiny app where the user can select multiple folders through a button. The number of folders selected varies according to the needs of the user. The selection of one folder works. If I set "multiple = TRUE", then also only one folder is selected. Does anyone have an idea how I can select multiple folders with a single button?
Here is my code:
library(shiny)
library(shinydashboard)
library(shinyFiles)
library(shinyWidgets)
ui <- fluidPage(
shinyDirButton("preinfolder", "Choose a folder" ,
title = "Please select a folder:",
buttonType = "default", class = NULL,
icon = icon("folder", lib = "font-awesome"), multiple = TRUE),
textOutput("prein_txt_file")
)
server <- function(input, output, session) {
volumes = getVolumes()()
observe({
shinyDirChoose(input, "preinfolder", roots = volumes, session = session)
if(!is.null(input$preinfolder)){
# browser()
preinfolder_selected<-parseDirPath(volumes, input$preinfolder)
output$prein_txt_file <- renderText(preinfolder_selected)
}})
}
shinyApp(ui, server)
Thanks for any idea
I am still trying to solve the problem. I want a list of selected folders.
For my previous solution, I can select multiple folders but only from one parent folder.
library(shiny)
library(shinydashboard)
library(shinyFiles)
library(shinyWidgets)
ui <- fluidPage(
shinyDirButton("preinfolder", "Choose a folder" ,
title = "Please select a folder:",
buttonType = "default", class = NULL,
icon = icon("folder", lib = "font-awesome"), multiple = TRUE),
uiOutput(outputId = "sel_subfolder"),
textOutput("prein_txt_file")
)
server <- function(input, output, session){
volumes = getVolumes()()
observe({
shinyDirChoose(input, "preinfolder", roots = volumes, session = session)
if(!is.null(input$preinfolder)){
# browser()
preinfolder_selected<-parseDirPath(volumes, input$preinfolder)
output$prein_txt_file <- renderText(preinfolder_selected)
}})
df <- reactive({
if(is.null(input$preinfolder)) {
return(NULL)
} else {
fol <- parseDirPath(volumes, input$preinfolder)
return(fol)
}
})
output$sel_subfolder <- renderUI({
if(is.null(df())) {
return(NULL)
} else if(!is.null(df())) {
return(tags$div(align = "left",
class = "multicol",
checkboxGroupInput(inputId = "sel_subfolder",
label = "Select subfolder",
choices = unlist(list.files(parseDirPath(volumes, input$preinfolder))))))
}
})
}
shinyApp(ui, server)
I am still looking for a solution how to create a list of multiple folders with one input button.
This is the solution I found (a bit convoluted but it works).
You basically pass the selected folder to a updateCheckboxGroupInput function that appends the folder to a list of previously chosen ones
library(shiny)
library(shinyFiles)
ui <- fluidPage(
titlePanel("Test"),
sidebarLayout(
sidebarPanel(
shinyDirButton('fld','Choose...','Choose a folder'),
actionButton("submitbutton", "Add folder", class = "btn btn-primary"),
checkboxGroupInput('chosenfolders','Chosen folders...')
),
mainPanel(
verbatimTextOutput("selected")
)
)
)
server <- function(input, output, session) {
volumes <- c(Home = fs::path_home(), "R Installation" = R.home(), getVolumes()())
shinyDirChoose(input, 'fld', session=session,
root=volumes, filetypes=c('txt'))
datasetInput <- reactive({
inputfolders <- c(input$chosenfolders,
parseDirPath(volumes, input$fld))
updateCheckboxGroupInput(session, 'chosenfolders',
choices = inputfolders,
selected = inputfolders)
print(list('Input folders' = inputfolders))
})
output$selected <- renderPrint({
if (input$submitbutton>0) {
isolate(datasetInput())
} else {
return("Server is ready for calculation.")
}
})
}
shinyApp(ui = ui, server = server)
Related
Let's say I have 4 sets of UI/Server modules in 4 different directories ("./X1/Y1/", "./X1/Y2/", "./X2/Y1/", "./X2/Y2/"). I want to load the selected set based on the input in the sidebar.
I tried using source() within dashboardBody(), but I was not successful.
library(shiny)
library(shinydashboard)
# path to modules
in_path <- "C:/a/b/c/"
# ui
ui <- dashboardPage(
dashboardHeader(title = "test"),
dashboardSidebar(
br(),
selectInput('f1', 'Folder 1', choices = c("X1", "X2")),
helpText(""),
selectInput('f2', 'Folder 2', choices = c("Y1", "Y2")),
br(),
actionButton("load", "Load", icon("thumbs-up"), width = "85%")
),
dashboardBody(
# UI module here from, e.g., "C:/a/b/c/X1/Y2/my_UI.R"
)
)
# server
server <- function(input, output, session) {
# server module here from, e.g., "C:/a/b/c/X1/Y2/my_Server.R"
}
shinyApp(ui, server)
As shiny modules are simply functions, I'd source them in the beginning, and use uiOutput to display the differnt modules.
Here's a working example of the general idea (sample module code proudly stolen from the official Shiny documentation):
<mod1.R>
counterButton <- function(id, label = "Counter") {
ns <- NS(id)
tagList(
actionButton(ns("button"), label = label),
verbatimTextOutput(ns("out"))
)
}
counterServer <- function(id) {
moduleServer(
id,
function(input, output, session) {
count <- reactiveVal(0)
observeEvent(input$button, {
count(count() + 1)
})
output$out <- renderText({
count()
})
count
}
)
}
<mod2.R>
csvFileUI <- function(id, label = "CSV file") {
ns <- NS(id)
tagList(
fileInput(ns("file"), label),
checkboxInput(ns("heading"), "Has heading"),
selectInput(ns("quote"), "Quote", c(
"None" = "",
"Double quote" = "\"",
"Single quote" = "'"
))
)
}
csvFileServer <- function(id, stringsAsFactors = TRUE) {
moduleServer(
id,
## Below is the module function
function(input, output, session) {
# The selected file, if any
userFile <- reactive({
# If no file is selected, don't do anything
validate(need(input$file, message = FALSE))
input$file
})
# The user's data, parsed into a data frame
dataframe <- reactive({
read.csv(userFile()$datapath,
header = input$heading,
quote = input$quote,
stringsAsFactors = stringsAsFactors)
})
# We can run observers in here if we want to
observe({
msg <- sprintf("File %s was uploaded", userFile()$name)
cat(msg, "\n")
})
# Return the reactive that yields the data frame
return(dataframe)
}
)
}
<app.R>
library(shiny)
source("mod1.R")
source("mod2.R")
my_mods <- list("Counter Button" = list(ui = counterButton,
server = counterServer),
"CSV Uploader" = list(ui = csvFileUI ,
server = csvFileServer))
ui <- fluidPage(
sidebarLayout(
sidebarPanel(
selectInput("mod_sel",
"Which Module should be loaded?",
names(my_mods))
),
mainPanel(
uiOutput("content"),
verbatimTextOutput("out")
)
)
)
server <- function(input, output) {
uuid <- 1
handler <- reactiveVal()
output$content <- renderUI({
my_mods[[req(input$mod_sel)]]$ui(paste0("mod", uuid))
})
observeEvent(input$mod_sel, {
handler(my_mods[[req(input$mod_sel)]]$server(paste0("mod", uuid)))
uuid <<- uuid + 1
})
output$out <- renderPrint(req(handler())())
}
shinyApp(ui, server)
Some Explanation
You put the module code in mod[12].R and it is rather straight forward.
In your main app, you load both(!) source files and for housekeeping reasons, I put both modules functions (ui and server) in a list, but this is not strictly necessary, but facilitates future extension.
In your UI you have an uiOutput which renders dynamically according to the selected module.
In your server you put the code to dynamically render the UI and call the respective server function.
The uid construct is basically there to force a fresh render, whenever you change the selection. Otherwise, you may see still some old values whenever you come back to a module which you have rendered already.
I have the following app which allows for text to be entered and it is then saved as VALUE and printed on a panel.
Although it looks like I can only do this with one text input at a time - even if I click add (so I don't believe this button is working). On top of that I would like for the user to be able to add multiple inputs (like I have below).
And then my VALUE function should be list with multiple inputs.
code below
library(shiny)
ui <- fluidPage(
headerPanel("R Package App"),
sidebarPanel(
# selectInput("options", "options", choices=c('abc','def')),
textInput("textbox", "Enter R Package Name", ""),
actionButton("add","Add")
),
mainPanel(
textOutput("caption")
)
)
server <- function(input, output, session) {
observe({
VALUE <- ''
if(input$add>0) {
isolate({
VALUE <- input$textbox
})
}
updateTextInput(session, inputId = "textbox", value = VALUE)
})
output$caption <- renderText({
input$textbox
})
}
shinyApp(ui = ui, server = server)
Have you considered using selectizeInput with it's create option?
library(shiny)
packagesDF <- as.data.frame(installed.packages())
ui <- fluidPage(
headerPanel("R Package App"),
sidebarPanel(
selectizeInput(
inputId = "selectedPackages",
label = "Enter R Package Name",
choices = packagesDF$Package,
selected = NULL,
multiple = TRUE,
width = "100%",
options = list(
'plugins' = list('remove_button'),
'create' = TRUE,
'persist' = TRUE
)
)
),
mainPanel(textOutput("caption"))
)
server <- function(input, output, session) {
output$caption <- renderText({
paste0(input$selectedPackages, collapse = ", ")
})
}
shinyApp(ui = ui, server = server)
Consider the following example application:
library(shiny)
library(shinyWidgets)
module_UI <- function(id){
tagList(
div(
uiOutput(
outputId = NS(id, "selection")
),
shinyWidgets::dropdown(
uiOutput(outputId = NS(id, "new_option")),
style = "unite",
label = "New",
color = "primary",
animate = animateOptions(
enter = animations$fading_entrances$fadeInLeftBig,
exit = animations$fading_exits$fadeOutRightBig
),
up = F,
width = "600px",
inline = T
)
)
)
}
module_server <- function(id){
moduleServer(id, function(input, output, session){
ns <- session$ns
return_values <- reactiveValues(selection=NULL)
output$selection <- renderUI({
selectInput(inputId = ns("selection"), label = "Select:", choices = 1:5)
})
output$new_option <- renderUI({
div(
numericInput(ns("new_option_input"), label = "Add a new option:"),
shinyWidgets::actionBttn(
inputId = ns("submit_new_option"),
label = "Submit",
icon = icon("paper-plane"))
)
})
observeEvent(input$submit_new_option, {
#does not work as intended
updateSelectInput(session = session, inputId = "selection", selected = input$new_option_input)
})
observe({
return_values$selection <- input$selection
})
return(return_values)
})
}
# Define UI for application that draws a histogram
ui <- fluidPage(
title = "Test App",
module_UI("test"),
verbatimTextOutput(outputId = "selection_chosen")
)
# Define server logic required to draw a histogram
server <- function(input, output) {
picker <- module_server("test")
output$selection_chosen <- renderText({
picker$selection
})
}
# Run the application
shinyApp(ui = ui, server = server)
Basically, the module should do two things:
Allow user to select a pre-existing option --> return that value from module
Allow user to create their own, new option --> return that value from module
I have #1 working, but am struggling on #2. Specifically, where I have the "does not work" comment. How can I achieve this functionality? What are/is the best practice(s) for returning server-side created values from a Shiny module? This is an example app; the real one involves reading the selectInput options from a database, as well as saving the newly created options in the database. Appreciate any help on this! A lot of SO answers regarding Shiny modules have the older callModule(...) syntax, which makes researching this topic a bit more confusing.
You just need to provide the default value in numericInput. Perhaps you are looking for this.
library(shiny)
library(shinyWidgets)
module_UI <- function(id){
ns <- NS(id)
tagList(
div(
uiOutput(
outputId = NS(id, "selection")
),
shinyWidgets::dropdown(
uiOutput(outputId = NS(id, "new_option")),
style = "unite",
label = "New",
color = "primary",
animate = animateOptions(
enter = animations$fading_entrances$fadeInLeftBig,
exit = animations$fading_exits$fadeOutRightBig
),
up = F,
width = "600px",
inline = T
),
DTOutput(ns("t1"))
)
)
}
module_server <- function(id){
moduleServer(id, function(input, output, session){
ns <- session$ns
return_values <- reactiveValues(selection=NULL,myiris = iris)
output$selection <- renderUI({
selectInput(inputId = ns("selection"), label = "Select:", choices = 1:5)
})
output$new_option <- renderUI({
tagList(
numericInput(ns("new_option_input"), label = "Add a new option:",10, min = 1, max = 100),
shinyWidgets::actionBttn(
inputId = ns("submit_new_option"),
label = "Submit",
icon = icon("paper-plane"))
)
})
observeEvent(input$submit_new_option, {
return_values$myiris <- iris[1:input$new_option_input,]
#does work as intended
updateSelectInput(session = session, inputId = "selection", choices= c(1:input$new_option_input), selected = input$new_option_input)
})
output$t1 <- renderDT({return_values$myiris})
observe({
return_values$selection <- input$selection
})
return(return_values)
})
}
# Define UI for application that draws a histogram
ui <- fluidPage(
title = "Test App",
module_UI("test"),
verbatimTextOutput(outputId = "selection_chosen"),
DTOutput("t2")
)
# Define server logic required to draw a histogram
server <- function(input, output) {
picker <- module_server("test")
output$selection_chosen <- renderText({
picker$selection
})
output$t2 <- renderDT({picker$myiris[,c(3:5)]})
}
# Run the application
shinyApp(ui = ui, server = server)
I am currently facing a problem in Shiny where I am unable to display filtered data (user selected) into a newly created navbar tab. This had also led to another strange new tab removal problem.
Problem: I am stuck with the select data, appendtab (in navbar), outputUI and display/plot logic sequence in Shiny.
Scenario:
User selected data from local computer
User makes first selection from drop down list
Click on Add new tab
User makes second selection from drop down list
Click on Add new tab
Data used:
I don't know how to upload data on stackover flow but a simple csv table with two columns A and B will replicate the result below
Result:
Tab A: shows "Error: cannot coerce type 'closure' to vector of type 'character'"
Tab B: Delete tab function is now broken as well
My end goal to give more context: To be able to use this user selected data display charts, calcs, tables in the new tab.
What I did before it started erroring: I have followed similar logic to this post to display user filtered data in a new tab (not new navbartab though):
How to reuse a dataset in different objects when renderUI is used to create tabs in ShinyR
Also some help I got from Stackoverflow before this problem started. This may help with providing more context, all the answers from contributors worked:
Append and remove tabs using sidebarPanel
Can't get disable button to work with observeEvent with if statement in ShinyR
As always thank you very much for looking into my problem.
Cheers
library(shiny)
library(plyr)
library(dplyr)
library(DT)
library(shinyjs)
library(data.table)
ui <- fluidPage(
useShinyjs(),
navbarPage(title = "Stackoverflow help", id = "tabs",
tabPanel("Home",
sidebarPanel(
fileInput("file", "Upload data",
accept = c(
"text/csv",
"text/comma-separated-values,text/plain",
".csv")
),
#checkboxInput("header", "Header", TRUE),
actionButton("append", "Add new tab"),
uiOutput('tabnamesui')
),
mainPanel(
)
)
)
)
server <- function(input, output, session) {
userfile <- reactive({
input$file
})
filereact <- reactive({
read.table(
file = userfile()$datapath,
sep = ',',
header = T,
stringsAsFactors = T
)
})
tabsnames <- reactive({
names(filereact())
})
output$tabnamesui <- renderUI({
req(userfile())
selectInput(
'tabnamesui',
h5('Tab names'),
choices = as.list(tabsnames()),
selected="",multiple = FALSE
)
})
tabnamesinput <- reactive({
input$tabnamesui})
#Delete selected tab logic
observeEvent(input$append,{
appendTab(inputId = "tabs",
tabPanel(input$tabnamesui,
sidebarPanel(
actionButton(paste0("remove_", input$tabnamesui), "Delete")),
mainPanel(
uiOutput("tabsets") #This is where I think something is broken
)
)
)
})
observeEvent(lapply(grep(pattern = "^remove_", x = names(input), value = TRUE), function(x){input[[x]]}),{
if(input$tabs != "Home"){
removeTab(inputId = "tabs", target = input$tabs)
updateSelectInput(session, "tabnamesui", selected = input$tabnamesui) # keep the selection when re-rendering sidebarPanel
}
})
#New tab logic to prevent inserting same tab twice with enable/disable action button
forcecombine = function(idtab,checker) {
colnames(idtab) = colnames(checker)
rbind(idtab,checker)
}
checker<-as.data.frame("checker")
idtab<-as.data.frame("checkers")
#only allow tab entry once
observeEvent(input$append, {
idtab <- paste0(tabnamesinput())
idtab<-as.data.frame(idtab)
checkerx<-forcecombine(idtab,checker)
repeated<-length(grep(idtab,checkerx))
if(repeated==1)
{
shinyjs::disable("append")
}
else {shinyjs::enable("append")
}
})
observeEvent(tabnamesinput(), {
shinyjs::enable("append")
})
shinyjs::disable("append")
observeEvent(input$file, {
shinyjs::enable("append")
})
#Subdata section, I want to only use the data the user has selected for the new Navbar tab
output$tabsets<-renderUI({
req(userfile())
tabtable<-reactive({
lapply(tabnamesinput(), function(x)
dataTableOutput(paste0('table',x)))
})
})
subsetdata<-reactive({
list_of_subdata<-lapply(tabnamesinput(), function(x) {
as.data.table((select(filereact(),x)))
})
names(list_of_subdata)<-tabnamesinput()
return(list_of_subdata)
})
observe(
lapply(tabnamesinput(), function(x) {
output[[paste0('table',x)]] <-
renderDataTable({
subsetdata()[[x]]
})}))
}
shinyApp(ui, server)
You cannot output same ID in multiple tabs. Once you fix that, it works. You still need to define what you wish to display in each tab. I am just displaying a filtered table and a sample plot. Also, tab removal required minor tweak. Working code is shown below.
ui <- fluidPage(
useShinyjs(),
navbarPage(title = "Stackoverflow help", id = "tabs",
tabPanel("Home",
sidebarPanel(
fileInput("file", "Upload data",
accept = c(
"text/csv",
"text/comma-separated-values,text/plain",
".csv")
),
#checkboxInput("header", "Header", TRUE),
actionButton("append", "Add new tab"),
uiOutput('tabnamesui')
),
mainPanel(
)
)
)
)
server <- function(input, output, session) {
userfile <- reactive({
input$file
})
filereact <- reactive({
read.table(
file = userfile()$datapath,
sep = ',',
header = T,
stringsAsFactors = T
)
})
tabsnames <- reactive({
names(filereact())
})
output$tabnamesui <- renderUI({
req(userfile())
selectInput(
'tabnamesui',
h5('Tab names'),
choices = as.list(tabsnames()),
selected="",multiple = FALSE
)
})
tabnamesinput <- reactive({
input$tabnamesui})
#Append selected tab logic
observeEvent(input$append,{
appendTab(inputId = "tabs",
tabPanel(input$tabnamesui,
sidebarPanel(
actionButton(paste0("remove_", input$tabnamesui), "Delete")),
mainPanel(
#uiOutput("tabsets") #This is where I think something is broken
DTOutput(paste0("table",input$tabnamesui)),
plotOutput(paste0("plot",input$tabnamesui))
)
)
)
})
# Delete selected tab logic
observeEvent(lapply(grep(pattern = "^remove_", x = names(input), value = TRUE), function(x){input[[x]]}),{
if(input$tabs != "Home"){
if (input[[paste0("remove_",input$tabs)]]) { ## remove tab only if delete button has been clicked
removeTab(inputId = "tabs", target = input$tabs)
updateSelectInput(session, "tabnamesui", selected = input$tabnamesui) # keep the selection when re-rendering sidebarPanel
}
}
})
#New tab logic to prevent inserting same tab twice with enable/disable action button
forcecombine = function(idtab,checker) {
colnames(idtab) = colnames(checker)
rbind(idtab,checker)
}
checker<-as.data.frame("checker")
idtab<-as.data.frame("checkers")
#only allow tab entry once
observeEvent(input$append, {
idtab <- paste0(tabnamesinput())
idtab<-as.data.frame(idtab)
checkerx<-forcecombine(idtab,checker)
repeated<-length(grep(idtab,checkerx))
if(repeated==1)
{
shinyjs::disable("append")
}
else {shinyjs::enable("append")
}
})
observeEvent(input$tabnamesui, {
shinyjs::enable("append")
output[[paste0("plot",input$tabnamesui)]] <- renderPlot(plot(cars))
lapply(tabnamesinput(), function(x) {
df <- as.data.table(filereact()[[as.name(tabnamesinput())]])
output[[paste0('table',x)]] <- renderDT({
df
#subsetdata()[[x]]
})})
})
shinyjs::disable("append")
observeEvent(input$file, {
shinyjs::enable("append")
})
}
shinyApp(ui, server)
Try this:
library(plotly)
library(shiny)
library(DT)
ui <- fluidPage(
mainPanel(
plotlyOutput("SepalPlot"),
DT::dataTableOutput("Sepal"),
plotlyOutput("PetalPlot"),
DT::dataTableOutput("Petal")
)
)
server <- function(input, output) {
output$SepalPlot<- renderPlotly({
plot_ly(iris, x = ~Sepal.Length, y = ~Sepal.Width, type = 'scatter', mode = 'markers')
})
sep<-data.frame(c(iris$Sepal.Length, iris$Sepal.Width))
output$Sepal<-renderDataTable({datatable(sep)})
output$PetalPlot<- renderPlotly({
plot_ly(iris, x = ~Petal.Length, y = ~Petal.Width, type = 'scatter', mode = 'markers')
})
pet<-data.frame(c(iris$Petal.Length, iris$Petal.Width))
output$Petal<-renderDataTable({pet})
}
shinyApp(ui = ui, server = server)
Dear people of sackoverflow,
I am currently trying to create an app where a user can upload a dataset from various sources. I tried to isolate the code for the upload in a separate Shiny module. However, R throws an error saying that an environment object is not subsettable. I am fairly new to shiny so would appreciate any advice regarding the use of modules too. :)
library(shiny)
fileInputUI <- function(id){
ns <- NS(id)
fileInput(
inputId = ns("file"),
label = "Select a file"
)
}
fileInputServer <- function(id){
moduleServer(id, function(input, output, session){
dataset <- reactive({
req(input$file)
# Get file extension and datapath
ext <- tools::file_ext(input$file$name)
datapath <- input$file$datapath
# Need reactive values to store input data in
read_data_options <- reactiveValues()
read_data_options$sep <- NULL
if(ext == "csv"){
choices_sep <- c(",", ";", "", "\\t")
names(choices_sep) <- c("Comma (,)", "Semicolon (;)", "White space ( )", "Tab separated (\\t)")
showModal(
ui = modalDialog(
selectInput(
inputId = NS(id, "sep"),
label = "Which separator is used for your data",
choices = names(choices_sep),
selected = ""
),
# Input is required so no dismiss button
footer = tagList(
modalButton("Dismiss"),
actionButton(
inputId = NS(id, "submit_csv"),
label = "Submit")
),
easyClose = TRUE
)
)
# Set input value from submit button
observeEvent(
eventExpr = input$submit_csv,
handlerExpr = {
read_data_options$sep <- choices_sep[input$sep]
removeModal()
read.csv(
file = datapath,
sep = read_data_options$sep
)
}
)
} else {
NULL
}
})
dataset
})
}
# Shiny App
ui <- fluidPage(
fileInputUI("test"),
tableOutput("head_data")
)
server <- function(input, output, session) {
dataset <- fileInputServer("test")
output$head_data <- renderTable(head(dataset()))
}
shinyApp(ui, server)
I used modalDialog because I figured it to be the simplest approach to asking users about additional inputs to the read function for example the separator in case of read.csv or the startRow for read.xlsx etc.
Thanks for your replies