1

The following shiny app uses modules, it works:

library(shiny)


LHSchoices <- c("X1", "X2", "X3", "X4")

LHSchoices2 <- c("S1", "S2", "S3", "S4")

#------------------------------------------------------------------------------#

# MODULE UI ----
variablesUI <- function(id, number) {

  ns <- NS(id)

  tagList(
    fluidRow(
      column(6,
             selectInput(ns("variable"),
                         paste0("Select Variable ", number),
                         choices = c("Choose" = "", LHSchoices)
             )
      ),

      column(6,
             numericInput(ns("value.variable"),
                          label = paste0("Value ", number),
                          value = 0, min = 0
             )
      )
    )
  )

}

#------------------------------------------------------------------------------#

# MODULE SERVER ----

variables <- function(input, output, session, variable.number){
  reactive({

    req(input$variable, input$value.variable)

    # Create Pair: variable and its value
    df <- data.frame(
      "variable.number" = variable.number,
      "variable" = input$variable,
      "value" = input$value.variable,
      stringsAsFactors = FALSE
    )

    return(df)

  })
}

#------------------------------------------------------------------------------#

# Shiny UI ----

ui <- fixedPage(
  tabsetPanel(type = "tabs",id="tabs",
              tabPanel("t1",value="t1"),
              tabPanel("t2",value="t2")),

  variablesUI("var1", 1),
  h5(""),
  actionButton("insertBtn", "Add another line"),

  verbatimTextOutput("test1"),
  tableOutput("test2")
)

# Shiny Server ----

server <- function(input, output) {

  add.variable <- reactiveValues()

  add.variable$df <- data.frame("variable.number" = numeric(0),
                                "variable" = character(0),
                                "value" = numeric(0),
                                stringsAsFactors = FALSE)

  var1 <- callModule(variables, paste0("var", 1), 1)

  observe(add.variable$df[1, ] <- var1())

  observeEvent(input$insertBtn, {

    btn <- sum(input$insertBtn, 1)

    insertUI(
      selector = "h5",
      where = "beforeEnd",
      ui = tagList(
        variablesUI(paste0("var", btn), btn)
      )
    )

    newline <- callModule(variables, paste0("var", btn), btn)

    observeEvent(newline(), {
      add.variable$df[btn, ] <- newline()
    })

  })

  output$test1 <- renderPrint({
    print(add.variable$df)
  })

  output$test2 <- renderTable({
    add.variable$df
  })

}

#------------------------------------------------------------------------------#

shinyApp(ui, server)

Now, I would like to update the selectInput, with dynamic choices. For this, I found this answer, and it is possible to use the function updateSelectInput.

But in this app, the selectInput is in a module. The following doesn't work

  observe({
    updateSelectInput(session, "variable",
                      choices = choices_var()
    )})

choices_var() is some reactive values (it can depend on the selected tab for example).

Here is the full code.

library(shiny)


LHSchoices <- c("X1", "X2", "X3", "X4")

LHSchoices2 <- c("S1", "S2", "S3", "S4")

#------------------------------------------------------------------------------#

# MODULE UI ----
variablesUI <- function(id, number) {

  ns <- NS(id)

  tagList(
    fluidRow(
      column(6,
             selectInput(ns("variable"),
                         paste0("Select Variable ", number),
                         choices = c("Choose" = "", LHSchoices)
             )
      ),

      column(6,
             numericInput(ns("value.variable"),
                          label = paste0("Value ", number),
                          value = 0, min = 0
             )
      )
    )
  )

}

#------------------------------------------------------------------------------#

# MODULE SERVER ----

variables <- function(input, output, session, variable.number){
  reactive({

    req(input$variable, input$value.variable)

    # Create Pair: variable and its value
    df <- data.frame(
      "variable.number" = variable.number,
      "variable" = input$variable,
      "value" = input$value.variable,
      stringsAsFactors = FALSE
    )

    return(df)

  })
}

#------------------------------------------------------------------------------#

# Shiny UI ----

ui <- fixedPage(
  tabsetPanel(type = "tabs",id="tabs",
              tabPanel("tab1",value="t1"),
              tabPanel("tab2",value="t2")),

  variablesUI("var1", 1),
  h5(""),
  actionButton("insertBtn", "Add another line"),

  verbatimTextOutput("test1"),
  tableOutput("test2")
)

# Shiny Server ----

server <- function(input, output,session) {


  choices_var <- reactive({
    if (input$tabs=="t1"){

      choices_var <- LHSchoices
    }
    if (input$tabs=="t2") {
      choices_var <- LHSchoices2
    }
    return(choices_var)
  })

  observe({
    updateSelectInput(session, "variable",
                      choices = choices_var()
    )})


  add.variable <- reactiveValues()

  add.variable$df <- data.frame("variable.number" = numeric(0),
                                "variable" = character(0),
                                "value" = numeric(0),
                                stringsAsFactors = FALSE)

  var1 <- callModule(variables, paste0("var", 1), 1)

  observe(add.variable$df[1, ] <- var1())

  observeEvent(input$insertBtn, {

    btn <- sum(input$insertBtn, 1)

    insertUI(
      selector = "h5",
      where = "beforeEnd",
      ui = tagList(
        variablesUI(paste0("var", btn), btn)
      )
    )

    newline <- callModule(variables, paste0("var", btn), btn)

    observeEvent(newline(), {
      add.variable$df[btn, ] <- newline()
    })

  })

  output$test1 <- renderPrint({
    print(add.variable$df)
  })

  output$test2 <- renderTable({
    add.variable$df
  })

}

#------------------------------------------------------------------------------#

shinyApp(ui, server)

I would like to how to modify the code so that the choices can be modified.

EDIT: I succeded to update the first UI by adding the code below. So now my question is: how can we dynamically reach the variablesUI?

  choices_var <<- reactive({
    if (input$tabs=="t1"){

      choices_var <- LHSchoices
    }
    if (input$tabs=="t2") {
      choices_var <- LHSchoices2
    }
    return(choices_var)
  })

  observeEvent({
    choices_var()
  }, {
    updateSelectInput(session, "var1-variable",
                      choices = choices_var())
  })

EDIT 2: I can do it manually as below, but that would be really ugly, and the number of added UI should be limited.

  observeEvent({
    choices_var()
  }, {
    updateSelectInput(session, "var1-variable",
                      choices = choices_var())
  })

  observeEvent({
    choices_var()
  }, {
    updateSelectInput(session, "var2-variable",
                      choices = choices_var())
  })

EDIT 3

Now my question becomes more specific: when inserting a selectInput using insertUI, how to update the choices of newly inserted selectInput with updateSelectInput ?

John Smith
  • 1,604
  • 4
  • 18
  • 45
  • 1
    It sounds like this might help: https://community.rstudio.com/t/accessing-inputs-in-a-updateselectinput-inside-a-shiny-module/12286. – Tonio Liebrand Jun 06 '20 at 09:16
  • Thank you Tonio, it seems that the problem in your link is slightly different. In my example, the `selectInput` is in a module, whereas in your link, the choices are in a module, but the `selectInput` is not. – John Smith Jun 06 '20 at 11:23

1 Answers1

2

Your variable input is in a module. You're trying to update it from the main server function. So you have a namespace mismatch. It also violates the principle that modules should be self-contained.

Ideally, you should update the variable input in the module which defines it. If the update depends on values which exist outside the module, you can pass them as reactives to the module server function.

*** Edit *** Here is a simple, self-conatined example in response to OP's request for demonstration of how to update a selectInput that lives inside a module with data provided by the main server function. I've removed everything that isn't directly relevant to the purpose of the demonstration.

The app includes two instances of the module (defined by moduleUI and moduleController). Each instance has its own id, so the server can distinguish between them. The main UI also includes pair of selectInputs, each of which tells one of the module instances what to display.

The key to making this work is passing the value of the controlling seelctinput to the controller of the appropriate instance of the module, for example:

mod1 <- callModule(moduleController, "Module1", reactive({input$module1Mode}))

The module controller function looks like this

moduleController <- function(input, output, session, selector) { ... }

Note the additional argument named selector, which corresponds to the current value of the controlling selectInput. The module reacts to changes to its controller with

  observeEvent(selector(), {
    updateSelectInput(session, "select", choices=choiceLists[[selector()]])
  })

And returns a value to the main server with

  returnValue <- reactive({
    input$select
  })
  
  return(returnValue)

If you play with the app, you'll see that the selection list displayed by each instance of the module can be controlled independently and the server can distinguish between (and react to) the values returned by each instance of the module.

Here's the full code:

library(shiny)

moduleUI <- function(id) {
  ns <- NS(id)
  
  wellPanel(
    h4(paste0("This is module id"), id),
    selectInput(ns("select"), label="Make a choice: ", choices=c())
  )
}

moduleController <- function(input, output, session, selector) {
  ns <- session$ns
  
  choiceLists <- list(
    "Animals"=c("Cat", "Dog", "Rabbit", "Fish"),
    "Fruit"=c("Apple", "Orange", "Pear", "Rambutan"),
    "Sports"=c("Football", "Cricket", "Rugby", "Volleyball"),
    "Countries"=c("Great Britain", "China", "USA", "France")
  )
  
  observeEvent(selector(), {
    updateSelectInput(session, "select", choices=choiceLists[[selector()]])
  })
  
  returnValue <- reactive({
    input$select
  })
  
  return(returnValue)
}

ui <- fixedPage(
  selectInput("module1Mode", label="Select module 1 mode", choices=c("Animals", "Fruit")),
  moduleUI("Module1"),
  selectInput("module2Mode", label="Select module 2 mode", choices=c("Sports", "Countries")),
  moduleUI("Module2"),
  textOutput("mod1Text"),
  textOutput("mod2Text")
)

server <- function(input, output, session) {
  mod1 <- callModule(moduleController, "Module1", reactive({input$module1Mode}))
  mod2 <- callModule(moduleController, "Module2", reactive({input$module2Mode}))
  
  observe({
    if (is(mod1(), "character")) print("Ah-ha!")
  })
  
  output$mod1Text <- renderText({
    paste("Module 1 selection is", mod1())
  })

  output$mod2Text <- renderText({
    paste("Module 2 selection is", mod2())
  })
}

shinyApp(ui, server)
Limey
  • 10,234
  • 2
  • 12
  • 32
  • Thank you @Limey for your comment. Would mind explain with a working code? Thank you. I am working on the link that Tonio commented above. – John Smith Jun 06 '20 at 10:59