javascripthtmlrshinygolem

How to setup a Shiny app designed with Golem that uses a htmlTemplate and links to an external JavaScript file


I am trying to understand the required architecture of a Shiny Golem app that utilises a html template and invokes some basic JavaScript on the press of a button. I think that I might be placing the index.html file in the wrong location, and I have not had success on invoking the JavaScript addEventListener on the button.

I have included the html with htmlTemplate, my understanding from reading through Engineering Production-Grade Shiny Apps (https://engineering-shiny.org/index.html) is that golem_add_external_resources() should handle linking the ‘script.js’ (which was created with a call to golem::add_js_file('script') during the project setup). I would assume then that script.js would load after the DOM is created.

I can see from looking at my browser’s console that the JavaScript ‘mybutton’ variable is null though, and that the addEventListener kicks the error ... ‘Uncaught TypeError: mybutton is null’.

Any help much appreciated thanks.

myproject
-- inst
    -- app
        -- www
            -- script.js
        -- index.html
-- R
    -- app_config.R
    -- app_server.R
    -- app_ui.R
    -- run_app.R

app_ui.R

#' @param request Internal parameter for `{shiny}`.
#'     DO NOT REMOVE.
#' @import shiny
#' @noRd
app_ui <- function(request) {
  tagList(
    golem_add_external_resources(),
    fluidPage(
      htmlTemplate('inst/app/index.html')
    )
  )
}

#' @import shiny
#' @importFrom golem add_resource_path activate_js favicon bundle_resources
#' @noRd
golem_add_external_resources <- function() {
  add_resource_path(
    "www",
    app_sys("app/www")
  )

  tags$head(
    favicon(),
    bundle_resources(
      path = app_sys("app/www"),
      app_title = "integrate0003"
    )
  )
}

app_server.R

#' @param input,output,session Internal parameters for {shiny}.
#'     DO NOT REMOVE.
#' @import shiny
#' @noRd
app_server <- function(input, output, session) {}

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE    =edge">
    <meta name="viewport" content="width=device-width,     initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <button id="mybtn">clickme</button>
</body>
</html>

script.js

let mybutton = document.getElementById('mybtn');

console.log(mybutton);

mybutton.addEventListener('click', function(){

    alert('clicked')

});

Launch the app with....

golem::detach_all_attached()
golem::document_and_reload()
run_app()

Solution

  • I found the solution at Chapter 17 of Engineering Production-Grade Shiny Apps (https://engineering-shiny.org/index.html) where it informs to wrap the JavaScript function in $(function(){}) so that the "function is launched only when the document is ready".

    The script.js now looks like below, and the event kicks the alert now.

    $(function(){
      
      let mybutton = document.getElementById('mybtn');
      console.log(mybutton);
      
      mybutton.addEventListener('click', function(){
        alert('clicked')
        
      });
    })