Disable button in UI based on input from module in Shiny app - r

In a Shiny app, I’m trying to disable/enable an action button in the UI of the main app based on user's input from a module. Basically, I want the “Next Page” (submit) button to be disabled until the user responds to the last item (item3). When the user responds to the last item, I want the button to be enabled. However, my app isn’t updating the toggle state of the action button.
Here’s a minimal reproducible example using a {Golem} structure:
app_ui.R:
library("shiny")
library("shinyjs")
app_ui <- function(request) {
tagList(
useShinyjs(),
fluidPage(
mod_mod1_ui("mod1_ui_1"),
actionButton(inputId = "submit",
label = "Next Page")
)
)
}
app_server.R:
library("shiny")
library("shinyjs")
app_server <- function( input, output, session ) {
state <- mod_mod1_server("mod1_ui_1")
# Enable the "Next Page" button when the user responds to the last item
observe({
toggleState("submit", state == TRUE)
})
}
mod_mod1.R:
library("shiny")
library("shinyjs")
mod_mod1_ui <- function(id){
ns <- NS(id)
tagList(
radioButtons(inputId = ns("item1"),
label = "Item 1",
choices = c(1, 2, 3, 4),
selected = character(0)),
radioButtons(inputId = ns("item2"),
label = "Item 2",
choices = c(1, 2, 3, 4),
selected = character(0)),
radioButtons(inputId = ns("item3"),
label = "Item 3",
choices = c(1, 2, 3, 4),
selected = character(0))
)
}
mod_mod1_server <- function(id){
moduleServer( id, function(input, output, session){
ns <- session$ns
# When the user completes the last survey question
completed <- logical(1)
observe({
lastQuestion <- input$item3
if(!is.null(lastQuestion)){
completed <- TRUE
} else {
completed <- FALSE
}
browser()
})
return(completed)
})
}
Using browser() statements, it appears that the completed variable is correctly being updated in the module, but that the state variable isn’t updating in the main app.

I am not clear if you want this to work when the user responds to only item3 or all items (1 thru 3). I am assuming that it is the latter. However, you can modify as your use case requires it. Defining a reactiveValues object works. Try this
library("shiny")
library("js")
mod_mod1_ui <- function(id){
ns <- NS(id)
tagList(
radioButtons(inputId = ns("item1"),
label = "Item 1",
choices = c(1, 2, 3, 4),
selected = character(0)),
radioButtons(inputId = ns("item2"),
label = "Item 2",
choices = c(1, 2, 3, 4),
selected = character(0)),
radioButtons(inputId = ns("item3"),
label = "Item 3",
choices = c(1, 2, 3, 4),
selected = character(0))
)
}
mod_mod1_server <- function(id){
moduleServer( id, function(input, output, session){
ns <- session$ns
# When the last survey question is completed
rv <- reactiveValues(completed=1)
observe({
lastQuestion <- input$item3
if(!is.null(lastQuestion) & !is.null(input$item2) & !is.null(input$item1)){
rv$completed <- 1
} else {
rv$completed <- 0
}
print(rv$completed )
#browser()
})
return(rv)
})
}
app_ui <- function(request) {
fluidPage(
useShinyjs(),
tagList(
mod_mod1_ui("mod1_ui_1"),
actionButton(inputId = "submit",
label = "Next Page")
)
)
}
app_server <- function( input, output, session ) {
state <- mod_mod1_server("mod1_ui_1")
# Don't show "Next Page" button until last item is completed
observe({
toggleState("submit", state$completed == TRUE)
})
}
shinyApp(ui = app_ui, server = app_server)

Related

R Shiny - Hiding an Output When a Value Changes

I Would like to make a form type Shiny app where the output is shown when a user clicks and action button, but the output is subsequently hidden if an input values changes.
Here is an example:
library(shiny)
library(shinyjs)
ui <- fluidPage(
radioButtons("myRadioButton", label = h4("Radio Input"),
choices = list("A" = 0,
"B" = 1,
"C" = 2),
selected = character(0)),
numericInput("myNumericInput", label = h4("Numeric Input"),
value = NA, min = 0, max = 50, step = 1),
actionButton("submit", "Submit"),
textOutput("myOutput")
)
server <- function(input, output, session){
score <- reactive({
scoreOut <- paste(input$myRadioButton, input$myNumericInput)
})
observeEvent(input$myRadioButton, {
hide("myOutput")
})
observeEvent(input$myNumericInput, {
hide("myOutput")
})
observeEvent(input$submit, {
show("myOutput")
output$myOutput <- renderText({
paste("This is your value:", score())
})
})
}
shinyApp(ui, server)
So in the above example the output displays after "Submit" is clicked. What I would like is if you go back and change say the radio or numeric input, the output disappears until "Submit" is clicked again.
You missed the useShinyjs() in the UI to load the JavaScript required to execute the hide function:
library(shiny)
library(shinyjs)
ui <- fluidPage(
# load required Java Script
useShinyjs(),
radioButtons("myRadioButton",
label = h4("Radio Input"),
choices = list(
"A" = 0,
"B" = 1,
"C" = 2
),
selected = character(0)
),
numericInput("myNumericInput",
label = h4("Numeric Input"),
value = NA, min = 0, max = 50, step = 1
),
actionButton("submit", "Submit"),
textOutput("myOutput")
)
server <- function(input, output, session) {
score <- reactive({
scoreOut <- paste(input$myRadioButton, input$myNumericInput)
})
observeEvent(input$myRadioButton, {
hide("myOutput")
})
observeEvent(input$myNumericInput, {
hide("myOutput")
})
observeEvent(input$submit, {
show("myOutput")
output$myOutput <- renderText({
paste("This is your value:", score())
})
})
}
shinyApp(ui, server)

How to update progress bar across over several modules and app in shiny?

Hi I´m very new to R programming.
Currently I´m working on a dashboard to create some data and display it.
This project got quite big quite quickly so I'm trying to modularize the dashboard.
That caused me the some problems. One being this Multiple tabItems in one shiny module.
Another being that I want / need to provide a progress bar for the user since the data processing takes up quite some time.
This processing of data is now divided in multiple modules like in the example below.
But the bar won't update itselfe further than the first module.
My guess is that the id's aren't matching and therefor the following updates aren't found.
I ain´t have any idea to "isolate" the id of updateProgressBar() and pass it across the modules.
Thanks so much for your help!
library(shiny)
library(shinydashboard)
library(shinydashboardPlus)
library(shinyWidgets)
#module_1
module_1_ui <- function(id){
ns <- NS(id)
tagList(
boxPlus(
title = "some title",
textOutput(ns("some_output"))
)
)
}
module_1_server <- function(id,see){
moduleServer(
id,
function(input, output, session){
ns <- session$ns
observe({
progressSweetAlert(
id = ns("progress"),
session = session,
value = 1,
total = 4,
)
Sys.sleep(1) #dummy for some functions that take some time to process
updateProgressBar(
id = ns("progress"),
session = session,
value = 2,
total = 4
)
})
output$some_output <- renderText({
see
})
}
)
}
#module_1
module_2_ui <- function(id){
ns <- NS(id)
tagList(
boxPlus(
title = "some title",
textOutput(ns("some_output"))
)
)
}
module_2_server <- function(id,see){
moduleServer(
id,
function(input, output, session){
ns <- session$ns
observe({
updateProgressBar(
session = session,
id = ns("progress"),
value = 3,
total = 4
)
Sys.sleep(4) #dummy for some functions that take some time to process
updateProgressBar(
session = session,
id = ns("progress"),
value = 4,
total = 4
)
Sys.sleep(2)
closeSweetAlert(session = session)
})
output$some_output <- renderText({
see
})
}
)
}
#app
ui <- dashboardPagePlus(
header = dashboardHeaderPlus(
title = "dummy app"
),
sidebar = dashboardSidebar(
sidebarMenu(
menuItem(
text = "home",
tabName = "home"
),
menuItem(
text = "module_1",
tabName = "tab_1"
),
menuItem(
text = "module_2",
tabName = "tab_2"
),
menuItem(
text = "some other tabItems",
tabName = "some_other_tabItems"
)
)
),
body = dashboardBody(
tabItems(
tabItem(
tabName = "home",
box(
title = "home of the app",
width = "auto"
)
),
tabItem(
tabName = "tab_1",
module_1_ui(
id = "module_1"
)
),
tabItem(
tabName = "tab_2",
module_2_ui(
id = "module_2"
)
),
tabItem(
tabName = "some_other_tabItems",
box(
title = "some other content"
)
)
)
)
)
server <- function(input, output){
module_1_server(
id = "module_1",
see = "something happens here"
)
module_2_server(
id = "module_2",
see = "something happens here as well"
)
}
shinyApp(ui,server)
I would push the progress update to the main app and let the modules simply notify the main app that it should update the progress bar. As it was not clear from your code how (in which sequence) the modules do their job and how the first module is strated, I made some assumptions:
The code ist started with a press on the Start button.
The first module does only one update. Once it is finished it notifies the second module to start.
The second module starts once the first module is finished and does 3 steps.
library(shiny)
library(shinyWidgets)
library(shinydashboard)
library(shinydashboardPlus)
m1_ui <- function(id) {
ns <- NS(id)
boxPlus(
title = "Module 1",
textOutput(ns("text_output"))
)
}
m1_server <- function(id, content, start) {
moduleServer(id,
function(input, output, session) {
trigger_update <- reactiveVal(0)
finished <- reactiveVal(FALSE)
observeEvent(start(), {
Sys.sleep(1)
trigger_update(trigger_update() + 1)
finished(rnorm(1))
}, ignoreInit = TRUE)
output$text_output <- renderText(content)
list(trigger_update = trigger_update,
finished = finished)
})
}
m2_ui <- function(id) {
ns <- NS(id)
boxPlus(
title = "Module 2",
textOutput(ns("text_output"))
)
}
m2_server <- function(id, content, start) {
moduleServer(id,
function(input, output, session) {
trigger_update <- reactiveVal(0)
trigger_next_step <- reactiveVal(0)
finished <- reactiveVal(FALSE)
observeEvent(start(), {
Sys.sleep(1)
trigger_update(trigger_update() + 1)
trigger_next_step(1)
}, ignoreInit = TRUE)
observeEvent(trigger_next_step(), {
Sys.sleep(1)
trigger_update(trigger_update() + 1)
if (trigger_next_step() <= 2) {
trigger_next_step(trigger_next_step() + 1)
} else {
finished(TRUE)
}
}, ignoreInit = TRUE
)
output$text_output <- renderText(content)
list(trigger_update = trigger_update,
finished = finished)
})
}
ui <- dashboardPagePlus(
dashboardHeaderPlus(
title = "dummy app"
),
dashboardSidebar(),
dashboardBody(fluidRow(actionButton("start", "Start")),
fluidRow(m1_ui("m1"), m2_ui("m2")))
)
server <- function(input, output, session) {
m1_handler <- m1_server("m1", "text 1", reactive(input$start))
m2_handler <- m2_server("m2", "text 2", m1_handler$finished)
current_status <- reactiveVal(0)
observeEvent({
m1_handler$trigger_update()
m2_handler$trigger_update()
}, {
current_status(current_status() + 1)
print(paste("Update", current_status()))
},
ignoreInit = TRUE
)
observeEvent(input$start, {
progressSweetAlert(
id = "progress",
session = session,
value = 0,
total = 4,
)
}
)
observe({
req(current_status() > 0)
if (current_status() < 5) {
updateProgressBar(session, "progress", value = current_status(), total = 4)
} else {
current_status(0)
closeSweetAlert(session)
}
})
}
shinyApp(ui, server)

R Shiny: Dynamically calling modules depending on user input

I am trying to create an app that will dynamically call modules depending on user input. In this example, I have a simple selectInput() which defaults to 1, with choices 1 and 2. What I want is for any time the user selects 1, the server calls a "first" module, which just has a textInput() box that displays "Your selection is (user input)", or in the case of 1, "Your selection is 1". Otherwise, if user selects 2, I want to call a different module, which is an add/remove button module, which in turn calls "first" module. Essentially, it selectInput() value of 2 will do the same as selectInput() value of 1, except that in addition to the UI output of "first" module, it will have an add and remove action button that if pressed will call more of the "first" module ui and server components. I have it working if 1 is selected, but for selectInput() of 2, it does not seem to call the addRmBtn module. Code below, thanks!
library(shiny)
firstUI <- function(id) { uiOutput(NS(id, "first")) }
firstServer <- function(input, output, session, inData) {
ns = session$ns
output$first <- renderUI({
textInput(ns("selection"), ns("selection"), value = paste0("Your selection is ", inData))
})
}
removeFirstUI <- function(id) {
removeUI(selector = paste0('#', NS(id, "first")))
}
addRmBtnUI <- function(id) {
ns <- NS(id)
tags$div(
actionButton(inputId = ns('insertParamBtn'), label = "Add"),
actionButton(ns('removeParamBtn'), label = "Remove"),
hr(),
tags$div(id = ns('placeholder'))
)
}
addRmBtnServer <- function(input, output, session, moduleToReplicate, ...) {
ns = session$ns
params <- reactiveValues(btn = 0)
observeEvent(input$insertParamBtn, {
params$btn <- params$btn + 1
callModule(moduleToReplicate$server, id = params$btn, ...)
insertUI(
selector = paste0('#', ns('placeholder')),
ui = moduleToReplicate$ui(ns(params$btn))
)
})
observeEvent(input$removeParamBtn, {
moduleToReplicate$remover(ns(params$btn))
params$btn <- params$btn - 1
})
}
ui <- fluidPage(
#addRmBtnUI("addRm"),
column(12, selectInput("inp", label = "Select", choices = list(1, 2), selected = 1)),
column(12, uiOutput("inpChoice"))
)
server <- function(input, output, session) {
observeEvent(input$inp, {
if (input$inp == 1) {
callModule(firstServer, id = 0, inData = input$inp)
output$inpChoice <- renderUI({ firstUI(0) })
} else {
callModule(addRmBtnServer,
id = "inpChoice",
moduleToReplicate = list(
ui = firstUI,
server = firstServer,
remover = removefirstIU
),
inData = input$inp
)
}
})
}
shinyApp(ui = ui, server = server)
Try using a conditionalPanel() in the UI to adapt the conditions to the user input.
Maybe something like
conditionalPanel(condition = "input.inp=='2'",[do second module stuff here])

Getting an input list containing inputs present in the current session

I want to retrieve the list of inputs in the current shiny session.
We can retrieve the list of inputs using names(input).
I have a uiOutput and based on different conditions I am rendering different types inputs. The current problem I am facing is that when the condition changes the inputs from previous renderUI is also present in the list. Is there a way to get only the inputs in the current session?
To explain my query better I have the following sample code:
library(shiny)
ui <- fluidPage(
sliderInput(inputId = "slider",label = "", min = 1, max = 3, value = 1),
uiOutput("UI"),
actionButton(inputId = "btn", label = "Show Inputs"),
verbatimTextOutput(outputId = "textOp")
)
server <- function(input, output){
observeEvent(input$slider,{
if(input$slider == 1){
output$UI <- renderUI(
textInput("txt1",label = "Slider in position 1")
)
}else if(input$slider == 2){
output$UI <- renderUI(
textInput("txt2",label = "Slider in position 2")
)
}else{
output$UI <- renderUI(
textInput("txt3",label = "Slider in position 3")
)
}
})
observeEvent(input$btn,{
output$textOp <- renderText(
paste0(names(input), ",")
)
})
}
shinyApp(ui = ui, server = server)
In the above code when I first click on action button labelled "Show Input" I get the following text as the output:
btn, slider, txt1,
Now when I move the slider to 2 my output is as follows:
btn, slider, txt1, txt2,
Here txt1 was generated when the slider was at position 1, and this renderUI was overridden by output$UI <- renderUI(textInput("txt2",label = "Slider in position 2")). I want an input list where txt1 is not there. Is there a way to do that?
I came up with kind of a workaround, assuming you dont have any inputs that should take a value of NULL. You could set the values of the inputs, that you wish to remove, to NULL and filter for non - NULLs when you display the names.
library(shiny)
ui <- fluidPage(
tags$script("
Shiny.addCustomMessageHandler('resetValue', function(variableName) {
Shiny.onInputChange(variableName, null);
});
"
),
sliderInput(inputId = "slider",label = "", min = 1, max = 3, value = 1),
uiOutput("UI"),
actionButton(inputId = "btn", label = "Show Inputs"),
verbatimTextOutput(outputId = "textOp")
)
server <- function(input, output, session){
observeEvent(input$slider,{
for(nr in 1:3){
if(nr != input$slider) session$sendCustomMessage(type = "resetValue", message = paste0("txt", nr))
}
})
output$UI <- renderUI(
textInput(paste0("txt", input$slider), label = paste0("Slider in position ", input$slider))
)
global <- reactiveValues()
observe({
inp = c()
for(name in names(input)){
if(!is.null(input[[name]])){
inp <- c(inp, name)
}
}
isolate(global$inputs <- inp)
})
output$textOp <- renderText({
global$inputs
})
}
shinyApp(ui = ui, server = server)

How to overwrite output using 2nd action button

I have a shiny app which writes a dataframe to output when an action button is pressed. This is the "Go" button in the bare-bones example below. I have a reset button which resets the values of the inputs. I'm wondering how I might also reset the output (so it becomes NULL & disappears when "reset" is pressed).
I've tried to pass input$goButtonReset to the eventReactive function (with the intention of using an if statement inside to indicate which button was making the call) but this didn't seem to be possible.
Any help much appreciated!
ui <- fluidPage(title = "Working Title",
sidebarLayout(
sidebarPanel(width = 6,
# *Input() functions
selectInput("Input1", label = h3("Select Input1"),
choices = list("A" = "A", NULL = "NULL"),
selected = 1),
actionButton("goButton", "Go!"),
p("Click the button to display the table"),
actionButton("goButtonReset", "Reset"),
p("Click the button to reset your inputs.")
),
mainPanel(
# *Output() functions
tableOutput("pf"))
)
)
# build the outputs here
server <- function(input, output, session) {
observeEvent(input$goButtonReset, {
updateSelectInput(session, "Input1", selected = "NULL")
})
writePF <- eventReactive(input$goButton, {
data.frame("test output")
})
output$pf <- renderTable({
writePF()
})
}
shinyApp(ui = ui, server = server)
You could try using reactiveValues to store the data frame. This worked for me:
ui <- fluidPage(title = "Working Title",
sidebarLayout(
sidebarPanel(width = 6,
# *Input() functions
selectInput("Input1", label = h3("Select Input1"),
choices = list("A" = "A", NULL = "NULL"),
selected = 1),
actionButton("goButton", "Go!"),
p("Click the button to display the table"),
actionButton("goButtonReset", "Reset"),
p("Click the button to reset your inputs.")
),
mainPanel(
# *Output() functions
tableOutput("pf"))
)
)
# build the outputs here
server <- function(input, output, session) {
df <- reactiveValues()
observeEvent(input$goButton,{
df$writePF <- data.frame("test output")
})
observeEvent(input$goButtonReset,{
df$writePF <- NULL
})
output$pf <- renderTable({
df$writePF
})
}
shinyApp(ui = ui, server = server)

Resources