R Shiny - Использование селекторов JQuery в smoothjs :: toggleState для отключения динамически создаваемых кнопок - PullRequest
1 голос
/ 07 июня 2019

Приложение ниже содержит модуль, который вставляет объект пользовательского интерфейса при каждом нажатии кнопки Add.Объект состоит из кнопки selectInput и Remove, которая удаляет объект пользовательского интерфейса:

enter image description here

Я хотел бы отключить * 1011Кнопка *, если в DOM остался только один selectInput.

Для этого я отслеживаю 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 неизвестен, поэтому я используюselector аргумент toggleState вместо id аргумента.Я пробовал варианты следующих 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)

В качестве проверки, предоставление полного идентификатора работает как в модульной, так и в немодулярной версиях, например, toggleState(id = 'remove2', condition = diff > 1).

1 Ответ

1 голос
/ 07 июня 2019

Установить класс для кнопок:

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