knitr::opts_chunk$set(echo = TRUE) library(shiny) library(shinyWidgets) library(shinydashboard) library(charpente)
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.
{shinydashboard}
is built on top of the AdminLTE2 admin template. The demonstration gives a overview of the whole capabilities.
Below, we'll design the updateBox
function.
.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 ...$...(...) }
{charpente}
provide tools to create input binding boilerplate, through the create_input_binding
. Run create_input_binding("boxBinding")
in the R console.
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")
.
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.
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.
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?
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);
Let's go further and try to programmatically update the box. We need 3 elements:
Send information from R to JS with the session$sendInputMessage
method. This is already done since question 4!
reveiveMessage
and setValue
. Hint: AdminLTE2
provides a plug and play toggleBox
method that you may use inside setValue
.
Test the code. Why is the box collapsing but the input value still unchanged (in the box title)?
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); });
Congrats, you've just created your first input binding!!!
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.