0

The app below contains a selectInput (input$month) and a button, Show Modal, that launches a modal window when clicked. The modal contains a button, Add UI, that inserts some text, Element x, where x is the value of a counter that increases by 1 each time Add UI is clicked.

Screenshot of app on startup with the modal window opened:

enter image description here

The app is modularised so that UI associated with the modal is rendered by function modUI and the corresponding server logic is defined in function modServer. modUI is rerendered each time input$month changes to overwrite previously inserted text elements.

I require the first block of text to be inserted programmatically, so that when the app loads OR the user changes input$month, Element 1 is already rendered in the modal:

enter image description here

To do this, I tried to get the insertUI observer to fire if input$add_ui greater than or equal to 0 - i.e. observeEvent(if(req(input$add_ui) >= 0) TRUE else return(), { #insertUI expr }). However, this doesn't work and I don't understand why. Since observers are eagerly evaluated, shouldn't this observer fire after input$add_ui has finished initialising?

The counter must also be reset to 0 each time input$month changes so that inserted text elements start at Element 1. To do this, I included the following observer in modServer:

observe({
    req(input$add_ui == 0) #checking that modUI has finished rerendering
    print(paste('month changed to:', month, 'resetting counter')); 
    counter(0)
  })

The req(input$add_ui == 0) checks if modUI has finished rerendering and the counter is only reset if the check is passed. But I feel like this makes the modServer less self-contained and was wondering if there was a less roundabout way?

Lastly, I am relatively new to modules and was wondering if someone could explain why counter() is not reset automatically whenever modServer is called via observe(callModule(modServer, 'hi', month = input$month))? Why does its value persist if the module has its own environment?

I would be grateful for any help as I have been stuck on this for a while.

Code to reproduce the above:

library(shiny)
library(shinyBS)

#MODULE UI ----
modUI <- function(id) {

  ns <- NS(id)

  tagList(
    actionButton(ns("show_modal"), "Show modal"),
    bsModal(
      id = ns('modal'),
      trigger = ns('show_modal'),

      actionButton(ns("add_ui"), "Add UI"),
      tags$div(id = ns("placeholder1"))
    )
  )
}

#MODULE SERVER ----
modServer <- function(input, output, session, month) {

  ns <- session$ns

  counter <- reactiveVal(0)

  # Observer to insert UI element
  observeEvent(if(req(input$add_ui) >= 0) TRUE else return(), {

    counter(counter() + 1)

    insertUI(
      selector = paste0("#", ns("placeholder1")),
      ui = tags$div(paste('Element', counter()))
    )
  })

  # Reset counter() if month is changed
  observe({
    req(input$add_ui == 0)
    print(paste('month changed to:', month, 'resetting counter')); 
    counter(0)
  })

  # Print
  observe({ print(paste('input$add_ui:', input$add_ui, 'counter:', counter())) })

}

#MAIN UI ----
ui <- fluidPage(
  tagList(
    selectInput('month', 'Month', month.abb),
    uiOutput('modal_ui')
  )
)

#MAIN SERVER ----
server <- function(input, output, session) {

  #Call modUI if input$month is changed
  observe(callModule(modServer, 'hi', month = input$month))

  #Rerender modUI if input$month is changed
  output$modal_ui <- renderUI({
    input$month
    modUI('hi')
  })

  observe(print(input$month))
}

shinyApp(ui = ui, server = server)
user51462
  • 1,658
  • 2
  • 13
  • 41
  • Try to debug step by step and watch out the events. It seems when you are in new month, the event of previous month is still fired. At some lines I think you have to use `isolate()`. If you could, you have to prefer `ObserveEvent()`. Try to restart to write your source all without module and when I works restart to write with module. Your source is confusing me even I tried it. – phili_b Jul 31 '19 at 09:41
  • I tried `counter<-reactiveValues()` and `counter[[month]]` but it has still the issues that it seems when you are in new month, the event of previous month is still fired. – phili_b Jul 31 '19 at 09:44
  • Please read this article [20/7/18, Using global input values inside of R Shiny modules, by T Bradley](https://tbradley1013.github.io/2018/07/20/r-shiny-modules--using-global-inputs/) in particular the conclusion where it's written to use `reactive(input$myglobalinput)`, in your case `reactive(input$month)` for input which are external at a module when you call a module, and inside the module to call the reactive parameter by `month()`. But try to read all the article :) – phili_b Aug 04 '19 at 08:55

0 Answers0