0

The app below contains an actionButton Add data that inserts a UI element each time it is clicked. Each UI element is a box that contains one selectInput Select data and an actionButton Edit that opens a modal when clicked.

Each modal contains:

  1. A data table with two columns: Parameter and Value (this column is editable).
  2. An actionButton Apply, which applies any changes made to the Value column.

When the user selects a dataset inside box x, a reactiveValue is created to store the corresponding parameters in a data.frame x_paramset (where x is the id of the box inserted via insertUI) and add a val column which has the same value as default (see list at the top of code below). I then use renderDataTable to add the Value column (which contains the numericInput) - this data table is displayed inside the modal.

To update the data.frame to apply any changes the user may have made in the modal, I use an observeEvent that listens for the Apply button and updates the val column in the data.frame x_paramset with the values inside the numericInputs in the Value column.

Here is the app (the bsModal has been commented out and replaced with a shinyWidgets::dropdownButton):

library(shiny)
library(shinydashboard)
library(shinyjs)
library(shinyWidgets)
library(DT)
library(tidyverse)

all = list(p1 = list(a = list(id = "a", default = 10)), 
           p2 = list(x = list(id = "x", default = 20)))

# UI ----------------------------------------------------------------------

ui<-fluidPage(shinyjs::useShinyjs(), 

              tags$head(
                tags$script("
                            $(document).on('click', '.dropdown-shinyWidgets li button', function () {
                            $(this).blur()
                            Shiny.onInputChange('lastClickId',this.id)
                            Shiny.onInputChange('lastClick',Math.random())
                            });                       

                            ")
                ),

              box(title = "Add data", 
                  column(width = 12,
                         fluidRow(
                           tags$div(id = 'add')
                         ),
                         fluidRow(
                           actionButton("addbox", "Add data")
                         ))
                  )
              )

# SERVER ------------------------------------------------------------------

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

  rvals = reactiveValues()

  getInputs <- function(pattern){
    reactives <- names(reactiveValuesToList(input))
    name = reactives[grep(pattern,reactives)]
    }

  observeEvent(input$addbox, {
    lr = paste0('box', input$addbox)
    insertUI(
      selector = '#add',
      ui = tags$div(id = lr,
                    box(title = lr,

                        selectizeInput(lr, "Choose data:", choices = names(all)), 

                        shinyWidgets::dropdownButton(inputId = paste0(lr, "_settings"), 
                                       circle = F, status = "success", icon = icon("gear"), width = "1000px",
                                       tooltip = tooltipOptions(title = "Click to edit"),

                                       tags$h4(paste0("Edit settings for Learner", lr)),
                                       hr(),
                                       DT::dataTableOutput( paste0(lr, "_paramdt") ), 
                                       bsButton(paste0(lr, "_apply"), "Apply")  
                        ) # end dropdownButton

                        )

      ) #end tags$div
    ) # end inserUI


    # create reactive dataset
    rvals[[ paste0(lr, "_paramset") ]] <- reactive({

      do.call(rbind, all[[ input[[lr]] ]]) %>% 
        cbind(., lr) %>%
        as.data.frame %>%
        mutate(val = default) 

    }) # end reactive


    # render DT in modal
    output[[ paste0(lr, "_paramdt") ]] <- renderDataTable({ 

      DT <- rvals[[ paste0(lr, "_paramset") ]]() %>% 
        mutate( 
          Parameter = id, 
          Value = as.character(numericInput(paste0(lr,"value",id), label = NULL, value = default))) %>%  
        select(Parameter:Value)

      datatable(DT, 
                selection = 'none', 
                #server = F,
                escape = F,
                options = list(preDrawCallback = JS('function() { Shiny.unbindAll(this.api().table().node()); }'),
                               drawCallback = JS('function() { Shiny.bindAll(this.api().table().node()); } '))) 

    }) # end renderDT


    # Apply changes
    observeEvent(input$lastClick, {

      # replace old values with new 
      rvals[[ paste0(lr, "_paramset") ]](rvals[[ paste0(lr, "_paramset") ]]() %>%
                                           mutate(
                                             val = input$box1valuea
                                             )
                                           )
    }) # end apply changes observeEvent

  }) #end observeEvent
}

shinyApp(ui=ui, server=server)

I encounter errors when I try the following:

  1. Add data >> Edit >> make some change to numericInput >> Apply - this resets the numericInput inside the modal back to its default whereas I would like the user-specified value to persist upon applying changes or closing the modal.
  2. The app crashes when I try: Add data >> Edit >> Apply >> close modal >> Add data OR Click Add data twice and then click Edit in either box.

I am not sure where my server logic is failing. I know Shiny does not support "persistent use" modals (https://github.com/rstudio/shiny/issues/1590) but I was wondering if there was a workaround? I am also not sure what inside the insertUI observeEvent is causing the app to crash in the cases described above. Any help you can offer would be greatly appreciated!

user51462
  • 1,658
  • 2
  • 13
  • 41
  • It's not easy to get the logic of your app. Couldn't you provide a shorter example that reproduces the issue ? If there is a problem of persistence in a modal, you could use a `dropdown` instead, with the `shinyWidgets` package. – Stéphane Laurent Mar 04 '19 at 13:44
  • Thank you for replying @Stéphane, I have replaced the modal with a dropdown button as suggested. However, the jQuery to capture the `Apply` button click no longer works, could you please help me with this? I have tried replacing the selector `$(document).on('click', '.modal button', function () {` with `$(document).on('click', 'button[aria-expanded=\"true\"] button', function () {...});` to no avail. – user51462 Mar 04 '19 at 23:40
  • Sorry, trying `$(document).on('click', '.dropdown-shinyWidgets li button', function () {` worked. The inputs are still resetting to default if I click `Apply` though which suggests the server logic is wrong. I have abridged the original post, would it be possible to have another look at it? The gist of the app is four observeEvents: the first inserts some ui, the second creates a reactiveValue, the third renders an editable data table and the fourth updates the reactiveValue with any changes made in the data table. – user51462 Mar 05 '19 at 00:05
  • Your code does not work for me. What is the `box` function ? – Stéphane Laurent Mar 05 '19 at 00:08
  • Ok, it is from `shinydashboard`. I still don't understand your code. But when you click on Apply, `rvals[[paste0(lr, "_paramset")]]` is updated, and the datatable is reactive to this, therefore it is re-rendered. – Stéphane Laurent Mar 05 '19 at 00:29
  • I have re-edited the code. Instead of using an observeEvent, I stored the data frame in a reactive expression `rvals[[ paste0(lr, "_paramset") ]]`. The last observeEvent is trying to replace the old values in this data frame by the user-specified ones in the data table. Am I on the right track here? Sorry for the confusing code, I'm terrible at thinking reactivity through :( – user51462 Mar 05 '19 at 03:02
  • I'm under the impression you try to replace the datatable by itself. The "old value" has already gone when the user changes the numeric input, no ? I'm probably missing something. Could you detail ? – Stéphane Laurent Mar 05 '19 at 06:42

0 Answers0