knitr::opts_chunk$set(echo = TRUE)
library(shiny)
library(shinyWidgets)
library(shinydashboard)
library(charpente)

{shinydashboard} boxes on steroids

The Shiny input binding system is too convenient to be only used for input elements. In {shinydashboard}, you may know the box function. Boxes are containers with a title, body, footer, as well as optional elements. It would be nice to capture the state of the box in an input, so as to trigger other actions as soon as this input changes, or to programmatically change the state of the box from the server.

AdminLTE2 exploration

{shinydashboard} is built on top of the AdminLTE2 admin template. The demonstration gives a overview of the whole capabilities.

  1. Navigate to the template demo and inspect the HTML structure of a box.
  2. What happens to the class when the box is collapsed? Closed? Hint: this will be useful for the input binding development!

Develop the input binding

Below, we'll design the updateBox function.

  1. In the .R/box2.R script, create an updateBox function based on the following code chunk and replace the ... by the appropriate elements.
updateBox <- function(...) {
  # your logic
  ...$...(...)
}
  1. {charpente} provide tools to create input binding boilerplate, through the create_input_binding. Run create_input_binding("boxBinding") in the R console.

  2. Create an HTML dependency pointing to the previously created script, that is boxBinding.js. This will be necessary to test the developed code. As a reminder, we may use create_custom_dependency("box", script = "input-boxBinding.js").

  3. The first step of the binding is the find method. Recall what it is supposed to do, then write the corresponding code. Hint: scope refers to the document. In jQuery, we use find to find all descendants of the selector, matching the condition. For instance $(document).find(".titi") will find all elements having the class titi. Importantly, if you apply find on a child element, it won't be able to find items in the parents. find must call return at the end of its definition.

  4. Implement the getValue method. Hint: hasClass allows to check whether the selected elements has the given class. getValue must call return at the end of its definition. We'll return an object return {collapsed: ...} to be able to access input$<box_id>$collapsed on the R side.

  5. It's time to create the R component. As we don't have unlimited time, the box2 function is already available in the ./R folder. Add it the id parameter necessary to link the HTML element to the JS binding. Once done add the JS dependency to the fluidPage element, using newly created the add_box_deps function.

devtools::load_all()
ui <- fluidPage(
  # import shinydashboard deps without the need of the dashboard template
  useShinydashboard(),

  tags$style("body { background-color: ghostwhite};"),

  br(),
  box2(
   title = textOutput("box_state"),
   "Box body",
   id = "mybox",
   collapsible = TRUE,
   plotOutput("plot")
  ),
  actionButton("toggle_box", "Toggle Box", class = "bg-success")
 )

 server <- function(input, output, session) {
  output$plot <- renderPlot({
    req(!input$mybox$collapsed)
    plot(rnorm(200))
  })

  output$box_state <- renderText({
    state <- if (input$mybox$collapsed) "collapsed" else "uncollapsed"
    paste("My box is", state)
  })

  observeEvent(input$toggle_box, {
    updateBox("mybox")
  })

 }

 shinyApp(ui, server)

Try to manually toggle the box. What happens and why? Why isn't the updateBox doing anything?

  1. The subscribe method gives instruction to Shiny on when to update the current input value. This is a good place for an event listener! Implement subscribe and check if the button works as expected. Hint: as a reminder an event listener is created with the following pattern.
$("selector").on("event_name", function(e) {
  // your logic
});

The will be 2 event listeners: - For manual actions (click) - For programmatically triggered changes (change)

A last advise: the box has an animation delay (500ms) according to the documentation. The setTimeout JS function allows to delay the execution of any JS code located inside by the specified amount of milliseconds. See the pattern below that you may try with repl.it:

var a = 1;
setTimeout(function() {
  a = 2;
  // printed after 1 second
  console.log(a);
}, 2000);
// printed immediately
console.log(a);
  1. Let's go further and try to programmatically update the box. We need 3 elements:

  2. Send information from R to JS with the session$sendInputMessage method. This is already done since question 4!

  3. Receive an treat information in the client (JS) with reveiveMessage and setValue.

Hint: AdminLTE2 provides a plug and play toggleBox method that you may use inside setValue.

  1. Test the code. Why is the box collapsing but the input value still unchanged (in the box title)?

  2. We actually have to update subscribe to add an extra event listener. This event must be triggered in the setValue or reveiveMessage method. Add $(el).trigger('change'); to setValue and update subscribe to listen to this new event. Hint: below is a starting point.

$(el).on(..., function(event) {
  setTimeout(function() {
    callback();
  }, 550);
});
  1. Check that everything works.

Congrats, you've just created your first input binding!!!



RinteRface/Unleash-Shiny-Exercise-3 documentation built on Oct. 12, 2020, 7:10 p.m.