Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to use Shiny.bindAll for a DT table? #3979

Closed
stla opened this issue Jan 31, 2024 · 8 comments
Closed

How to use Shiny.bindAll for a DT table? #3979

stla opened this issue Jan 31, 2024 · 8 comments

Comments

@stla
Copy link

stla commented Jan 31, 2024

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)
@gadenbuie
Copy link
Member

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.

@stla
Copy link
Author

stla commented Jan 31, 2024

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.

@gadenbuie
Copy link
Member

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
Copy link
Member

@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
Copy link
Member

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.

@stla
Copy link
Author

stla commented Jan 31, 2024

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
Copy link
Author

stla commented Jan 31, 2024

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

@gadenbuie
Copy link
Member

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants