0
votes

Let's say we have a Shiny app with two DT tables, which we would like to modify with some Javascript after the tables have been rendered. We are appending a different superscript to a header in each table; we append a superscripted '1' to table 1, and a superscripted '2' to table 2. This JS is triggered using initComplete, i.e., when the table is rendered.

My JS skills are nonexistent, so I am currently selecting the table header to append to with th:contains(<header-string>). For example, appending a superscript to a table header containing the string 'Sepal':

jsc <- '
    function(settings, json) {
        $("th:contains(\'Sepal\')").append(\'<sup>1</sup>\');
    }
'

Since we need a different superscript for the different tables, we need to define separate Javascript to trigger for each table. This issue is that the table headers both contain the string that I am looking for, resulting in both superscripts being appended to both table headers.

Assuming that the different tables will always contain the same string in the relevant table header (and therefore that th:contains will not be a viable option -- the table headers could be exactly the same), how can we select a specific table to apply the Javascript to? We can't supply an elementId to the datatable call in a Shiny app, e.g.,

output$table1<- renderDT({datatable(iris, elementId = 'table1')})

results in the warning

Warning in renderWidget(instance) : Ignoring explicitly provided widget ID "table1"; Shiny doesn't use them

A reproducible example is below. The two tables have identical column names, so the Javascript is appending both superscripts to the Sepal.Length header of each table. The desired output is to have the first table's Sepal.Length header with a superscripted '1', and the second table's Sepal.Length header with a superscripted '2', while the current example appends '12' to both table's headers.

Funnily enough, when the app opens in the RStudio Viewer, only the first initComplete is run, so both table headers have a superscripted '1'. I am ignoring this and checking results on Firefox and Chrome.

library(shiny)
library(DT)

jsc <- '
    function(settings, json) {
        $("th:contains(\'Sepal\')").append(\'<sup>1</sup>\');
    }
'

jsc2 <- '
    function(settings, json) {
        $("th:contains(\'Sepal\')").append(\'<sup>2</sup>\');
    }
'

ui <- {
    fluidPage(
        fluidRow(
            column(6,
               DTOutput('table1')
            ),
            column(6,
               DTOutput('table2')
            )
        )
    )
}

server <- function(input, output, session) {
    output$table1 <- renderDT({
        datatable(iris[, c('Species', 'Sepal.Length')],
            options(
                initComplete = JS(jsc))
        )
    })
    
    output$table2 <- renderDT({
        datatable(iris[, c('Species', 'Sepal.Length')],
            options(
                initComplete = JS(jsc2)  
            )
        )
    })
}

options(shiny.launch.browser = TRUE)
shinyApp(ui, server)

Please note that I'm aware we can could directly write the <sup>1</sup> into the table headers with escape = FALSE, but there are other JS functions to execute on the table so this approach will not be suitable. Thank you for any advice.

1

1 Answers

1
votes

First, options must be a list:

datatable(iris[, c('Species', 'Sepal.Length')],
          options = list(
            initComplete = JS(jsc)
          )
)

Now, you can target the table by providing the id in the jQuery selector:

jsc <- '
    function(settings, json) {
        $("#table1 th:contains(\'Sepal\')").append(\'<sup>1</sup>\');
    }
'

Otherwise, $("th:contains(\'Sepal\')") selects all th that contain Sepal that can be found on the whole page.

But this way requires to change the JS code if you change the id. It is more practical to do:

jsc <- '
    function(settings, json) {
        var $table = $(this.api().table().node());
        $table.find("th:contains(\'Sepal\')").append(\'<sup>1</sup>\');
    }
'

Full code:

library(shiny)
library(DT)

jsc <- '
    function(settings, json) {
        var $table = $(this.api().table().node());
        $table.find("th:contains(\'Sepal\')").append(\'<sup>1</sup>\');
    }
'

jsc2 <- '
    function(settings, json) {
        var $table = $(this.api().table().node());
        $table.find("th:contains(\'Sepal\')").append(\'<sup>2</sup>\');
    }
'

ui <- {
  fluidPage(
    fluidRow(
      column(6,
             DTOutput('table1')
      ),
      column(6,
             DTOutput('table2')
      )
    )
  )
}

server <- function(input, output, session) {
  output$table1 <- renderDT({
    datatable(iris[, c('Species', 'Sepal.Length')],
              options = list(
                initComplete = JS(jsc)
              )
    )
  })
  
  output$table2 <- renderDT({
    datatable(iris[, c('Species', 'Sepal.Length')],
              options = list(
                initComplete = JS(jsc2)  
              )
    )
  })
}

shinyApp(ui, server)