Search code examples
javascriptrshinyshinyjsgt

How to use Javascript correctly to target gt table row elements in Shiny


I am making a shiny app with a gt table.
I would like to make text in the .gt_group_heading class bold if capitalized.

It seems any text passed to this element is created within the td tag as pure text and not recognized as HTML.

So I was thinking perhaps I can alter the look of the text using Javascript?

When I run the app, I cannot get the script to work.
I get the following error:

Uncaught TypeError: Cannot read property 'innerHTML' of null
at (index):22 

However, when I run the app, then type the javascript directly into the browser console, I am able to get a capitalized text to appear as bold.

So is it something about shiny that is preventing the code from running correctly? It seems I need the code to run after the GT table is created, perhaps that is why the error message throws a null - but I don't know how to make that order of events happen in shiny.

FYI I have tried to wrap the strings in df$name with html tags, but this does not render correctly, i.e. the following does not work:

`df$name <- mutate(df$name, paste("<b>", df$name, "</b>"))`    

Any help very much appreciated. Here is my sample code.

library(tidyverse)
library(gt)
library(shiny)

df <- tibble(

  name = c("john", "john", "jerry", "jerry", "jack", "jack", "jim", "jim"), 
  day = c("wed", "wed", "thurs", "thurs", "mon", "mon", "tues", "tues"), 
  lotto = c(12, 42, 24, 57, 234, 556, 34, 23), 
  car = c("chevy", "toyota", "honda", "gmc", "tesla", "nissan", "ford", "jeep")

) %>% 
  mutate(name = toupper(name))

options(gt.row_group.sep = "\n")

ui <- fluidPage(

  gt_output("table"),

  tags$script(
    HTML(
  
     "
    var heading = document.querySelector('#one > table > tbody > tr > td');
    var html = heading.innerHTML;
    html = html.replace(/(\b[A-Z]{2,}\b)/g,'<strong>$1</strong>');
    heading.innerHTML = html;

    "
    )
  )



)


server <- function(input, output, session){


  output$table <- render_gt(



    df %>% 
      arrange(lotto) %>%
      gt(
    
        id = "one",
        groupname_col = c("name", "day"), 
        rownames_to_stub = TRUE, 
        row_group.sep = getOption("gt.row_group.sep", "\n")
    
      ) %>%
  
      opt_css(
        css = "

    #one .gt_group_heading {

    white-space:pre-wrap;
    word-wrap:break-word;


    }

    "
      )

  )

}

shinyApp(ui, server)

Solution

  • A possibility is to delay the execution of the JavaScript code with setTimeout, for example 2 seconds:

    setTimeout(function() {
      var heading = document.querySelector('#one > table > tbody > tr > td');
      var html = heading.innerHTML;
      html = html.replace(/(\b[A-Z]{2,}\b)/g, '<strong>$1</strong>');
      heading.innerHTML = html;
    }, 2000); // 2000ms = 2s
    

    It's never clear how to chose a nice value of the delay. Another possibility is to use an interval, which is executed every 100ms until it finds the object:

    var myinterval = setInterval(function() {
      var heading = document.querySelectorAll('#one > table > tbody > tr > td');
      if(heading) {
        clearInterval(myinterval);
        for(var i=0; i<heading.length; i++){
          var html = heading[i].innerHTML;
          html = html.replace(/(\b[A-Z]{2,}\b)/g, '<strong>$1</strong>');
          heading[i].innerHTML = html;
        } 
      } 
    }, 100);
    

    This is the JavaScript code. But since it is sent from a string, you have to double the backslashes in the regular expression: /(\\b[A-Z]{2,}\\b)/g.