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 !
Follow us: - Recommanded sites: R-bloggers R weekly Twitter #rstats Jobs for R-users