R Shiny - 在 shinyjs::toggleState 中使用 JQuery 选择器禁用动态创建的按钮

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

下面的应用程序包含一个模块,每次单击 Add 按钮时都会插入一个 UI 对象。该对象由一个 selectInput 和一个 Remove 按钮组成,它删除了 UI 对象:

如果 DOM 中只剩下一个 selectInput,我想禁用 Remove 按钮。

为此,我跟踪 1) 使用计数器 rv$ct 插入了多少输入,以及 2) rv$rmvd 中删除了多少输入。我设置了一个观察器来监听 rv$ctlength(rv$rmvd) 之间的差异值,并在该观察器内部使用 shinyjs::toggleState 以在差异大于时启用 Remove 按钮1.

应用程序如下:

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)

由于用户可以插入任意数量的输入并随机单独删除它们,因此 DOM 中最后一个剩余删除按钮的完整 ID 未知,因此我使用 selector 参数toggleState 而不是 id 参数。我尝试了以下 selectors、none 的变体,其中 none 似乎有效:

button[id *= "remove"]

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

div.rmvBttn

让我感到困惑的是,它们似乎在应用程序的非模块化版本中工作得很好(见下文):

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)

作为检查,提供完整的 ID 在模块化和非模块化版本中都有效,例如toggleState(id = 'remove2', condition = diff > 1).

为按钮设置 class:

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

(不是包含按钮的 div)。

很奇怪

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

不起作用。相反,你可以这样做:

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