【问题标题】:R Shiny - Using JQuery selectors in shinyjs::toggleState to disable dynamically created buttonsR Shiny - 在 shinyjs::toggleState 中使用 JQuery 选择器来禁用动态创建的按钮
【发布时间】:2019-10-22 12:55:44
【问题描述】:

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

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

为此,我会跟踪 1) 使用计数器 rv$ct 插入了多少输入,以及 2) 在 rv$rmvd 中删除了多少。我设置了一个观察者,它监听rv$ct 和length(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 是未知的,因此我使用 toggleState 的 selector 参数而不是 @ 987654337@ 参数。我尝试了以下selectors 的变体,但似乎都没有工作:

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 &gt; 1).

【问题讨论】:

    标签: jquery r shiny shinyjs


    【解决方案1】:

    为按钮设置一个类:

    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)"))
    }
    

    【讨论】:

    • 谢谢@Stéphane,您的解决方案完美运行。我错过了actionButton 文档中的...,所以没有意识到我可以直接向它传递一个类。
    • 当您在模块中使用selector 参数时,id 参数会自动添加到底层对象。 id 是使用 NS 函数生成的模块的命名空间。根据文档,如果提供了 id,则忽略选择器。这就是为什么它不起作用。对我来说,这是一个问题/错误。当您对 id 进行操作时,该机制运行良好,那么您不需要在 id 前面加上命名空间。但是,如果您想使用选择器,它将无法正常工作:(
    猜你喜欢
    • 2019-08-18
    • 2022-01-11
    • 2021-01-27
    • 1970-01-01
    • 2021-08-28
    • 1970-01-01
    • 1970-01-01
    • 2014-10-27
    • 1970-01-01
    相关资源
    最近更新 更多