Creating Custom Modules
Nikolas Burkoff
2022-03-24
creating-custom-modules.RmdIntroduction
The teal framework provides a large number of analysis
modules to be incorporated into teal applications. However,
it is also possible to create your own modules using the
module() function.
Here is an implementation of a simple module:
library(teal)
example_module <- function(label = "example teal module") {
checkmate::assert_string(label)
module(
label,
server = function(id, datasets) {
moduleServer(id, function(input, output, session) {
output$text <- renderPrint(datasets$get_data(input$dataname, filtered = TRUE))
})
},
ui = function(id, datasets) {
ns <- NS(id)
teal.widgets::standard_layout(
output = verbatimTextOutput(ns("text")),
encoding = selectInput(ns("dataname"), "Choose a dataset", choices = datasets$datanames())
)
},
filters = "all"
)
}which can be added into teal apps using
example_module(label = "Label for tab").
Components of a module
UI function
This function contains the UI required for the module. It should be a
function with at least the arguments id and
datasets. It can contain standard UI components alongside
additional widgets provided by the teal.widgets package. In
the example above we are using the standard_layout function
of teal.widgets which generates a layout including an
encoding panel on the left and main output covering the rest of the
module’s UI.
Server function
This function contains the shiny server logic for the module and should be of the form:
function(id, datasets, ...) {
moduleServer(id, function(input, output, session) {
# module code here
})
}
The datasets argument is a FilteredData
object which provides a call to extract the data after having been
filtered by the filterpanel:
datasets$get_data(<<dataname>>, filtered = TRUE).
For more details about the FilteredData object see the
supporting teal package teal.slice
A More Complicated Example
The teal framework also provides:
- A way to create modules which the generate the R code needed to
reproduce their outputs; these modules use the
teal.codepackage. - A way extract from and merge related datasets using the
teal.transformpackage. - A way to allow app creators to customize your modules also using
teal.transform.
The annotated example below shows these features inside a simple histogram module where app developers can choose what data and which columns from them, their app users can select to be displayed as a histogram.
See the package and function documentation for further details.
library(teal)
# ui function for the module
# histogram_var is a teal.transform::data_extract_spec object
# specifying which columns of which datasets users can choose
ui_histogram_example <- function(id, datasets, histogram_var) {
ns <- NS(id)
teal.widgets::standard_layout(
output = plotOutput(ns("plot")),
encoding = div(
teal.transform::data_extract_ui(
id = ns("histogram_var"),
label = "Variable",
data_extract_spec = histogram_var
)
),
# we have a show R code button to show the code needed
# to generate the histogram
forms = teal::get_rcode_ui(ns("rcode"))
)
}
# server function for the module
# histogram_var is a teal.transform::data_extract_spec object
# specifying which columns of which datasets users can choose
srv_histogram_example <- function(id, datasets, histogram_var) {
moduleServer(id, function(input, output, session) {
# initialize the reproducibility part of teal (i.e. "chunks")
teal.code::init_chunks()
# get the selected dataset and column from the UI
extracted <- teal.transform::data_extract_srv("histogram_var", datasets, histogram_var)
dataname <- reactive(extracted()$dataname)
selected <- reactive(extracted()$select)
# the reactive which adds the code to plot the histogram into the chunks
plot_code <- reactive({
validate(need(length(selected) == 1, "Please select a variable"))
# take the filtered data from the datasets object and add it into the chunks environment
new_env <- new.env()
assign(dataname(), datasets$get_data(dataname(), filtered = TRUE), envir = new_env)
teal.code::chunks_reset(envir = new_env)
# add the code for the plot into the chunks
teal.code::chunks_push(
bquote(hist(.(as.name(dataname()))[, .(selected())]))
)
# evaluate the chunks
teal.code::chunks_safe_eval()
})
# shiny component to view
output$plot <- renderPlot({
plot_code()
})
# Show the R code when user clicks 'Show R Code' button
teal::get_rcode_srv(
id = "rcode",
datasets = datasets,
modal_title = "R code for custom plot",
code_header = "R code for custom plot"
)
})
}
# the function which creates the teal module for users
tm_histogram_example <- function(label, histogram_var) {
checkmate::assert_character(label)
checkmate::assert_class(histogram_var, "data_extract_spec")
module(
label = label,
server = srv_histogram_example,
ui = ui_histogram_example,
ui_args = list(histogram_var = histogram_var),
server_args = list(histogram_var = histogram_var),
filters = "all"
)
}An example teal application using this module is shown
below:

library(teal)
app <- init(
data = teal_data(
dataset("IRIS", iris, code = "IRIS <- iris"),
check = TRUE
),
modules = modules(
tm_histogram_example(
label = "Simple Module",
histogram_var = data_extract_spec(
dataname = "IRIS",
select = select_spec(
choices = c("Sepal.Length", "Sepal.Width", "Petal.Length", "Petal.Width")
)
)
)
),
header = "Simple app with custom histogram module"
)
if (interactive()) {
shinyApp(app$ui, app$server)
}
shiny input cycle
When teal modules are run inside the teal::init the
initial shiny input cycle is empty for each of them. In practice, this
means that some inputs might be initialized with NULL
value, unnecessary triggering some observers. A developer has to be
aware of this situation as often It will require shiny::req
or ignoreInit argument in observers or
reactive expressions. This side effect is caused by the
shiny::insertUI function. We are aware of this
inconvenience and have already started to look for a solution.