Arguments from Module Manager
arguments_from_module_manager.Rmd
When developing a module, we create a wrapper function that returns a list that is used to instantiate a given module inside module manager. Within this list the server
is a function with a single parameter provided by module manager when the server function is called. This parameter by convention usually called afmm
, an acronym of arguments from module manager. afmm
is a list with the following entries:
data: a list of lists of data frames (or of functions that return data frames). Pass-through of the
run_app
argument by the same name.unfiltered_dataset: a metaReactive list containing the tables inside the selected dataset before filtering them.
filtered_dataset: a metaReactive list containing the tables inside the selected dataset after filtering them.
url_parameters: a reactive list of the parameters passed in the url.
dataset_name: a reactive string containing the name of the selected dataset. (Deprecated)
-
dataset_metadata: a list with the following entries:
- name: a reactive string with the name of the selected dataset
- date_range: a reactive character vector with two entries earliest and latest modification date in the dataset.
module_output: a function that when called returns a list of all the values returned by the different modules indexed by the module_id.
module_names: a non-reactive named list containing as values the names of the module entries as displayed on the tab and as names the ids of the module entries as used by Shiny.
-
utils: a list of convenience functions:
- switch2mod: a function that allows switching between tabs programatically.
- switch2: DEPRECATED a function that allows switching between tabs programatically.
Lets see some example recipes to use all these fields.
Firstly, we will declare a simple module that creates a table, and a dataset. These will be the basis for our examples.
# Module
table_ui <- function(id) {
ns <- shiny::NS(id)
shiny::tagList(
DT::DTOutput(ns("table"))
)
}
table_server <- function(id, dataset) {
shiny::moduleServer(
id,
function(input, output, session) {
output$table <- DT::renderDT(
{
dataset()
},
selection = "single"
)
}
)
}
Raw access
Accessing the un/filtered_dataset
Below we can see a wrapper that allow us to access the filtered dataset.
mod_table <- function(table_name, mod_id) {
mod <- list(
ui = table_ui,
server = function(afmm) {
table_server(id = mod_id, dataset = shiny::reactive(afmm[["filtered_dataset"]]()[[table_name]]))
},
module_id = mod_id
)
mod
}
data_list <- list(
"D1" = list(adsl = pharmaverseadam::adsl, adae = pharmaverseadam::adae),
"D2" = list(adsl = pharmaverseadam::adsl, adae = pharmaverseadam::adae)
)
module_list <- list(
"Table adsl" = mod_table(mod_id = "mod_1", "adsl"),
"Table adae" = mod_table(mod_id = "mod_1", "adae")
)
run_app(
data = data_list,
module_list = module_list,
filter_data = "adsl",
filter_key = "USUBJID"
)
When creating the module list we can select which of the tables inside the dataset we will access. Nonetheless, this is just a design choice of the module developer. See below another possible example in which we can select either the filtered or unfiltered dataset input by toggling the logical filtered
parameter.
mod_table <- function(table_name, filtered = FALSE, mod_id) {
mod <- list(
ui = table_ui,
server = function(afmm) {
if (filtered) {
source <- "filtered_dataset"
} else {
source <- "unfiltered_dataset"
}
table_server(id = mod_id, dataset = shiny::reactive(afmm[[source]]()[[table_name]]))
},
module_id = mod_id
)
mod
}
data_list <- list("D1" = list(adsl = pharmaverseadam::adsl, adae = pharmaverseadam::adae))
module_list <- list(
"Table adsl" = mod_table(mod_id = "mod_1", "adsl"),
"Table adae" = mod_table(mod_id = "mod_1", "adae")
)
run_app(
data = data_list,
module_list = module_list,
filter_data = "adsl",
filter_key = "USUBJID"
)
A note on reactives
The call to afmm is included in a reactive, because afmm[["unfiltered_dataset]]
and afmm[["filtered_dataset]]
are metaReactives, therefore, they can only be accessed inside a reactive environment. Also note the parenthesis resolving the reactive before doing the subsetting. This does not differ from usual reactive programming.
A note on metaReactives In this example we are using simple reactive to not overwhelm readers with no experience using the shinymeta
package. To make use of the export functionality provided in modulemanager, this reactive should be replaced by shinymeta::metaReactive
. Please see vignette("export_func_dev")
for a longer explanation on metaReactivity that escapes the scope of this vignette.
Accessing dataset_name
Find below an example on how to access the dataset_name
dataset_name_UI <- function(id) { # nolint
ns <- shiny::NS(id)
shiny::tagList(
shiny::textOutput(ns("text"))
)
}
dataset_name_server <- function(id, dataset_name) {
shiny::moduleServer(
id,
function(input, output, session) {
output$text <- shiny::renderText({
dataset_name()
})
}
)
}
mod_dataset_name <- function(module_id) {
mod <- list(
ui = dataset_name_UI,
server = function(afmm) {
dataset_name_server(module_id, afmm[["dataset_metadata"]][["name"]])
},
module_id = module_id
)
mod
}
run_app(
data = data_list,
module_list = list(
"Dataset Name" = mod_dataset_name("mod1")
),
filter_data = "adsl",
filter_key = "USUBJID"
)
Note how in this case we did not manipulate the dataset_name and therefore it is not included in a reactive call, as it can be passed “as is”.
Accessing the module_output
afmm[["module_output"]]
behavior slightly differs from the other entries in the afmm
list. In this case it is a function that when called returns a list containing all the values returned by the different modules indexed by the module_id
. Returning a function call instead of a list is based on the fact that we are passing as an argument something that is not yet created, as we are creating the list at the same time we are calling the server functions.
This also forces us to wrap all afmm[["module_output"]]
calls in a metaReactive/reactive call, regardless of the object used being a reactive or not, as we need to delay the evaluation until the app has started.
Lets see an example of this. In this case we have created a module that receives a value and displays it. In this case we will receive that value from another module.
com_UI <- function(id, choices = c(1, 2, 3), message) { # nolint
ns <- shiny::NS(id)
shiny::tagList(
shiny::selectizeInput(ns("select"), label = "Select a number", choices = choices),
shiny::p(message),
shiny::textOutput(ns("output"))
)
}
com_server <- function(id, value) {
module <- function(input, output, session) {
output$output <- shiny::renderText({
shiny::req(value())
})
return(shiny::reactive(input$select))
}
return(
shiny::moduleServer(
id,
module
)
)
}
mod_com_test <- function(choices, message, value, mod_id) {
mod <- list(
ui = function(id) {
com_UI(id, choices, message)
},
server = function(afmm) {
com_server(id = mod_id, value = shiny::reactive(afmm[["module_output"]]()[[value]]))
},
module_id = mod_id
)
mod
}
run_app(
data = list(),
module_list = list(
"Send and Receive 1" = mod_com_test(
choices = 1:3,
message = "The other module has selected",
value = "mod_2",
mod_id = "mod_1"
),
"Send and Receive 2" = mod_com_test(
choices = c("a", "b", "c"),
message = "The other module has selected",
value = "mod_1",
mod_id = "mod_2"
)
),
filter_data = ""
)
See how, we wrap everything in a reactive, make the call to module_output and then resolve the reactive once we have subsetted. Note that it is the module/app developer how to treat that output value.
Accessing util functions
The utils
field contains a list of utility functions for actions that need module manager. In this case the access is the simplest of all cases as it is just a list of functions.
switch2mod
switch2
allows switching from tab to another in a programmatic manner.
########### Switch module
switch_UI <- function(id, name) { # nolint
ns <- shiny::NS(id)
shiny::tagList(
shiny::h1(name),
shiny::actionButton(ns("switch"), "Switch tab")
)
}
switch_server <- function(id, selected, switch_func) {
shiny::moduleServer(
id,
function(input, output, session) {
shiny::observeEvent(input$switch, {
switch_func(selected)
})
}
)
}
mod_switch <- function(name, selected, module_id) {
mod <- list(
ui = function(module_id) {
switch_UI(module_id, name)
},
server = function(afmm) {
switch_server(module_id, selected, afmm[["utils"]][["switch2mod"]])
},
module_id = module_id
)
mod
}
run_app(
data = list(),
module_list = list(
"Mod 1" = mod_switch("Mod 1", "mod2", "mod1"),
"Mod 2" = mod_switch("Mod 2", "mod1", "mod2")
),
filter_data = "",
filter_key = "USUBJID"
)
Notice that as opposed to its predecessor switch2
, switch2mod
uses the module_id and not the name of the module tabs.
Dispatchers
Module manager include a couple of convenience functions that deal with all the nuances described providing a uniform of accessing all fields above.
Lets see it with our first example.
mod_table <- function(dataset, mod_id) {
mod <- list(
ui = table_ui,
server = function(afmm) {
table_server(id = mod_id, dataset = mm_resolve_dispatcher(dataset, afmm))
},
module_id = mod_id
)
mod
}
data_list <- list(
"D1" = list(adsl = pharmaverseadam::adsl, adae = pharmaverseadam::adae),
"D2" = list(adsl = pharmaverseadam::adsl, adae = pharmaverseadam::adae)
)
module_list <- list(
"Table adsl" = mod_table(mod_id = "mod_1", mm_dispatch("filtered_dataset", "adsl")),
"Table adae" = mod_table(mod_id = "mod_2", mm_dispatch("filtered_dataset", "adsl"))
)
run_app(
data = data_list,
module_list = module_list,
filter_data = "adsl",
filter_key = "USUBJID"
)
In this case we let mm_resolve_dispatcher
take care of all the nuances, including reactive or metareactive nature of the inputs, for us.
This also allows to quickly change the source without modifying the wrapper function.
module_list <- list(
"Table adsl" = mod_table(mod_id = "mod_1", mm_dispatch("unfiltered_dataset", "adsl")),
"Table adae" = mod_table(mod_id = "mod_2", mm_dispatch("module_output", "mod_return_table")),
"Return table" = mod_return_table(..., mod_id = "mod_return_table"),
"Table independent data" = mod_table(mod_id = "mod_indp", mtcars),
)
In the above example we can quickly change the source just by changing the first parameter of the dispatcher. Or even pass an argument non related to afmm
as mm_resolve_dispatcher
will ignore objects that do not come from an mm_dispatch
function.
This dispatcher also works for utils
given the proper adjustments in the wrapper:
module_list <- list(
"Mod 1" = mod_switch("Mod 1", "Mod 2", mm_dispatch("utils", "switch2"), "mod1"),
"Mod 2" = mod_switch("Mod 2", "Mod 1", mm_dispatch("utils", "switch2"), "mod2")
)