shinyEvents: build shiny apps with event handlers
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.
RStudio's shiny is a great framework to generate web applications with R. In a classical shiny app, interactivity is not generated via event handlers but by reactive programming
. For details, see the shiny documentation and tutorials under http://shiny.rstudio.com/.
While shiny's reactive programming model is great for smaller apps, I personally found it less useful for bigger applications that create a lot of interactive dynamic UI.
For example, when writing the initial shiny interface for my package RTutor https://github.com/skranz/RTutor, I felt that some observers or render functions were triggered too frequently, and I was not sure where to best put the 'server code' of newly dynamically created objects. Of course, it is definitely possible to write large applications with reactivity, but given my limited understanding of the reactivity model, it just was hard for me…
Anyway, I generated the package shinyEvents
to emulate the classical event-handling paradigm for shiny applications and find it personally quite useful…
The shinyEvents package allows to write shiny applications that use classical event handlers, e.g. for button clicks, value changes, etc. One does not write an explicit server function, but just adds event handlers to an app
object. Widgets will be updated with explicit calls to updateXXX or setXXXX functions, like e.g. setText(id, "New text")
. Widget values and event handlers can be set in a similar fashion for an app that has not yet started as for an already running app.
Installation
To install the package see the description on the package's Github page:
https://github.com/skranz/shinyEvents
Examples
A simple static app
Here is a simple example app.
library(shinyEvents) # Create a new eventsApp app = eventsApp() # ui app$ui = fluidPage( actionButton("plotBtn", "plot"), selectInput("mySelect", "Select:", c("Cylinders" = "cyl", "Transmission" = "am", "Gears" = "gear") ), textOutput("myText"), plotOutput("myPlot") ) # Handler for the plot button buttonHandler("plotBtn", function(session, id, value, app,...) { setText("myText", paste0("You pressed the button ",id," ", value," times. ")) setPlot("myPlot", plot(runif(10), runif(10))) }) # Handler for change of an input value changeHandler("mySelect", function(id, value,...) { setText("myText",paste0("You chose the list item ", value,". ", "A random number: ", sample(1:1000,1))) }) # Set an initial text setText("myText","This is the start text...") # Directly launch the events app in the viewer pane runEventsApp(app,launch.browser=rstudio::viewer)
Note that a call to eventsApp()
stores the generated app object (an environment) globally. The calls to buttonHandler, changeHandler and setText reference by default to this globally stored app object. Once the app starts, a copy of the app object will be generated for each user session that is generated by shiny.
Such a simple app app could be much easier written with the standard reactivity model of shiny. Yet, shinyEvents can become more useful when you have an app that creates a lot of dynamic UI.
A simple app with dynamic UI
Here is a simple app that creates dynamic UI.
# Create a new eventsApp app = eventsApp() # main ui app$ui = fluidPage( actionButton("uiBtn", "make dynamic ui"), textOutput("myText"), uiOutput('myUI') ) # Dynamically create UI with button and add handler for it buttonHandler("uiBtn", function(session, value,...) { # Set a new dynamic UI dynUI= fluidRow( actionButton("dynBtn", paste0("Dynamic button ",value)) ) setUI("myUI", dynUI) # Add handler for the new dynBtn in the new UI. # Existing handlers for dynBtn are by default replaced buttonHandler("dynBtn", ui.count = value, function(value,ui.count,...) { setText("myText", paste0( "UI was created ", ui.count, " times.n", "Dynamic button pressed ", value, " times.")) }) }) # Directly launch the events app in the viewer pane runEventsApp(app,launch.browser=rstudio::viewer)
The button handler for the static button creates and sets a new UI with another button and also generates a handler for the new button.
Note:
The syntax for creating handlers (and setting values) stays the same for dynamic objects created by an already running app as for static objects that are created before the app has started.
The dynamically created button Handler
buttonHandler("dynBtn", ui.count = value, function(value,ui.count...) {
passes a manual parameter ui.count to the handler function.
A small chat app
The code below generates a small chat application as a shiny events app in which multiple users can interact. Open multiple browser windows to see how chatting among multiple clients works.
library(shinyEvents) library(shinyAce) app = eventsApp() # app$glob can contain "global" variables that are visible # for all sessions. # app$glob$txt will be the content of the chat window app$glob$txt = "Conversation so far" app$ui = fluidPage( textInput("userName","User Name",""), # Chat window aceEditor("convAce",value = app$glob$txt, height="200px", showLineNumbers = FALSE, debounce=100), # Enter new text aceEditor("enterAce",value = "Your text",height="30px", showLineNumbers = FALSE,debounce = 100, hotkeys = list(addTextKey="Ctrl-Enter")), actionButton("addBtn", "add") ) addChatText = function(session,app,...) { restore.point("addChatText") user = getInputValue("userName") str = getInputValue("enterAce") app$glob$txt = paste0(app$glob$txt,"n",user, ": ",paste0(str,collapse="n")) updateAceEditor(session,"convAce", value = app$glob$txt) updateAceEditor(session,"enterAce", value = " ") } # Add chat text when button or Ctrl-Enter is pressed buttonHandler(id="addBtn",addChatText) aceHotkeyHandler("addTextKey",addChatText) # refresh chat window each second timerHandler("refreshChatWindow",1000, function(session,app,...) { txt = getInputValue("convAce") if (!identical(txt, app$glob$txt)) { cat("Refresh chat window...") updateAceEditor(session, "convAce", value = app$glob$txt) } }) # Initialize each new session with a random user name appInitHandler(function(session,app,...) { updateTextInput(session,"userName", value=paste0("guest", sample.int(10000,1)) ) updateAceEditor(session,editorId = "convAce",value = app$glob$txt) }) runEventsApp(app, launch.browser=TRUE) # To test chat function, open several browser tabs
We use some new handlers in this example:
aceHotkeyHandler(...)
can handle hotkeys in an aceEditor inputtimerHandler(...)
specifies a function that will be called in fixed time intervalsappInitHandler(...)
specifies a function to customize a newly initiated session of the app
The app object has a field glob
that can be used to store variables that will be shared among sessions. (Of course, you could alternatively just use a global variable directly in R.)
Deploying as a shiny app
The example run the generated app locally. Of course you can also deploy an event app via shiny server. Just generate in the usual fashion an app folder with files ui.R
, server.R
, and global.R
.
I would recommend to ui.R and server.R to be the following one-liners:
# ui.R shinyUI(app$ui)
and
# server.R shinyServer(app$server)
The generation of the app can then be put into global.R. If our first example, we would put into global.R:
# global.R for simply shiny events app library(shinyEvents) # Create a new eventsApp app = eventsApp() # ui app$ui = fluidPage( actionButton("plotBtn", "plot"), selectInput("mySelect", "Select:", c("Cylinders" = "cyl", "Transmission" = "am", "Gears" = "gear") ), textOutput("myText"), plotOutput("myPlot") ) # Handler for the plot button buttonHandler("plotBtn", function(session, id, value, app,...) { setText("myText", paste0("You pressed the button ",id," ", value," times. ")) setPlot("myPlot", plot(runif(10), runif(10))) }) # Handler for change of an input value changeHandler("mySelect", function(id, value,...) { setText("myText",paste0("You chose the list item ", value,". ", "A random number: ", sample(1:1000,1))) }) # Set an initial text setText("myText","This is the start text...")
R-bloggers.com offers daily e-mail updates about R news and tutorials about learning R and many other topics. Click here if you're looking to post or find an R/data-science job.
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.