简体   繁体   中英

R Shiny - Using JQuery selectors in shinyjs::toggleState to disable dynamically created buttons

The app below contains a module that inserts a UI object each time the Add button is clicked. The object consists of a selectInput and a Remove button, which removes the UI object:

在此处输入图片说明

I would like to disable the Remove button if there is only one selectInput left in the DOM.

To do this, I keep track of 1) how many inputs were inserted using a counter rv$ct and 2) how many were removed inside rv$rmvd . I set up an observer that listens to the value of the difference between rv$ct and length(rv$rmvd) and use shinyjs::toggleState inside that observer to enable the Remove button if the difference is greater than 1.

The app is as follows:

library(shiny)
library(shinyjs)

# module UI ---------------------------------------------------------------

modUI <- function(id) {

  ns = NS(id)

  tagList(
    actionButton(ns('add'), 'Add'),
    fluidRow(div(id = ns('placeholder')))
  )

}

# module server -----------------------------------------------------------

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

  ns = session$ns

  rv = reactiveValues(ct = 0, rmvd = NULL)

  observeEvent(input$add, {

    rv$ct = rv$ct + 1

    Id = function(id) paste0(id, rv$ct)

    insertUI(
      selector = paste0('#', ns('placeholder')),
      ui = div(
        id = Id(ns('inputGroup')),
        splitLayout(
          cellWidths = '10%',
          h4(Id('State ')),
          selectInput(Id(ns('state')), 'State:', state.abb),
          div(
            class = 'rmvBttn',
            actionButton(Id(ns('remove')), 'Remove'))
        )

      )
    )

    remove_id = Id('remove')
    remove_group = Id(ns('inputGroup'))

    observeEvent(input[[remove_id]], {

      removeUI(selector = paste0('#', remove_group))
      rv$rmvd = c(rv$rmvd, str_extract(remove_id, '\\d+$'))

    })
  })

  observe({

    diff = rv$ct - length(rv$rmvd)

    delay(1000, toggleState(selector = 'div.rmvBttn', condition = diff > 1)) #not working 

    # Other selectors I have tried that don't work:
    # delay(1000, toggleState(selector = paste0('#', ns('placeholder'), 'button'), condition = diff > 1))
    # delay(1000, toggleState(selector = 'button[id *= "remove"]', condition = diff > 1))

    # Using the id works:
    # delay(1000, toggleState(id = 'remove1', condition = diff > 1)) #this works

  })
}

# main UI -----------------------------------------------------------------

ui <- fluidPage(
  useShinyjs(),

  tags$head(tags$style(HTML('.shiny-split-layout > div { overflow: visible; }'))),

  modUI('mod')

)

# main server -------------------------------------------------------------

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

  callModule(modServer, 'mod')

}

# Run app
shinyApp(ui, server)

Since the user can insert any number of inputs and remove them individually at random, the full ID of the last remaining remove button in the DOM is unknown so I am using the selector argument of toggleState instead of the id argument. I have tried variations of the following selectors , none of which appear to be working:

button[id *= "remove"]

paste0('#', ns('placeholder'), 'button')

div.rmvBttn

What's confusing me is that they seem to work just fine in the non-modularised verion of the app (see below):

 library(shiny) library(shinyjs) # UI ----------------------------------------------------------------- ui <- fluidPage( useShinyjs(), tags$head(tags$style(HTML('.shiny-split-layout > div { overflow: visible; }'))), tagList( actionButton('add', 'Add'), fluidRow(div(id = 'placeholder')) ) ) # server ------------------------------------------------------------- server <- function(input, output, session) { rv = reactiveValues(ct = 0, rmvd = NULL) observeEvent(input$add, { rv$ct = rv$ct + 1 Id = function(id) paste0(id, rv$ct) insertUI( selector = paste0('#placeholder'), ui = div( id = Id('inputGroup'), splitLayout( cellWidths = '10%', h4(Id('State ')), selectInput(Id('state'), 'State:', state.abb), div( class = 'rmvBttn', actionButton(Id('remove'), 'Remove')) ) ) ) remove_id = Id('remove') remove_group = Id('inputGroup') observeEvent(input[[remove_id]], { removeUI(selector = paste0('#', remove_group)) rv$rmvd = c(rv$rmvd, str_extract(remove_id, '\\\\d+$')) }) }) observe({ diff = rv$ct - length(rv$rmvd) # delay(1000, toggleState(selector = 'div.rmvBttn', condition = diff > 1)) # delay(1000, toggleState(selector = '#placeholder button', condition = diff > 1)) delay(1000, toggleState(selector = 'button[id *= "remove"]', condition = diff > 1)) }) } # Run app shinyApp(ui, server) 

As a check, providing the full ID works in both the modularised and non-modularised versions, eg toggleState(id = 'remove2', condition = diff > 1) .

Set a class to the buttons:

actionButton(Id(ns('remove')), 'Remove', class = 'rmvBttn')

(not to the div containing the button).

It's very strange that

toggleState(selector = '.rmvBttn', condition = diff > 1)

does not work. Instead of that, you can do:

if(diff <= 1){
  delay(1000, runjs("$('.rmvBttn').attr('disabled', true)"))
}else{
  delay(1000, runjs("$('.rmvBttn').attr('disabled', false)"))
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM