shiny icon indicating copy to clipboard operation
shiny copied to clipboard

How to use `Shiny.bindAll` for a DT table?

Open stla opened this issue 5 months ago • 8 comments

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)

stla avatar Jan 31 '24 00:01 stla

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.

gadenbuie avatar Jan 31 '24 15:01 gadenbuie

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.

stla avatar Jan 31 '24 17:01 stla

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.

gadenbuie avatar Jan 31 '24 17:01 gadenbuie

@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()
)

gadenbuie avatar Jan 31 '24 18:01 gadenbuie

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.

gadenbuie avatar Jan 31 '24 18:01 gadenbuie

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.

stla avatar Jan 31 '24 21:01 stla

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

stla avatar Jan 31 '24 21:01 stla

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.

gadenbuie avatar Jan 31 '24 21:01 gadenbuie