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-toggleAttributes 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"]] <- valuetagAppendChild(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 tab
On 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/aboutsession/259b8dc2f18ffc975b246d206b60073f/dataobj/aboutsession/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] 4Shiny = 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 renderBoxLogic
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-toggleAttributes 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"]] <- valuetagAppendChild(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 tab
On 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/aboutsession/259b8dc2f18ffc975b246d206b60073f/dataobj/aboutsession/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] 4Shiny = 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 renderBoxLogic
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'); });});