We're in for 4 hours of fun!
Disclaimer: this workshop is not about building Shiny Apps!!!
<!DOCTYPE HTML><html> <head> <!-- head content here --> </head> <body> <!-- body content here --> </body></html>
There are 2 types of tags:
<div></div>
<img/>
We classify them by usage:
<head></head>
, <body></body>
<script></script>
, <button></button>
<font></font>
Block vs inline:
<div><p>Hello World</p></div>
: Good<span><div><p>Hello World</p></div></span>
: not good<div class="awesome-item" id="myitem"></div><!-- the class awesome-item may be applied to multiple tags --><span class="awesome-item"></span>
The most common attributes:
data-toggle
Attributes are used by CSS and JavaScript to interact with the web page!
DOM is a convenient representation (tree) of the HTML document. We inspect the code of any web page with Firefox or Chrome:
library(shiny)ui <- fluidPage(p("Hello World"))server <- function(input, output) {}shinyApp(ui, server)
Elements
panel, double click between the <p></p>
tag to edit the current text. Press enter when finished{htmltools}
{htmltools}
is a R package designed to:
Historically, {htmltools}
was extracted out of {shiny}
to extend it. That's why, both packages have many common functions!
At the moment, {htmltools}
does not have any user guide, although being an important package for all web things
Let's play ๐ฎ:
runExample("01_hello")
The htmtools::findDependencies
function allows to get the dependencies from a tag.
findDependencies(fluidPage())
Works but not portable
fluidPage( tags$head( tags$style(...), tags$script(src = "path-to-script"), tags$script( "$(function() { // JS logic ... }); " ) ))
With {htmltools}
, we define the dependency with htmlDependency
, then attach it to a tag with tagList
:
use_bs4_dep <- function(tag) { bs4_dep <- htmlDependency( name = "Bootstrap 4", version = "1.0", src = c(href = "https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/"), stylesheet = "bootstrap.min.css" ) tagList(tag, bs4_dep)}use_bs4_dep(fluidPage())
In HTML, we have:
<div class="divclass" id = "daddy"> <h1>A child</h1> <span class="child" id="baby">Crying</span></div>
The corresponding R code is
mytag <- div( class = "divclass", id = "daddy", h1("A child"), span(class = "child", id = "baby", "Crying"))
Some tags like <nav></nav>
need the tags$
prefix before (tags
is a list of functions). The withTags
function to use tags$<tagName>
This process is not interesting and time consuming!
Tools exist:
{charpente}
does the same from the R console with html_2_R
Code conversion by {charpente}
html_2_R('<div class="divclass" id = "daddy"></div>')
## div(## class = "divclass",## id = "daddy"## )
How to access name, attributes, children?
We could use str(mytag)
to inspect the structure of the R object.
mytag$attribs
## $class## [1] "divclass"## ## $id## [1] "daddy"
mytag$children
## [[1]]## <h1>A child</h1>## ## [[2]]## <span class="child" id="baby">Crying</span>
There are 2 methods:
tagAppendAttributes(tag, list of attributes)
is preferredtag$attribs[["new-attribute"]] <- value
tagAppendChild(tag, child)
tagAppendChildren(tag, list of children)
There exist other functions but we'll not use them.
In the following, you'll reconstruct the {shinybulma}
package step by step!
Main tasks:
page
function Lets' start! Open shinybulma.Rmd
and enjoy ๐
25:00
One very big shiny.js
* responsible for:
JS source may be found here. In short all these files are concatenated, giving shiny.js and *shiny.min.js!
var myNumber = 1; // affectationmyNumber--; // decrementconsole.log(myNumber); // print 0
const you = { name: 'your name', // property music : 'your favorite music', printName: function() { // method console.log(`I am ${this.name}`); // here "this" is the object }}you.geek = true; // add extra property
An event listener is a program that triggers when a given event occurs, like after a mouse click.
<button id="mybutton">Go ! </button>
With vanilla JS:
var btn = document.getElementById('mybutton'); // select the buttonbtn.addEventListener('click', function() { // action + consequences alert('You clicked me!'); // action});
With jQuery:
$('#mybutton').on('click', function() { alert('You clicked me!');});
Hopefully you are convinced that jQuery is less verbose than pure JS!
Let's consider the following app
ui <- fluidPage( textInput("text", "My text", "Some text"), actionButton("update", "Update"))server <- function(input, output, session) { observeEvent(input$update, { updateTextInput(session, "text", value = "Updated text") })}shinyApp(ui, server)
Waiiit!!
This is the magic piece allowing bidirectional communication between R and JavaScript. The technology behind is provided by {httpuv}
and {websocket}
.
Network
tabOn the R side session
is an instance of the ShinySession
R6 class allowing to send messages to JS.
2 main methods:
sendCustomMessage
sends R messages to JSsendInputMessage
sends R messages to input bindingsShiny.addCustomMessageHandler
is the JS part of session$sendCustomMessage
.
Both are linked by the type parameter!
In this part you'll need to work on the following RStudio Cloud project. We split the audience in 3 groups (1 instructor per group). Each group will choose between:
After the workshop, you'll have the opportunity to bring this change to the shinybulma repository.
20:00
Input tags have various HTML structure.
<input id = inputId type = "text" class = "input-text" value = value>
The simplest is probably the text input:
$('.input-text').action();
1 All instances of the same input share a unique input binding. Hence, id is mandatory!
An input binding:
๐ Bindings rely on a JS class defined in the input_binding.js file
var customTextBinding = new Shiny.InputBinding();
In the following, we'll need devtools::install_github("DivadNojnarg/OSUICode")
The idea is to locate the input in the DOM.
We generally filter by class, scope being the document.
find: function(scope) { console.log($(scope).find('.input-text')); return $(scope).find('.input-text');}
Run the following and open the HTML inspector.
library(OSUICode)customTextInputExample(1)
This step ensures that R gets the input value at any time. The jQuery val
method allows to get the current value.
getValue: function(el) { return $(el).val();}
customTextInputExample(2)
setValue(el, value)
is used to set the input value. It called within receiveMessage(el, data)
, which is the JS part of all the R updateInput
functions.
setValue: function(el, value) { $(el).val(value);}receiveMessage: function(el, data) { console.log(data); if (data.hasOwnProperty('value')) { this.setValue(el, data.value); } // other parameters to update...}
Run the following and open the HTML inspector. Why doesn't the output value change?
updateCustomTextInputExample(3)
subscribe(el, callback)
listens to events defining Shiny to update the input value and make it available in the app. For a text input, we might have:
subscribe: function(el, callback) { // when updated $(el).on('change.customTextBinding', function(event) { callback(false); }); // keyboard, copy and paste, ... $(el).on('keyup.customTextBinding input.customTextBinding', function(event) { callback(true); });}
Run the following demonstration.
updateCustomTextInputExample(4)updateCustomTextInputExample(5)
callback() tells to update the input on the server (R). If true, a rate policy is applied!
As mentioned before, callback(true)
will set rate policy for the given event listener. Setting a rate policy is relevant when we donโt want to flood the server with tons of update requests.
getRatePolicy: function() { return { // Can be 'debounce' or 'throttle' policy: 'debounce', delay: 500 };}
Below, the text input only updates after releasing the keyboard for 250ms.
updateCustomTextInputExample(6)
Boxes are a center element of {shinydashboard}
. Yet the latter only exploit 10% of their
capability. Your mission: unleash the AdminLTE API to deliver box on steroids! Open box_on_steroids.Rmd
.
20:00
A brief plan of this part of the workshop
Serve data as an HTTP response and read it in JavaScript to produce an HTML output.๐ฎ
Unleash-Shiny-Exercises-Part-3
RStudio Cloud.Unleash-Shiny-Exercises-Part-3
RStudio Cloud.Have Unleash-Shiny-Exercises-Part-3
ready, it will come in handy.
"Traditional" applications and websites
A new page is requested
/
, server responds/about
, server gives a different response/products
, server gives a yet a different responseand so on
๐ก Think {plumber}
So which one of those does shiny use?
Both!
But one (probably) more than the other...
When you visit a shiny app:
server
which responds with the initial ui
.input
, output
).No other /page
is ever visited in shiny
HTTP header ~ meta data: gives necessary information to the browser.
Importantly:
Status
- How is the response? 404
not found200
all good500
server-side error301
redirectContent-Type
- type of content ('text/html', 'application/json', etc.)Content
- content to renderThese are all standardized, we don't get to choose.
A websocket sends binary data so there is no need for headers.
path <- session$registerDataObj(name = "about", data, filterFunc)
Since the response if only valid for a single session the path returned is dynamically generated.
Example paths for different sessions:
session/0a6ed2556d97dcaa1ddeb615dc04cd1e/dataobj/about
session/259b8dc2f18ffc975b246d206b60073f/dataobj/about
session/72abd5a5da9a1651bee32d25a908a0cd/dataobj/about
What is this path
anywayโ
It's where the HTTP response is servedโ
path <- session$registerDataObj(name = "about", data = "<h1>Hello!</h1>", filterFunc)
The core of it all: it actually serves the HTTP response.
# filterFuncfunction(data, req){ shiny:::httpResponse( status = 200, # default content_type = "text/html", # default content = data )}
Note: httpResponse
is not exported, hence the :::
Minor changes to return JSON data.
path <- session$registerDataObj( name = "cars-data", data = cars, function(data, req) { # seralise to JSON res <- jsonlite::toJSON(data) shiny:::httpResponse( content_type = "application/json", content = res ) })
[ { "speed": 4, "dist": 2 }, { "speed": 4, "dist": 10 }]
Meet "box" our soon-to-be custom output โ
๐ bulma.io/documentation/layout/level
renderBox.R
(1)Internally all render*
functions return another function()
โ
It's a function! ๐
shiny::exprToFunction({ head(cars, 10)})
## function () ## {## head(cars, 10)## }
We can run it ๐
fn <- shiny::exprToFunction({ x <- 1 x + 3})fn()
## [1] 4
Shiny = Reactivity: functions are easier to rerun
output$something <-
you actually assign a function
โ
renderBox.R
(2)Great, render*
returns a function, but what about that one then ๐
renderSomething <- function(expr){ func <- shiny::exprToFunction(expr) # assigned to `output` function(){ data <- func() # sent to the JavaScript binding return(data + 1) }}
The result of render*
is a function, the result of which is sent to the JavaScript binding ๐ก
Eerily similar to inputs: 1) initialise, 2) extend, 3) register.
var boxBinding = new Shiny.OutputBinding();$.extend(boxBinding, { find: function(scope) { return $(scope).find(".box"); }, renderValue: function(el, data) { // Get the serialised JSON // Use it to render content }});Shiny.outputBindings.register(boxBinding, "pharma.box");
The renderValue
in the JavaScript binding the following:
function(el, data) { // httr::GET (1) fetch(data) // httr::content (2) .then(response => response.json()) // lapply | purrr::map (3) .then(data => { data.map((row)=>{ let div = createElement(row); $(el).append(div); }) })}
Args
el
the element (boxOutput
)data
the path sent by renderBox
Logic
level-item
<nav>
Numerous improvements could be made.
box
function could accept a model and extract relevant statisticsbox
accepts different kinds of objects (e.g.: xts
)Plenty more can be done!
ui <- fluidPage( textInput('txt_a', 'Input Text A'), textInput('txt_b', 'Input Text B'), uiOutput('txt_c_out'), verbatimTextOutput("show_last"))server <- function(input, output, session) { output$txt_c_out <- renderUI(textInput('txt_c', 'Input Text C')) values <- reactiveValues(lastUpdated = NULL) observe({ lapply(names(input), function(x) { observe({ input[[x]] values$lastUpdated <- x }) }) }) output$show_last <- renderPrint(values$lastUpdated)}
We leverage Shiny.setInputValue
and Shiny JS events. shiny:inputchanged
fires each time an input is changed!
$(document).on('shiny:inputchanged', function(event) { Shiny.setInputValue('pleaseStayHome', {name: event.name, value: event.value, type: event.binding.name.split('.')[1]});});
On the R side, we listen to input$pleaseStayHome
. That's it!
The {shinylogs}
package developed by dreamRs contains this feature:
shinyApp( ui = fluidPage( numericInput("n", "n", 1), sliderInput("s", "s", min = 0, max = 10, value = 5), verbatimTextOutput("lastChanged") ), server = function(input, output, session) { # specific to shinylogs track_usage(storage_mode = store_null()) output$lastChanged <- renderPrint(input$`.shinylogs_lastInput`) })
Don't forget that Shiny
is a JS object having the notifications.show
method!
We add onclick
to an actionButton
.
ui <- fluidPage( actionButton( "notif", "Show notification", onclick = "Shiny.notifications.show({ html: '<strong>Oups</strong>', type: 'error', duration: 2000 });" ))server <- function(input, output, session) {}shinyApp(ui, server)
Case study: we would like to modify the action button behavior on the fly.
Steps:
shiny:connected
eventShiny.inputBindings
registry bindAll
$(function() { $(document).on('shiny:connected', function(event) { Shiny.unbindAll(); $.extend(Shiny .inputBindings .bindingNames['shiny.actionButtonInput'] .binding, { // do whathever you want to edit existing methods }); Shiny.bindAll(); });});
What if you don't want to update input from the server? ๐คท
setValue
to add new valuesubscribe
(tells Shiny to update from the R side)$(function() { // each time we click on #test (a button) $('#test').on('click', function() { var $obj = $('#button'); var inputBinding = $obj.data('shiny-input-binding'); var val = $obj.data('val') || 0; inputBinding.setValue($obj, val + 10); $obj.trigger('click'); });});
Keyboard shortcuts
โ, โ, Pg Up, k | Go to previous slide |
โ, โ, Pg Dn, Space, j | Go to next slide |
Home | Go to first slide |
End | Go to last slide |
Number + Return | Go to specific slide |
b / m / f | Toggle blackout / mirrored / fullscreen mode |
c | Clone slideshow |
p | Toggle presenter mode |
t | Restart the presentation timer |
?, h | Toggle this help |
o | Tile View: Overview of Slides |
Esc | Back to slideshow |
We're in for 4 hours of fun!
Disclaimer: this workshop is not about building Shiny Apps!!!
<!DOCTYPE HTML><html> <head> <!-- head content here --> </head> <body> <!-- body content here --> </body></html>
There are 2 types of tags:
<div></div>
<img/>
We classify them by usage:
<head></head>
, <body></body>
<script></script>
, <button></button>
<font></font>
Block vs inline:
<div><p>Hello World</p></div>
: Good<span><div><p>Hello World</p></div></span>
: not good<div class="awesome-item" id="myitem"></div><!-- the class awesome-item may be applied to multiple tags --><span class="awesome-item"></span>
The most common attributes:
data-toggle
Attributes are used by CSS and JavaScript to interact with the web page!
DOM is a convenient representation (tree) of the HTML document. We inspect the code of any web page with Firefox or Chrome:
library(shiny)ui <- fluidPage(p("Hello World"))server <- function(input, output) {}shinyApp(ui, server)
Elements
panel, double click between the <p></p>
tag to edit the current text. Press enter when finished{htmltools}
{htmltools}
is a R package designed to:
Historically, {htmltools}
was extracted out of {shiny}
to extend it. That's why, both packages have many common functions!
At the moment, {htmltools}
does not have any user guide, although being an important package for all web things
Let's play ๐ฎ:
runExample("01_hello")
The htmtools::findDependencies
function allows to get the dependencies from a tag.
findDependencies(fluidPage())
Works but not portable
fluidPage( tags$head( tags$style(...), tags$script(src = "path-to-script"), tags$script( "$(function() { // JS logic ... }); " ) ))
With {htmltools}
, we define the dependency with htmlDependency
, then attach it to a tag with tagList
:
use_bs4_dep <- function(tag) { bs4_dep <- htmlDependency( name = "Bootstrap 4", version = "1.0", src = c(href = "https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/"), stylesheet = "bootstrap.min.css" ) tagList(tag, bs4_dep)}use_bs4_dep(fluidPage())
In HTML, we have:
<div class="divclass" id = "daddy"> <h1>A child</h1> <span class="child" id="baby">Crying</span></div>
The corresponding R code is
mytag <- div( class = "divclass", id = "daddy", h1("A child"), span(class = "child", id = "baby", "Crying"))
Some tags like <nav></nav>
need the tags$
prefix before (tags
is a list of functions). The withTags
function to use tags$<tagName>
This process is not interesting and time consuming!
Tools exist:
{charpente}
does the same from the R console with html_2_R
Code conversion by {charpente}
html_2_R('<div class="divclass" id = "daddy"></div>')
## div(## class = "divclass",## id = "daddy"## )
How to access name, attributes, children?
We could use str(mytag)
to inspect the structure of the R object.
mytag$attribs
## $class## [1] "divclass"## ## $id## [1] "daddy"
mytag$children
## [[1]]## <h1>A child</h1>## ## [[2]]## <span class="child" id="baby">Crying</span>
There are 2 methods:
tagAppendAttributes(tag, list of attributes)
is preferredtag$attribs[["new-attribute"]] <- value
tagAppendChild(tag, child)
tagAppendChildren(tag, list of children)
There exist other functions but we'll not use them.
In the following, you'll reconstruct the {shinybulma}
package step by step!
Main tasks:
page
function Lets' start! Open shinybulma.Rmd
and enjoy ๐
25:00
One very big shiny.js
* responsible for:
JS source may be found here. In short all these files are concatenated, giving shiny.js and *shiny.min.js!
var myNumber = 1; // affectationmyNumber--; // decrementconsole.log(myNumber); // print 0
const you = { name: 'your name', // property music : 'your favorite music', printName: function() { // method console.log(`I am ${this.name}`); // here "this" is the object }}you.geek = true; // add extra property
An event listener is a program that triggers when a given event occurs, like after a mouse click.
<button id="mybutton">Go ! </button>
With vanilla JS:
var btn = document.getElementById('mybutton'); // select the buttonbtn.addEventListener('click', function() { // action + consequences alert('You clicked me!'); // action});
With jQuery:
$('#mybutton').on('click', function() { alert('You clicked me!');});
Hopefully you are convinced that jQuery is less verbose than pure JS!
Let's consider the following app
ui <- fluidPage( textInput("text", "My text", "Some text"), actionButton("update", "Update"))server <- function(input, output, session) { observeEvent(input$update, { updateTextInput(session, "text", value = "Updated text") })}shinyApp(ui, server)
Waiiit!!
This is the magic piece allowing bidirectional communication between R and JavaScript. The technology behind is provided by {httpuv}
and {websocket}
.
Network
tabOn the R side session
is an instance of the ShinySession
R6 class allowing to send messages to JS.
2 main methods:
sendCustomMessage
sends R messages to JSsendInputMessage
sends R messages to input bindingsShiny.addCustomMessageHandler
is the JS part of session$sendCustomMessage
.
Both are linked by the type parameter!
In this part you'll need to work on the following RStudio Cloud project. We split the audience in 3 groups (1 instructor per group). Each group will choose between:
After the workshop, you'll have the opportunity to bring this change to the shinybulma repository.
20:00
Input tags have various HTML structure.
<input id = inputId type = "text" class = "input-text" value = value>
The simplest is probably the text input:
$('.input-text').action();
1 All instances of the same input share a unique input binding. Hence, id is mandatory!
An input binding:
๐ Bindings rely on a JS class defined in the input_binding.js file
var customTextBinding = new Shiny.InputBinding();
In the following, we'll need devtools::install_github("DivadNojnarg/OSUICode")
The idea is to locate the input in the DOM.
We generally filter by class, scope being the document.
find: function(scope) { console.log($(scope).find('.input-text')); return $(scope).find('.input-text');}
Run the following and open the HTML inspector.
library(OSUICode)customTextInputExample(1)
This step ensures that R gets the input value at any time. The jQuery val
method allows to get the current value.
getValue: function(el) { return $(el).val();}
customTextInputExample(2)
setValue(el, value)
is used to set the input value. It called within receiveMessage(el, data)
, which is the JS part of all the R updateInput
functions.
setValue: function(el, value) { $(el).val(value);}receiveMessage: function(el, data) { console.log(data); if (data.hasOwnProperty('value')) { this.setValue(el, data.value); } // other parameters to update...}
Run the following and open the HTML inspector. Why doesn't the output value change?
updateCustomTextInputExample(3)
subscribe(el, callback)
listens to events defining Shiny to update the input value and make it available in the app. For a text input, we might have:
subscribe: function(el, callback) { // when updated $(el).on('change.customTextBinding', function(event) { callback(false); }); // keyboard, copy and paste, ... $(el).on('keyup.customTextBinding input.customTextBinding', function(event) { callback(true); });}
Run the following demonstration.
updateCustomTextInputExample(4)updateCustomTextInputExample(5)
callback() tells to update the input on the server (R). If true, a rate policy is applied!
As mentioned before, callback(true)
will set rate policy for the given event listener. Setting a rate policy is relevant when we donโt want to flood the server with tons of update requests.
getRatePolicy: function() { return { // Can be 'debounce' or 'throttle' policy: 'debounce', delay: 500 };}
Below, the text input only updates after releasing the keyboard for 250ms.
updateCustomTextInputExample(6)
Boxes are a center element of {shinydashboard}
. Yet the latter only exploit 10% of their
capability. Your mission: unleash the AdminLTE API to deliver box on steroids! Open box_on_steroids.Rmd
.
20:00
A brief plan of this part of the workshop
Serve data as an HTTP response and read it in JavaScript to produce an HTML output.๐ฎ
Unleash-Shiny-Exercises-Part-3
RStudio Cloud.Unleash-Shiny-Exercises-Part-3
RStudio Cloud.Have Unleash-Shiny-Exercises-Part-3
ready, it will come in handy.
"Traditional" applications and websites
A new page is requested
/
, server responds/about
, server gives a different response/products
, server gives a yet a different responseand so on
๐ก Think {plumber}
So which one of those does shiny use?
Both!
But one (probably) more than the other...
When you visit a shiny app:
server
which responds with the initial ui
.input
, output
).No other /page
is ever visited in shiny
HTTP header ~ meta data: gives necessary information to the browser.
Importantly:
Status
- How is the response? 404
not found200
all good500
server-side error301
redirectContent-Type
- type of content ('text/html', 'application/json', etc.)Content
- content to renderThese are all standardized, we don't get to choose.
A websocket sends binary data so there is no need for headers.
path <- session$registerDataObj(name = "about", data, filterFunc)
Since the response if only valid for a single session the path returned is dynamically generated.
Example paths for different sessions:
session/0a6ed2556d97dcaa1ddeb615dc04cd1e/dataobj/about
session/259b8dc2f18ffc975b246d206b60073f/dataobj/about
session/72abd5a5da9a1651bee32d25a908a0cd/dataobj/about
What is this path
anywayโ
It's where the HTTP response is servedโ
path <- session$registerDataObj(name = "about", data = "<h1>Hello!</h1>", filterFunc)
The core of it all: it actually serves the HTTP response.
# filterFuncfunction(data, req){ shiny:::httpResponse( status = 200, # default content_type = "text/html", # default content = data )}
Note: httpResponse
is not exported, hence the :::
Minor changes to return JSON data.
path <- session$registerDataObj( name = "cars-data", data = cars, function(data, req) { # seralise to JSON res <- jsonlite::toJSON(data) shiny:::httpResponse( content_type = "application/json", content = res ) })
[ { "speed": 4, "dist": 2 }, { "speed": 4, "dist": 10 }]
Meet "box" our soon-to-be custom output โ
๐ bulma.io/documentation/layout/level
renderBox.R
(1)Internally all render*
functions return another function()
โ
It's a function! ๐
shiny::exprToFunction({ head(cars, 10)})
## function () ## {## head(cars, 10)## }
We can run it ๐
fn <- shiny::exprToFunction({ x <- 1 x + 3})fn()
## [1] 4
Shiny = Reactivity: functions are easier to rerun
output$something <-
you actually assign a function
โ
renderBox.R
(2)Great, render*
returns a function, but what about that one then ๐
renderSomething <- function(expr){ func <- shiny::exprToFunction(expr) # assigned to `output` function(){ data <- func() # sent to the JavaScript binding return(data + 1) }}
The result of render*
is a function, the result of which is sent to the JavaScript binding ๐ก
Eerily similar to inputs: 1) initialise, 2) extend, 3) register.
var boxBinding = new Shiny.OutputBinding();$.extend(boxBinding, { find: function(scope) { return $(scope).find(".box"); }, renderValue: function(el, data) { // Get the serialised JSON // Use it to render content }});Shiny.outputBindings.register(boxBinding, "pharma.box");
The renderValue
in the JavaScript binding the following:
function(el, data) { // httr::GET (1) fetch(data) // httr::content (2) .then(response => response.json()) // lapply | purrr::map (3) .then(data => { data.map((row)=>{ let div = createElement(row); $(el).append(div); }) })}
Args
el
the element (boxOutput
)data
the path sent by renderBox
Logic
level-item
<nav>
Numerous improvements could be made.
box
function could accept a model and extract relevant statisticsbox
accepts different kinds of objects (e.g.: xts
)Plenty more can be done!
ui <- fluidPage( textInput('txt_a', 'Input Text A'), textInput('txt_b', 'Input Text B'), uiOutput('txt_c_out'), verbatimTextOutput("show_last"))server <- function(input, output, session) { output$txt_c_out <- renderUI(textInput('txt_c', 'Input Text C')) values <- reactiveValues(lastUpdated = NULL) observe({ lapply(names(input), function(x) { observe({ input[[x]] values$lastUpdated <- x }) }) }) output$show_last <- renderPrint(values$lastUpdated)}
We leverage Shiny.setInputValue
and Shiny JS events. shiny:inputchanged
fires each time an input is changed!
$(document).on('shiny:inputchanged', function(event) { Shiny.setInputValue('pleaseStayHome', {name: event.name, value: event.value, type: event.binding.name.split('.')[1]});});
On the R side, we listen to input$pleaseStayHome
. That's it!
The {shinylogs}
package developed by dreamRs contains this feature:
shinyApp( ui = fluidPage( numericInput("n", "n", 1), sliderInput("s", "s", min = 0, max = 10, value = 5), verbatimTextOutput("lastChanged") ), server = function(input, output, session) { # specific to shinylogs track_usage(storage_mode = store_null()) output$lastChanged <- renderPrint(input$`.shinylogs_lastInput`) })
Don't forget that Shiny
is a JS object having the notifications.show
method!
We add onclick
to an actionButton
.
ui <- fluidPage( actionButton( "notif", "Show notification", onclick = "Shiny.notifications.show({ html: '<strong>Oups</strong>', type: 'error', duration: 2000 });" ))server <- function(input, output, session) {}shinyApp(ui, server)
Case study: we would like to modify the action button behavior on the fly.
Steps:
shiny:connected
eventShiny.inputBindings
registry bindAll
$(function() { $(document).on('shiny:connected', function(event) { Shiny.unbindAll(); $.extend(Shiny .inputBindings .bindingNames['shiny.actionButtonInput'] .binding, { // do whathever you want to edit existing methods }); Shiny.bindAll(); });});
What if you don't want to update input from the server? ๐คท
setValue
to add new valuesubscribe
(tells Shiny to update from the R side)$(function() { // each time we click on #test (a button) $('#test').on('click', function() { var $obj = $('#button'); var inputBinding = $obj.data('shiny-input-binding'); var val = $obj.data('val') || 0; inputBinding.setValue($obj, val + 10); $obj.trigger('click'); });});