Приложение ниже содержит модуль, который вставляет объект пользовательского интерфейса при каждом нажатии кнопки Add
.Объект состоит из кнопки selectInput
и Remove
, которая удаляет объект пользовательского интерфейса:
Я хотел бы отключить * 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)
.