Skip to contents

Introduction

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.code package.
  • A way extract from and merge related datasets using the teal.transform package.
  • 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:

Teal Duck

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.

Adding reporting to a module

Refer to vignette("adding_support_for_reporting") to read about adding support for reporting in your teal module.