Pabort
Pabort

Reputation: 348

use Shiny.setInputValue inside module from DT callback

I have a problem inside a bigger app, I narrowed it to this repex

I have a DT table inside a module. I need to get data from rows of that table by clicking on it. For that, I use the callback param from DT::renderDataTable

Outside the module, it's fine. Inside the module, I can't get the click event data back. In the explorer console, it says that the value doesn't exist, so it can't log it.

library(shiny)
library(DT)

tableUI <- function(id){
  ns <- NS(id)
  tagList(
    DT::dataTableOutput(ns("mytable"))
    textOutput(ns("mytext"))
  )
}

tableServer <- function(id){
  moduleServer(id, function(input, output, session){
    output$mytable = DT::renderDataTable({
      mtcars
    },
    callback = JS('table.on("click.dt", "tr",
                      function() {
                      data = table.rows(this).data().toArray();
                      Shiny.setInputValue("mytext1", data, {priority: "event"});
                      console.log(mytext1);
                      });')
    )
    
    observeEvent(req(input$mytext1), {
      output$mytext <- renderText(input$mytext1)
    })
  })
}
ui <- basicPage(
  tableUI("table1")
)

server <- function(input, output) {
  tableServer("table1")
}

shinyApp(ui, server)

I tried adding the ns id to the setInputValue id, like "mytable-mytext1", but that gives me a shiny error and the app doesn't even start:

Warning: Error in eval: object 'mytext1' not found
  [No stack trace available]

Upvotes: 3

Views: 2142

Answers (1)

Mikko Marttila
Mikko Marttila

Reputation: 11908

You need to include the module namespace with Shiny.setInputValue(). You mention trying with "mytable-mytext1" but that’s using the table ID, not the module ID to build the namespace. Instead, you’d want "table1-mytext1". A better way to build that would be with sessions$ns() (see below).

Furthermore, for the example you showed, you don’t need to use a custom callback in the first place. You can instead make use of the built in input that DT creates for the last clicked row.

library(shiny)
library(DT)

tableUI <- function(id) {
  ns <- NS(id)
  tagList(
    DT::dataTableOutput(ns("mytable")),
    textOutput(ns("mytext1")),
    textOutput(ns("mytext2")),
  )
}

tableServer <- function(id) {
  moduleServer(id, function(input, output, session) {
    output$mytable <- DT::renderDataTable(
      mtcars,
      callback = JS(sprintf(
      'table.on("click.dt", "tr", function() {
        data = table.rows(this).data().toArray();
        Shiny.setInputValue("%s", data);
      });', session$ns("mytext1")))
    )
    
    output$mytext1 <- renderText(req(input$mytext1))
    output$mytext2 <- renderText({
      row <- mtcars[req(input$mytable_row_last_clicked), ]
      # Need to append rowname to match the JavaScript result
      c(rownames(row), as.character(row))
    })
  })
}

ui <- basicPage(
  tableUI("table1")
)

server <- function(input, output) {
  tableServer("table1")
}

shinyApp(ui, server)

Upvotes: 5

Related Questions