---
title: "Transform Module Output"
author: "NEST CoreDev"
output:
rmarkdown::html_vignette:
toc: true
vignette: >
%\VignetteIndexEntry{Transform Module Output}
%\VignetteEngine{knitr::rmarkdown}
%\VignetteEncoding{UTF-8}
---
```{r setup, include=FALSE}
library(teal)
library(ggplot2)
```
## Introduction
The outputs produced by `teal` modules, like graphs or tables, are created by the module developer and look a certain way.
It is hard to design an output that will satisfy every possible user, so the form of the output should be considered a default value that can be customized.
In [Transform Input Data](transform-input-data.html) we described how `teal_module`'s input data can be modified using `teal_transform_module`. Here we present how to utilize `teal_transform_module` to modify an output created by a `teal_module`, enabling you to tailor outputs to your specific requirements without rewriting the original module code.
## How to Transform outputs?
Custom transformations for the output objects can be created with `teal_transform_module()` and thus they are `shiny` modules.
They are passed to `teal_module` constructors as arguments (see below).
Their server logic will be used to modify objects such as plots or tables that exist in the server function of a `teal_module`.
A `ui` function can provide interactivity but that is optional, an app developer is free to transform outputs objects of a `teal` module that do not require user input.
### Requirements and Limitations
Transforming `teal` module output requires the following:
1. **Module Support**:
`teal` will apply transformations to `teal_module` outputs, but the module in question must explicitly support this functionality.
It is the responsibility of to the module developer to accept and consume the list of `teal_transform_module`.
2. **Matching Object Names**:
Transformations have to reference variables that already exist in the `teal_module` server function and therefore must use the appropriate variable names.
Think of it as extending the plot/table code that already exists in the module.
Module developers are encouraged to provide the relevant names in the module's documentation, otherwise the person writing the output transformation must follow the source code.
3. **Maintaining Object Classes**:
A transformation must not alter the class of the object that it modifies.
This is because a different class may require a different rendering function and that is part of the module structure, which beyond the control of decorators.
If change of this magnitude is required, it is recommended to create a new module.
## Building Output Transformations (Decorators)
For simplicity, we will refer to the output transformers as **decorators** in the code examples below.
### Server
Here we create a simple transformator that does not provide any user input.
Knowing that the module contains an object of class `ggplot2` named `plot`, we will modify its title and x-axis title:
```{r static_decorator}
static_decorator <- teal_transform_module(
label = "Static decorator",
server = function(id, data) {
moduleServer(id, function(input, output, session) {
reactive({
req(data())
within(data(), {
plot <- plot +
ggtitle("This is a better title") +
xlab("the real x axis")
})
})
})
}
)
```
### UI
If the transformation requires a user input, a `ui` function can be added.
Here, the x-axis title is obtained from a `textInput` widget, giving the user some flexibility.
Note how the input values are passed to the `within()` function using its `...` argument.
See `?teal.code::within.qenv` for more examples.
```{r interactive_decorator}
interactive_decorator <- teal_transform_module(
label = "Interactive decorator",
ui = function(id) {
ns <- NS(id)
div(
textInput(ns("x_axis_title"), "X axis title", value = "the suggested x axis")
)
},
server = function(id, data) {
moduleServer(id, function(input, output, session) {
reactive({
req(data())
within(data(),
{
plot <- plot +
ggtitle("This is a better title") +
xlab(my_title)
},
my_title = input$x_axis_title
)
})
})
}
)
```
### Variable Names as Arguments
The server function of a transforming `teal_transform_module` must conform to the names of the variables that exist in the server function of the transformed `teal_module`.
Writing a universal transformator that applies to any module is impossible because different modules may use different variable names for their output elements.
It is possible, however, to create a transformator that will take the relevant variable names as arguments.
Here, the `output_name` variable name is passed to a transformator, allowing it to work with multiple modules.
```{r dynamic_decorator}
dynamic_decorator <- function(output_name) {
teal_transform_module(
label = "Dynamic decorator",
ui = function(id) {
ns <- NS(id)
div(
textInput(ns("x_axis_title"), "X axis title", value = "the syggested x axis")
)
},
server = function(id, data) {
moduleServer(id, function(input, output, session) {
reactive({
req(data())
within(data(),
{
output_name <- output_name +
xlab(x_axis_title)
},
output_name = as.name(output_name),
x_axis_title = input$x_axis_title
)
})
})
}
)
}
```
Note that when the function is used, `output_name` will be passed a character string but the expression passed to `within` needs a `name`/`symbol`, a language object, hence the argument value must be converted to a `name`.
## Using Output Transformations (Decorators)
Transformations are applied to a `teal` module as follows:
1. A list of transformations is passed to the module constructor function (_e.g._ `tm_my_module`).
2. The module constructor calls the module generator function (`teal::module`) and passes the transformations to the `ui_args` and `server_args` arguments.
3. The module functions, UI and server, take a list of transformations as arguments and resolve them using `ui_transform_teal_data` and `srv_transform_teal_data`, respectively.
Here is a minimal illustration:
```{r pseudo_module, eval = FALSE}
# styler: off
pseudo_decorated_module <- function(
label = "Pseudo Module with Decorator Support",
decorators = list() # <--- added block (1)
) {
module(
label = label,
ui_args = list(decorators = decorators), # <--- added block (2)
server_args = list(decorators = decorators), # <--- added block (2)
ui = function(id, decorators) {
ns <- NS(id)
div(
# ,
#