I created a module to help me accept 1)an excel file 2)a text input for the sheet name and 3) a text input for the range.
I want to be able to use this module in an App such that each time I click on the action button (AddExcelDataButton in the code below), it allows me to input a different file. I also need to be able to extract the contents of the file later.
I tried the below code in the main App, but it is throwing me the following errors
error1: The UI is linking all the inputs "inside the button"
error2: I am unable to figure out how to "access" or retrieve the filenames later in the code.
Any help in doing this the right way is highly appreciated please!
CODE FOR THE MODULE:
importExceldataUI <- function(importExceldata){
tagList(
tags$div(
HTML(paste0("<b>", "Enter Your Data Here"))
),
tags$div(
fileInput(inputId = "ImportExcelFile",
label = "Excel File",
multiple=FALSE),
style = "display:inline-block; vertical-align:top"
),# end of tags$div for fileInput ImportExcelFile
tags$div(
textInput(inputId = "ExcelSheetName",
label = "Sheet",
value="Data",),
style = "display:inline-block; vertical-align:top"
),#end of tags$Div for texinput-ExcelSheetName
tags$div(
textInput(inputId = "ExcelSheetRange",
label = "Range",
value = "C5:BN1000"),
style = "display:inline-block"
)#end of tags$div for textInput - sheetrange
)
}
importExceldataServer <- function(importExceldata){
moduleServer(importExceldata, function(input, output, session){
})
}
CODE FOR MAIN APP
importExceldataApp <- function(){
ui <- fluidPage(
mainPanel(
actionButton(inputId = "AddExcelDataButton", label = "Click here to add Excel Data"),
)#emd pf mainpanel
)
server <- function(input, output, session){
observeEvent(input$AddExcelDataButton, {
insertUI(selector = "#AddExcelDataButton",
ui = importExceldataUI(paste0("file",input$AddExcelDataButton)))
})#end of observeEvent
}
shinyApp(ui, server)
}
importExceldataApp()
There are some errors you need to fix:
to use modules, you must have IDs for both UI and server. For each pair, they must have the same ID.
For ids in module UI, you must use namespace NS.
For insertUI, default is insert into the selector, apparently, you don't want to insert inside a button, you need to add after the button, so you need have where argument, please read the help file of this function.
You should read more about shiny modules standards
Here is the working code:
library(shiny)
importExceldataUI <- function(id){
ns <- NS(id)
tagList(
tags$div(
HTML(paste0("<b>", "Enter Your Data Here"))
),
tags$div(
fileInput(inputId = ns("ImportExcelFile"),
label = "Excel File",
multiple=FALSE),
style = "display:inline-block; vertical-align:top"
),# end of tags$div for fileInput ImportExcelFile
tags$div(
textInput(inputId = ns("ExcelSheetName"),
label = "Sheet",
value="Data"),
style = "display:inline-block; vertical-align:top"
),#end of tags$Div for texinput-ExcelSheetName
tags$div(
textInput(inputId = ns("ExcelSheetRange"),
label = "Range",
value = "C5:BN1000"),
style = "display:inline-block"
)#end of tags$div for textInput - sheetrange
)
}
importExceldataServer <- function(id){
moduleServer(id, function(input, output, session){
observeEvent(input$ImportExcelFile, {
req(input$ImportExcelFile)
print(input$ImportExcelFile$datapath)
})
})
}
importExceldataApp <- function(){
ui <- fluidPage(
mainPanel(
actionButton(inputId = "AddExcelDataButton", label = "Click here to add Excel Data"),
)#emd pf mainpanel
)
server <- function(input, output, session){
observeEvent(input$AddExcelDataButton, {
insertUI(selector = "#AddExcelDataButton", where = "afterEnd",
ui = importExceldataUI(paste0("file",input$AddExcelDataButton)))
importExceldataServer(paste0("file",input$AddExcelDataButton))
})#end of observeEvent
}
shinyApp(ui, server)
}
importExceldataApp()
So to read the path of uploaded file, just use input$ImportExcelFile$datapath, datapath is the file location. Here in my code, I just print it out, you can do other things.
Related
I've created a SelectizeInput() UI using the insertUI() function. Essentially, I have an action button which adds a SelectizeInput() every time it's clicked. The idea is that the user selects columns from their data to put into groups. The creation of the UI works fine. I can also see in the Shiny trace that the selection works fine. However, I'm unsure how to access these variables to use in later plots. This is the code I have:
UI:
actionButton("cr_exp", "Create new biological group")
Server:
observeEvent(input$cr_exp, {
insertUI(
selector = "#cr_exp",
where = "afterEnd",
ui = selectizeInput(inputId = paste0("grp", input$cr_exp), label = "Select samples", choices = colnames(exp_dff()), options = list(create=TRUE), multiple=TRUE))
tags$div(id = paste0("grp", input$cr_exp))
})
In the shiny trace, it shows that the group is created, but I can't figure out how to access the value:
RECV {"method":"update","data":{"grp1":["MV4negControl01","MV4negControl02"]}}
You access the values just like any other input values: by the input element’s
id from the input reactive values:
library(shiny)
ui <- fluidPage(
actionButton("cr_exp", "Create new biological group"),
verbatimTextOutput("choices")
)
server <- function(input, output, session) {
observeEvent(input$cr_exp, {
insertUI(
selector = "#cr_exp",
where = "afterEnd",
ui = selectizeInput(
inputId = paste0("grp", input$cr_exp),
label = "Select samples",
choices = LETTERS,
options = list(create = TRUE),
multiple = TRUE
)
)
tags$div(id = paste0("grp", input$cr_exp))
})
output$choices <- renderPrint({
lapply(seq_len(input$cr_exp), function(i) input[[paste0("grp", i)]])
})
}
shinyApp(ui, server)
I have a shiny app with a button which calls a modal dialog where a UI should be inserted. I'm using insertUI because I want to add UI elements dynamically. The problem is, whenever I press the action button, the app crashes with the following error message:
Warning: Error in as.character: cannot coerce type 'closure' to vector of type 'character'
[No stack trace available]
If I set immediate = T in the insertUI() then there is no error, but also no UI inserted. Can someone explain what's going on here? Here's a reprex.
library(shiny)
ui <- fluidPage(
actionButton("add", "addConstraints")
)
server <- function(input, output, session) {
observeEvent(input$add, {
showModal(modalDialog(
selectizeInput(session$ns("constraintType"), label = "Select constraint type", choices = c("Comparison", "Numeric", "Interval")),
tags$div(id = session$ns("constraintPlaceholder")),
insertUI(
selector = paste0("#", session$ns("constraintPlaceholder")),
where = "afterEnd",
ui = HTML("test")
),
title = "Set Constraints",
footer = tagList(
modalButton("Cancel"),
actionButton(session$ns("confirmConstraint"), "Add")
)
))
})
}
shinyApp(ui, server)
insertUI needs to be called outside modalDialog. What inside modalDialog is UI components, they should be shinyTags, or HTML elements. insertUI is a server call, it can't be added to modalDialog
library(shiny)
ui <- fluidPage(
actionButton("add", "addConstraints")
)
server <- function(input, output, session) {
observeEvent(input$add, {
showModal(modalDialog(
selectizeInput(session$ns("constraintType"), label = "Select constraint type", choices = c("Comparison", "Numeric", "Interval")),
tags$div(id = session$ns("constraintPlaceholder")),
title = "Set Constraints",
footer = tagList(
modalButton("Cancel"),
actionButton(session$ns("confirmConstraint"), "Add")
)
))
insertUI(
selector = paste0("#", session$ns("constraintPlaceholder")),
where = "afterEnd",
ui = HTML("test")
)
})
}
shinyApp(ui, server)
I would like to have one of the tabPanels in my Shiny app launch a shinyFiles style input. In this case I would like to launch a shinySaveButton, without the shinySaveButton being in my dataset (By clicking the save icon [which is actually a tabPanel])
Reproducible example below
library(shiny)
library(shinyFiles)
ui <- navbarPage('Test App',id = "inTabset", selected="panel1",
tabPanel(title = "", value = "Save", icon = icon("save")),
tabPanel(title = "Panel 1", value = "panel1",
h1("Panel1")),
tabPanel(title = "Panel 2",value = "panel2",
h1("Panel2"))
)
server <- function(input, output, session) {
values = reactiveValues(tabSelected="panel1")
observe({
if (input$inTabset=="Save") {
updateNavbarPage(session,"inTabset",selected=values$tabSelected)
#CODE FOR LOADING SHINYFILES DIALOG IN HERE
} else {
values$tabSelected<-input$inTabset
}
})
}
shinyApp(ui, server)
Any help would be greatly appreciated.
Work around using hidden element trick
library(shiny)
library(shinyFiles)
library(shinyjs)
jsCode<-"shinyjs.saveButton=function(){ $('#buttonFileSaveHidden').click(); }"
ui <- fluidPage(
useShinyjs(),
extendShinyjs(text = jsCode),
navbarPage('Test App',id = "inTabset", selected="panel1",
tabPanel(title = "", value = "Save", icon = icon("save")),
tabPanel(title = "Panel 1", value = "panel1",
h1("Panel1")
),
tabPanel(title = "Panel 2",value = "panel2",
h1("Panel2"))
),
# HIDDEN BUTTON TO INITIATE THE SAVE
hidden(shinySaveButton( "buttonFileSaveHidden",
label="",
title="Save as ...",
list('hidden_mime_type'=c("R")),
class='hiddenButton')),
wellPanel( #ONLY INCLUDED TO DISPLAY OF PATH INFO OF THE CHOICE
h3('Current save path info'),
tableOutput('table')
)
)
server <- function(input, output, session) {
values = reactiveValues(tabSelected="panel1")
observe({
if (input$inTabset=="Save") {
updateNavbarPage(session,"inTabset",selected=values$tabSelected)
#CODE FOR LOADING SHINYFILES DIALOG IN HERE
js$saveButton()
} else {
values$tabSelected<-input$inTabset
}
})
shinyFileSave(input, "buttonFileSaveHidden", session=session, roots=c(wd="~"), filetypes=c('R') ) #hidden
# GET THE SAVE PATH CHOICE AND RECORD IT IN fp.dt.rv
fp.dt.rv<-reactiveVal("")
observeEvent(input$buttonFileSaveHidden,{
fp.dt<-parseSavePath(c(wd='~'), input$buttonFileSaveHidden)
fp.dt.rv(fp.dt) #or just use to immediately write.
})
# ONLY TO DISPLAY THE SAVE CHOICE
output$table <- renderTable(fp.dt.rv())
}
shinyApp(ui, server)
I have two inputs to be removed from the ui.R
fileInput(inputId = "FileInput",label = "Choose a csv file",accept = '.csv'),
uiOutput("SKU")
Inside server i am using an observeEvent to remove these 2 inputs and insert one. Though the insertUi is working I am not able to remove the other 2.
PFB the code:
observeEvent(input$Save,{
removeUI(
selector = "div:has(> #FileInput)"
)
insertUI(
selector = "#Save",
where = "afterEnd",
ui =fluidPage(
tags$hr(),
fluidRow(column(offset=0,1,actionButton("clean","Start cleaning the Data")))
)
)
})
It seems you have to treat it the same way in which the shiny blog example treats text by wrapping it in a div with id.
tags$div(
fileInput(inputId = "FileInput",label = "Choose a csv file",accept = '.csv'),
id='FileInput'
)
Example
ui <- fluidPage(
mainPanel(
tags$div(fileInput('element1','Input file...'),id='element1'),
actionButton('remove','Remove File Input')
)
)
server <- function(input, output) {
observeEvent(input$remove,{
removeUI(selector = '#element1')
})
}
shinyApp(ui = ui, server = server)
I would like to implement a 'Reset inputs' button in my shiny app.
Here is an example with just two inputs where I'm using the update functions to set the values back to the default values:
library(shiny)
runApp(list(
ui = pageWithSidebar(
headerPanel("'Reset inputs' button example"),
sidebarPanel(
numericInput("mynumber", "Enter a number", 20),
textInput("mytext", "Enter a text", "test"),
tags$hr(),
actionButton("reset_input", "Reset inputs")
),
mainPanel(
h4("Summary"),
verbatimTextOutput("summary")
)
),
server = function(input, output, session) {
output$summary <- renderText({
return(paste(input$mytext, input$mynumber))
})
observe({
input$reset_input
updateNumericInput(session, "mynumber", value = 20)
updateTextInput(session, "mytext", value = "test")
})
}
))
What I would like to know is if there is also a function that sets back everything to default? That would be useful in case of multiple inputs.
Additionally, I'm not sure if my use of the observe function in order to detect when the action button was hit is the 'proper way' of handling the action buttons?
First of all, your use of the observer is correct, but there is another way that's slightly nicer. Instead of
observe({
input$reset_input
updateNumericInput(session, "mynumber", value = 20)
updateTextInput(session, "mytext", value = "test")
})
You can change it to
observeEvent(input$reset_input, {
updateNumericInput(session, "mynumber", value = 20)
updateTextInput(session, "mytext", value = "test")
})
Also note that you don't need to explicitly "return" from a renderText function, the last statement will automatically be used.
Regarding the main question: Matthew's solution is great, but there's also a way to achieve what you want without having to move all your UI into the server. I think it's better practice to keep your UI in the UI file just because separation of structure and logic is generally a good idea.
Full disclaimer: my solution involves using a package that I wrote. My package shinyjs has a reset function that allows you to reset an input or an HTML section back to its original value. Here is how to tweak your original code to your desired behaviour in a way that will scale to any number of inputs without having to add any code. All I had to do is add a call to useShinyjs() in the UI, add an "id" attribute to the form, and call reset(id) on the form.
library(shiny)
runApp(list(
ui = pageWithSidebar(
headerPanel("'Reset inputs' button example"),
sidebarPanel(
shinyjs::useShinyjs(),
id = "side-panel",
numericInput("mynumber", "Enter a number", 20),
textInput("mytext", "Enter a text", "test"),
tags$hr(),
actionButton("reset_input", "Reset inputs")
),
mainPanel(
h4("Summary"),
verbatimTextOutput("summary")
)
),
server = function(input, output, session) {
output$summary <- renderText({
return(paste(input$mytext, input$mynumber))
})
observeEvent(input$reset_input, {
shinyjs::reset("side-panel")
})
}
))
There isn't such a function in shiny, however, here's a way to accomplish this without having to essentially define your inputs twice. The trick is to use uiOutput and wrap the inputs you want to reset in a div whose id changes to something new each time the reset button is pressed.
library(shiny)
runApp(list(
ui = pageWithSidebar(
headerPanel("'Reset inputs' button example"),
sidebarPanel(
uiOutput('resetable_input'),
tags$hr(),
actionButton("reset_input", "Reset inputs")
),
mainPanel(
h4("Summary"),
verbatimTextOutput("summary")
)
),
server = function(input, output, session) {
output$summary <- renderText({
return(paste(input$mytext, input$mynumber))
})
output$resetable_input <- renderUI({
times <- input$reset_input
div(id=letters[(times %% length(letters)) + 1],
numericInput("mynumber", "Enter a number", 20),
textInput("mytext", "Enter a text", "test"))
})
}
))
Here is yet another option that works for either static or dynamic inputs, and doesn't involve re-rendering inputs entirely.
It uses:
reactiveValuesToList to get all initial input values, and (optionally) any dynamic input values that get initialized afterward.
session$sendInputMessage to update values for generic inputs. The updateXyzInput functions call this under the hood like session$sendInputMessage(inputId, list(value = x, ...).
Every Shiny input uses value for its input message, and almost all will update with their input value as-is. Only a two inputs I've found need special casing - checkboxGroupInput to not send NULL when nothing is checked, and dateRangeInput to convert its c(start, end) to a list(start = start, end = end).
It may not be a good idea to blindly reset ALL inputs (even tabs will be reset), but this can easily be adapted to reset a filtered set of inputs.
library(shiny)
ui <- pageWithSidebar(
headerPanel("'Reset inputs' button example"),
sidebarPanel(
numericInput("mynumber", "Enter a number", 20),
textInput("mytext", "Enter text", "test"),
textAreaInput("mytextarea", "Enter text", "test"),
passwordInput("mypassword", "Enter a password", "password"),
checkboxInput("mycheckbox", "Check"),
checkboxGroupInput("mycheckboxgroup", "Choose a number", choices = c(1, 2, 3)),
radioButtons("myradio", "Select a number", c(1, 2, 3)),
sliderInput("myslider", "Select a number", 1, 5, c(1,2)),
uiOutput("myselUI"),
uiOutput("mydateUI"),
tags$hr(),
actionButton("reset_input", "Reset inputs")
),
mainPanel(
h4("Summary"),
verbatimTextOutput("summary")
)
)
server <- function(input, output, session) {
initialInputs <- isolate(reactiveValuesToList(input))
observe({
# OPTIONAL - save initial values of dynamic inputs
inputValues <- reactiveValuesToList(input)
initialInputs <<- utils::modifyList(inputValues, initialInputs)
})
observeEvent(input$reset_input, {
for (id in names(initialInputs)) {
value <- initialInputs[[id]]
# For empty checkboxGroupInputs
if (is.null(value)) value <- ""
session$sendInputMessage(id, list(value = value))
}
})
output$myselUI <- renderUI({
selectInput("mysel", "Select a number", c(1, 2, 3))
})
output$mydateUI <- renderUI({
dateInput("mydate", "Enter a date")
})
output$summary <- renderText({
return(paste(input$mytext, input$mynumber))
})
}
shinyApp(ui, server)
You can also create a reset button by assigning NULL to your reactive values object.
See this RStudio Shiny article on Using Action Buttons: http://shiny.rstudio.com/articles/action-buttons.html. Specifically, read the sections titled Pattern 4 - Reset buttons and Pattern 5 - Reset on tab change. Examples (including code) are provided in the article.
The article provides solutions that don't require additional packages if that's a concern.