Shiny update broke inputs in datatable

2,544 views
Skip to first unread message

Jack

unread,
Dec 11, 2015, 10:25:19 AM12/11/15
to Shiny - Web Framework for R
Hi,

I have a dataTable with inputs in the cells which change during a session, but as of an update in the Shiny package, these inputs no longer trigger anything reactive. I tried implementing a bindAll on the drawCallback, and this works for the first draw of the dataTable, but after a redraw, it doesn't seem to be able to rebind the inputs, even if they're the same inputs. I have a system running Shiny 0.11.1 which still has this working (without any bindAll necessary), but the newest version, 12.2, does not work even with it.

Example code: 
prog$S <- paste0('<div><div class="checkbox">
                     <label><input id="selone',prog$S,'" type="checkbox"/><span></span></label></div></div>')
DT::datatable(prog,options = list(paging=TRUE,searching=TRUE), escape = c(-1), rownames = FALSE)

This is the drawCallback option for the datatable that lets it work on the first draw, but not subsequent draws (from updating the table content):
drawCallback = JS('function() { 
                           Shiny.unbindAll(this.api().table().node());
                           Shiny.bindAll(this.api().table().node());} ')

Thanks!
Jack

Joe Cheng

unread,
Dec 11, 2015, 12:47:47 PM12/11/15
to Jack, Shiny - Web Framework for R
Thanks for the report. Can I trouble you to create a reproducible example? That would be really helpful, thanks.

--
You received this message because you are subscribed to the Google Groups "Shiny - Web Framework for R" group.
To unsubscribe from this group and stop receiving emails from it, send an email to shiny-discus...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/shiny-discuss/7f87f18e-a218-4e7c-870b-74853bbfa75f%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Jack DeWeese

unread,
Dec 11, 2015, 1:40:26 PM12/11/15
to Joe Cheng, Shiny - Web Framework for R
Sure thing!

ui.R:
library(shiny)
shinyUI(fluidPage(
  fluidRow(
    column(
      width=5,offset=0,
      textOutput("preview1"),
      DT::dataTableOutput("islandList")
    ),
    column(
      width=1,
      HTML("<br><br>"),
      uiOutput("deleteUI")
    )
  )
))

server.R:
library(shiny)
library(htmlwidgets)

shinyServer(function(input,output,session) {
  values <- reactiveValues(deleted = character(0))
  
  dat <- reactive({
    del <- values$deleted
    isolate({
      dat <- data.frame(Island = names(islands), Area = unname(islands))
      dat$Island <- as.character(dat$Island)
      dat <- dat[!(dat$Island %in% del),]
      return(dat)
    })
  })
  
  output$islandList <- DT::renderDataTable({
    island <- dat()
    validate(
      need(island,""),
      need(nrow(island)>0,"No islands found.")
    )
    island$Delete <- paste0('<div><div class="checkbox">
                     <label><input id="selone',island$Island,'" type="checkbox"/><span></span></label></div></div>')
    DT::datatable(island,options = list(paging=TRUE,searching=TRUE,
                                        preDrawCallback = JS('function() {
                                                             Shiny.unbindAll(this.api().table().node());}'),
                                   drawCallback = JS('function() {
                                                     Shiny.bindAll(this.api().table().node());} '))
                  , escape = c(1,2), rownames = FALSE)
  })
  
  output$preview1 <- renderText({
    island <- dat()
    return(paste0("Total Area: ",sum(island$Area)))
  })
  
  observe({
    asdf <- input$delete
    if(is.null(asdf) || asdf == 0)
    {
      return(NULL)
    }
    isolate({
      island <- dat()
      ids <- island$Island
      toSel <- vapply(ids,function(i){temp <- input[[paste0('selone',i)]]; ifelse(is.null(temp),0,temp)},numeric(1))
      values$deleted <- c(values$deleted,ids[toSel==1])
    })
  })
  
  output$deleteUI <- renderUI({
    island <- dat()
    if(is.null(island))
    {
      return(NULL)
    }
    ids <- island$Island
    toSel <- vapply(ids,function(i){temp <- input[[paste0('selone',i)]]; ifelse(is.null(temp),0,temp)},numeric(1))
    if(sum(toSel))
    {
      return(actionButton("delete","Delete"))
    }
  })
  
  }
)

Jack

unread,
Dec 11, 2015, 1:51:09 PM12/11/15
to Shiny - Web Framework for R, j...@rstudio.com
(If you click any of the checkboxes, it should show the Delete button, which removes the item from the list, but after the first delete, the checkboxes no longer work)
To unsubscribe from this group and stop receiving emails from it, send an email to shiny-discuss+unsubscribe@googlegroups.com.

Joe Cheng

unread,
Dec 14, 2015, 2:34:26 PM12/14/15
to Jack, Shiny - Web Framework for R
I was hoping to run a git bisect but I couldn't get your app into a working state with Shiny 0.11.1, since the DT package requires Shiny 0.12. I tried removing the DT dependency and just using the old built-in renderDataTable/dataTableOutput functions, but I get JavaScript errors.

The problem is that by the time your unbindAll call runs, the old controls are already gone, so they are never unbound. Therefore the new controls are ignored since they refer to input IDs that have already been bound. I'm not sure how this ever would have worked though.

The good news is that the DT package now has row selection built in, so I don't think you need these checkboxes at all. See section 2.1 in the DT docs:

To unsubscribe from this group and stop receiving emails from it, send an email to shiny-discus...@googlegroups.com.

Jack DeWeese

unread,
Dec 14, 2015, 2:59:30 PM12/14/15
to Joe Cheng, Shiny - Web Framework for R
Oh, sorry, I should have been more specific, I'm only using the drawbackCalls in Shiny 0.12 - they aren't needed at all in 0.11.1 so I just have:

DT::datatable(island,options = list(paging=TRUE,searching=TRUE), escape = c(1,2), rownames = FALSE)

(I'm using DT 0.0.25 with Shiny 0.11.1 with no problems on my Shiny Server)

I'll try out the new DT selection capability, but having the checkboxes is a lot more intuitive for users so hopefully that functionality can be restored.

Yihui Xie

unread,
May 4, 2016, 2:50:47 AM5/4/16
to Jack DeWeese, Joe Cheng, Shiny - Web Framework for R, Sean Lopp
I vaguely remember in Shiny 0.11.1 all checkboxes were automatically bound no matter where or when they were added to the page, but this behavior was changed later. Anyway, the key issue is exactly what Joe pointed out: when the last table was destroyed, its checkboxes were not notified to be unbound. I thought the destroy event would be triggered in this case (https://datatables.net/reference/event/destroy), but it was not. So I guess the only way out is probably manually notify the table to unbind its Shiny inputs before it is redrawn with new data. Here is an idea (I only added the code highlighted in red to your example):

library(shiny)
library(htmlwidgets)
shinyApp(
  ui = fluidPage(

    fluidRow(
      column(
        width=5,offset=0,
        textOutput("preview1"),
        DT::dataTableOutput("islandList"),
        tags$script(HTML("Shiny.addCustomMessageHandler('unbind-DT', function(id) {
          Shiny.unbindAll($('#'+id).find('table').DataTable().table().node());
        })"))

      ),
      column(
        width=1,
        HTML("<br><br>"),
        uiOutput("deleteUI")
      )
    )
  ),
  server = function(input,output,session) {

    values <- reactiveValues(deleted = character(0))
   
    dat <- reactive({
      del <- values$deleted
      session$sendCustomMessage('unbind-DT', 'islandList')
Regards,
Yihui

Carl Ganz

unread,
Jun 1, 2016, 3:44:37 PM6/1/16
to Shiny - Web Framework for R, jackp....@gmail.com, j...@rstudio.com, se...@rstudio.com
Doesn't this solution generate a javascript error?

Vincent Jicquel

unread,
May 5, 2017, 9:34:32 AM5/5/17
to Shiny - Web Framework for R, jackp....@gmail.com, j...@rstudio.com, se...@rstudio.com
Very useful answer. Thank you very much. 

To people who have the same problem in modules, don't forget to add the id of your module and - before the id of the dataTable. 
For example : 
tags$script(HTML("Shiny.addCustomMessageHandler('unbind-DT', function(id) {
          Shiny.unbindAll($('#id_mod-'+id).find('table').DataTable().table().node());
        })"))

Regards,
Vincent Jicquel
Reply all
Reply to author
Forward
0 new messages