cssrgt

gt groupname_col option initially closed


I'm using the groupname_col option of the gt package to group the table. When we use this in combination with the opt_interactive function we can collapse the tables per group which is really usefull. Initially the folds are open, but I want to have them closed at start. Here is some reproducible code:

library(gt)

iris |>
  gt(groupname_col = "Species") |>
  opt_interactive() 

Output:

enter image description here

As you can see the setosa tab is initially open. How can we have these tabs all initially closed?


Solution

  • There doesn't seem to be a way to do this directly by providing an R option. However, if you look at the html file generated you'll see that it contains some json with "defaultExpanded":true. There are two ways to fix this:

    1. Update the defaultExpanded value before saving to html

    We can basically reproduce what gt:::gt_save_html() does under the hood, but set defaultExpanded <- TRUE:

    create_collapsed_html <- function(gt_tbl, outfile = "gt.html") {
        gt_tbl <- gt:::as.tags.gt_tbl(gt_tbl)
        gt_tbl$children <- lapply(
            gt_tbl$children,
            \(l) {
                target <- l$x$tag$attribs$defaultExpanded
                if (!is.null(target)) {
                    l$x$tag$attribs$defaultExpanded <- FALSE
                }
                l
            }
        )
        htmltools::save_html(gt_tbl, outfile)
    }
    
    iris |>
        gt(groupname_col = "Species") |>
        opt_interactive() |>
        create_collapsed_html()
    

    This will create an html file with the sections not expanded by default:

    buttons

    2. Dynamic changes use JavaScript

    If you're not writing to a static html file, e.g. you are creating a Shiny app, the above might not be an option. You can instead use some JS to click the button to collapse each of the sections when the page loads. Having said that, this is more challenging than I thought. There are a few hurdles:

    1. You can't save a gt table with htmlwidgets::saveWidget() so you can't inject JS with htmlwidgets::onRender().
    2. The collapse buttons do not load immediately so you need to wait for them to be painted.
    3. After you have clicked the buttons, you need to cancel the event listener so that if the user expands the buttons they are not immediately collapsed again.

    Here is some JS which basically waits for the buttons to load, counts how many there are, collapses them all and then stops listening:

    function collapseAllButtons() {
        function collapseButton(button) {
            button.click();
            buttonsCollapsed += 1;
            if (buttonsCollapsed < numButtons) {
                // keep going until none left
                requestAnimationFrame(waitForButtons);
            }
        }
    
        function waitForButtons(timestamp, buttonSelector = '.rt-expander-button[aria-expanded=\"true\"]') {
            const button = document.querySelector(buttonSelector);
            if (button) {
                if (firstRun) {
                    numButtons = document.querySelectorAll(buttonSelector).length;
                    firstRun = false;
                }
                collapseButton(button); // begin collapsing
            } else {
                requestAnimationFrame(waitForButtons); // check again next frame
            }
        }
    
        let firstRun = true;
        let buttonsCollapsed = 0;
        let numButtons;
        requestAnimationFrame(waitForButtons);
    }
    collapseAllButtons();
    

    If we save this as collapseAllButtons.js, in R we can then append this in a <script> to the html generated by gt:

    script <- sprintf(
        "<script>%s\n</script>",
        paste(readLines("./collapseAllButtons.js"), collapse = "\n")
    ) |> htmltools::HTML()
    
    iris |>
        gt(groupname_col = "Species") |>
        opt_interactive() |>
        gt:::as.tags.gt_tbl() |>
        htmltools::tagAppendChildren(script) |>
        htmltools::save_html("./gt_iris_tmp.html")
    

    Or in Shiny you can just stick the script in there somewhere. For a Quarto example, see below.

    Quarto example

    Here is a minimal example of how to do use the first approach in Quarto. This is a complete qmd file:

    # Iris example
    
    ```{r, echo=FALSE}
    create_collapsed_html <- function(gt_tbl, outfile = "gt.html") {
        gt_tbl <- gt:::as.tags.gt_tbl(gt_tbl)
        gt_tbl$children <- lapply(
            gt_tbl$children,
            \(l) {
                target <- l$x$tag$attribs$defaultExpanded
                if (!is.null(target)) {
                    l$x$tag$attribs$defaultExpanded <- FALSE
                }
                l
            }
        )
        gt_tbl
    }
    ```
    
    ```{r}
    iris |>
        gt::gt(groupname_col = "Species") |>
        gt::opt_interactive() |>
        create_collapsed_html()
    ```
    

    If you quarto render this you should get:

    enter image description here