Shiny Modules (part 3): Dynamic module call
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.
On the previous post “Share reactive among multiple modules” we showed how to send/get data to/from modules.
Now that toying with modules has no more secret for you. You may want to call one module multiple times without having to explicitly code it ?
On this article we will see how to call a module dynamically and how to manage its outputs from both server
and ui
side with 2 examples:
- Dynamic call to get
UI
output - Dynamic call to get both
UI
&server
outputs.
Want to run the examples ?
All code used in this post are available in Github ardata-fr/Shiny-Modules-Tutorials.
This repository is actually an R package containing all the modules. Applications are stored in the folder inst
.
To get apps locally, install the package and run applications:
# install.packages("remotes") remotes::install_github("ardata-fr/Shiny-Modules-Tutorials") library(shinyModulesTuto) # List available applications listEx() # Run first application runEx(listEx()[1])
Example 1: Get the UI
output
As seen in the previous article, your module is split into 2 funtions. The UI
side and the Server
logic.
In this example, the server
logic has no return. So we just need to handle the UI
output.
Online application https://ardata.shinyapps.io/dynamic-call/ or use command:
# Run Shiny application runEx("dynamic-call")
NB: On this example each UI
output is a shinyWidgets::panel
.
Initialize rv$all_ui
On the application the reactiveValues rv
is initialized as below:
rv <- reactiveValues(all_ui = list())
Dynamic call through observeEvent
Every time a dataset is loaded (eg: data_mod1$trigger
increments) the observeEvent
:
- launch the module
server
logic
callModule( module = data_info, id = data_mod1$trigger, data = data_mod1$data, data_name = data_mod1$data_name )
- Get the module
UI
output in the reactiverv$all_ui
rv$all_ui[[data_mod1$trigger]] <- data_infoUI(id = data_mod1$trigger)
Render UI
elements
Note that the UI
element returned by the module is a shinyWidgets::panel
.
Thus, we can use it in a simple tagList
function.
output$all_results <- renderUI({ tagList(rv$all_ui) })
Example 2: Get the UI
& server
outputs
Remember the example whole-app
on the previous article ?
You can still get it online here or use command:
# Run Shiny application runEx("whole-app")
For the next example, I created a module merge_modules
that contains all the modules used in whole-app
except the load_data
.
The idea here is to set only once the module load_data
and then call the module merge_modules
every time the user load a data:
Ce “super-module” merge_modules
retourne les parties UI
de tous les modules sous-jacents. Sa partie server
renvoi une reactiveValues
contenant le nom ainsi que le nombre de fonction appliqués sur chaque jeux de données chargés.
This module return every nested modules UI
and a reactiveValues
containing the name of dataset loaded & the number of functions applied.
Online application https://ardata.shinyapps.io/dynamic-call-whole-app/ or use command:
# Run Shiny application runEx("dynamic-call-whole-app")
NB: On this example we will use a tabsetPanel
, each UI
output will be stored inside a new tabPanel
. Moreover, each tabPanel
contains a close button inside its title.
Variables initialization
trick <- reactiveVal(0) res <- list() obs_close <- list()
- The
reactiveVal
trick
is an integer that incremente every time the user call the modulemerge_modules
. It is used as anid
for thetabPanel
and for listres
names. - The
list
res
contains eachserver logic
return of modulesmerge_modules
. - The
list
obs_close
containsobservers
that trigger to close atabPanel
.
Dynamic call through observeEvent
Every time a dataset is loaded (eg: data_mod1$trigger
increments) the observeEvent
:
- launch the module
server
logic and store result insideres
res[[paste(id)]] <<- callModule( module = merge_modules, id = id, data = data, name = name )
- Get the module
UI
output in a newtabPanel
:
appendTab( inputId = "all_tabs", tabPanel( title = tabTitle(name, id), value = id, tags$br(), merge_modulesUI(id = id) ), select = TRUE )
- Add to
obs_close
anobserver
to triggertabPanel
close:
obs_close[[paste(id)]] <<- observe({ shinyjs::onclick(id = paste0("close", id), closeTab(id = id)) })
- Incremente the
reactiveValue
trick
trick(trick() + 1)
Incrementing trick
will trigger the renderUI
: ui_summary
The reactiveVal
trick
is used to trigger an update in therenderUI
ui_summary
because the listres
isn’t reactive itself !
Use server
outputs in application
You can use the list res
elsewhere in your application. However don’t forget to reference the trick
reactiveVal
to be sure your reactive context updates.
# Summary of all opened tabs output$ui_summary <- renderUI({ # Reference trick to update reactive context fool <- trick() zz <- lapply(res, reactiveValuesToList) if (length(zz) == 0) { content <- div(class = "warn ", "No variable loaded") } else { # Use output of module merge_modules: # - name # - nb_funs content <- tags$ul( lapply(zz, function(x) { tags$li(paste(x$name, ":", x$nb_funs)) }) ) } tagList(content) })
Bonus: Close a tabPanel
In our example we can “revert” a dynamic module call. To do so, we need to undo what’s done in the previous chapter:
- Remove a
tabPanel
(UI
part). - Remove related element from list
res
(Server
part).
This 2 steps are part of the function closeTab
described below. This function is called in the observe
created dynamically (eg previous chapter):
closeTab <- function(id) { # Remove tab from UI removeTab(inputId = "all_tabs", target = paste(id)) # Remove related element from list res[[paste(id)]] <<- NULL # Increase trick to refresh output$ui_summary trick(trick() + 1) }
Note that the cross “button” inside the tabPanel
title is created with function below:
# Function to add a cross after tabPanel title tabTitle <- function(name, id) { tags$span( name, HTML(" "), tags$span( id = paste0("close", id), class = "close", HTML("×") ) ) }
Conclusion
Call dynamically your module is usefull when :
- the module can be called an indefinite number of times.
- the module is called through a user action.
The call must be done inside an observeEvent
or observe
and then:
If you need
server
output, store results inside a new element of a list.For
UI
there are 2 solutions:- Store
UI
output inside areactiveValues
(example 1). - Use
UI
output directly (example 2).
- Store
Be a shiny module master
Along trough our 3 articles we saw:
- Why using modules ?: Why and how to organize your Shiny application using modules.
- Share reactive among shiny modules: How to send/get data from/to modules.
- Call modules dynamically: Call a module dynamically and manage its output(s).
I hope these articles will help you to have fun developping awesome Shiny applications !
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.