shiny
shiny copied to clipboard
How to use `Shiny.bindAll` for a DT table?
In order to use a Shiny widget in a DT table with shiny < 1.8
, we used the following options:
options = list(
preDrawCallback = JS('function() { Shiny.unbindAll(this.api().table().node()); }'),
drawCallback = JS('function() { Shiny.bindAll(this.api().table().node()); }')
))
That doesn't work anymore. This is probably due to the breaking change in shiny 1.8.0
. I tried to play with async/await
without success. What should we do?
Here is an example:
library(shiny)
library(DT)
dat <- data.frame(
select = as.character(selectInput("id", label = NULL, choices = c("A", "B")))
)
ui <- fluidPage(
br(),
DTOutput("dtable")
)
server <- function(input, output, session) {
output$dtable <- renderDT({
dat
},
rownames = FALSE,
escape = FALSE,
options = list(
preDrawCallback = JS('function() { Shiny.unbindAll(this.api().table().node()); }'),
drawCallback = JS('function() { Shiny.bindAll(this.api().table().node()); }')
))
observe({
print(input$id)
})
}
shinyApp(ui, server)
Hi @stla and thanks for including a reprex with your issue! I've tried your example with shiny 1.7.1, 1.8.0, and the latest version from main
and in all three cases the example seems to be working.
Can you clarify what you mean by "doesn't work anymore"? In my case, I noticed some unusual behavior with the select input not showing the B selection, but the server is correctly registering the choice indicating the the Shiny.bindAll()
might have worked as expected.
It works for you?? For me the observer executing print(input$id)
never prints anything. Also, when I open the console and I look at Shiny.shinyapp.$bindings
, there's only dtable
, not id
.
It works for you?? For me the observer executing
print(input$id)
never prints anything.
Shoot, yeah, I do see this now trying again, sorry. I'm not sure why it looked like it was working before, maybe browser caching. I'll look into this.
@stla after a bit of digging, I've discovered that the issue isn't about async binding but rather about using selectize in this scenario. If you set selectize = FALSE
in the selectInput()
, the select input works as expected. But selectizeInput()
doesn't work when passing through as.character()
.
library(shiny)
ui <- fixedPage(
HTML(as.character(selectInput("id", label = NULL, choices = c("A", "B"))))
)
server <- function(input, output, session) {
observe(print(input$id))
}
shinyApp(ui, server)
The issue is that the selectize dependencies are attached to the element returned by selectInput()
and don't survive the as.character()
coercion. If you want to use selectize as in your reprex, you can include its dependencies somewhere on the page, at which point your example works correctly.
ui <- fluidPage(
br(),
DTOutput("dtable"),
shiny:::selectizeDependency()
)
That behavior hasn't changed recently, so one possibility is that you used this pattern in an app that happened to have another selectInput()
elsewhere on the page and recently used the pattern in app without other select inputs.
Thanks! Alternatively, one can initialize the selectize input with this DT option:
initComplete = JS(c(
"function(settings){",
" $('#id').selectize();",
"}"
))
but in order that it works, one needs to have a selectizeInput
somewhere in the app. We can hide it if we don't want it.
I've found a better way. No need to add a selectizeInput
in the app with this way:
library(shiny)
library(DT)
library(htmltools)
select_input <- selectInput("id", label = NULL, choices = c("A", "B"))
deps <- htmlDependencies(select_input)
dat <- data.frame(
select = as.character(select_input)
)
ui <- fluidPage(
br(),
DTOutput("dtable"),
tagList(deps)
)
Yeah, that general pattern is the way to go if you're coercing a shiny tag
or tagList
to character. I would recommend that you use htmltools::findDependencies()
instead of htmltools::htmlDependencies()
. findDependencies()
recurses into the tag object, whereas htmlDependencies()
reads the dependencies attached to the particular tag or object passed into it.