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.