Skip to contents

Before starting to develop a DaVinci package you should be familiar with how it works as explained in vignette("how_does_work").

Developing a new module

Developing modules compatible with the dv.manager follow a very similar process as developing them in pure Shiny. The only consideration we must have is that:

  1. After programming the module, we must include a “wrapper” function compatible with dv.manager requirements. You can think of this as an adapter so that your module plays nicely with ours. These requirements are explained in this vignette.
  2. dv.manager is prepared to work with ADAM or SDTM datasets and will be filtered using the a common key across all datasets specified by the user, by default “USUBJID”, see vignette("data_filtering").

1. Program a module with Shiny

First we will program a Shiny module in the usual way (see: Modularizing Shiny app code):

For this example, we will create a module that displays the number of rows in the data set as well as the number of unique entries in a variable that is selected by the user in a drop-down menu.

Of course, this module is not very useful, but for the purposes of explaining how dv.manager works, it will suffice.

library(shiny)

module_ui <- function(id, column_names) {
  ns <- NS(id) # this line is standard in all shiny modules

  tagList(
    textOutput(ns("num_rows")),
    selectInput(ns("col_select"), "Select a column", choices = column_names),
    textOutput(ns("num_unique")),
  )
}

module_server <- function(id, dataset) {
  module <- function(input, output, session) {
    output$num_rows <- renderText({
      paste("Number of rows:", nrow(dataset()))
    })

    output$num_unique <- renderText({
      paste(
        "There are",
        length(unique(dataset()[[input$col_select]])),
        "unique values in",
        input$col_select
      )
    })
  }

  return(
    moduleServer(id, module)
  )
}

In the code chunk below, notice how we wrap the data set in a reactive expression, this is done because our module expect the dataset to be a reactive value. As it will be the case inside module.manager.

We could run this module in an application without using dv.manager.

dataset <- pharmaverseadam::adsl

ui <- fluidPage(
  module_ui("my_module", names(dataset))
)

server <- function(input, output, session) {
  module_server(
    "my_module",
    reactive(dataset)
  )
}

shinyApp(ui, server)

2. Wrap this module

To use this module inside dv.manager we must first wrap it in a function that will receive:

  1. All parameters needed to start the module
  2. A module id

And it will return a list with the following fields:

  1. ui: A function that will be invoked to create the UI
  2. server: A one argument function that will call the module server
  3. module_id

We name the function mod_uniq_values to indicate that it is a module and what it does.

mod_uniq_values <- function(table_name, column_names, module_id) {
  mod <- list(

    # UI function
    ui = function(mod_id) {
      module_ui(mod_id, column_names)
    },

    # Server function
    server = function(afmm) {
      module_server(
        module_id,
        dataset = reactive(afmm[["filtered_dataset"]]()[[table_name]])
      )
    },
    # Module ID
    module_id = module_id
  )
  return(mod)
}

The function defined in the server element will be later evaluated by module manager and it will receive a set of arguments as defined explained in vignette("arguments_from_module_manager").

dataset <- list(adsl = pharmaverseadam::adsl)
module_list <- list("First module" = mod_uniq_values(
  "adsl",
  c("USUBJID", "AGE"),
  module_id = "mod1"
))
dv.manager::run_app(
  data = list("DS" = dataset),
  module_list = module_list,
  filter_data = "adsl"
)

Also notice how dv.manager provides us with a dv.filter on the left-hand side and a module selection bar on the top. These come standard with every application created using dv.manager.

An alternative way of developing this example module

We can pass not only one data set, but a list of them to the module and update the inputs in the UI based on this list.

Notice that in this case, the only parameter in the UI function is the ID. The selectInput UI elements are defined, but the choices option is not specified. These choices will be updated at run-time in the server function.

library(shiny)

module_ui <- function(id) {
  ns <- NS(id)
  tagList(
    textOutput(ns("num_rows")),
    selectInput(ns("data_select"), "Select a dataset", choices = NULL),
    selectInput(ns("col_select"), "Select a column", choices = NULL),
    textOutput(ns("num_unique")),
  )
}

Here, two observeEvent functions have been added that will update the choices in the input. The first will update the names in the dataset_list. The second will update the names of the columns in the selected data set.

Here two observeEvent has been added that will update the choices in the input to the:

  • names in the dataset_list

  • names of the columns in the selected dataset

module_server <- function(id, dataset_list) {
  module <- function(input, output, session) {
    observeEvent(dataset_list(),
      {
        updateSelectInput(
          inputId = "data_select",
          choices = names(dataset_list())
        )
      },
      once = TRUE
    )

    observeEvent(input$data_select, {
      updateSelectInput(
        inputId = "col_select",
        choices = names(dataset_list()[[input$data_select]])
      )
    })

    output$num_rows <- renderText({
      paste(
        "Number of rows:",
        nrow(dataset_list()[[input$data_select]][[input$col_select]])
      )
    })

    output$num_unique <- renderText({
      paste(
        "There are",
        length(unique(dataset_list()[[input$data_select]][[input$col_select]])),
        "unique values in",
        input$col_select
      )
    })
  }

  return(
    moduleServer(id, module)
  )
}

Next, we modify the wrapper that allows our example module to be used by dv.manager.

First, we remove the extra parameter from the UI function. Notice that this allows us to directly pass the function name instead of wrapping it in a anonymous function (see previous example).

Then, we pass the whole filtered_dataset list to the dataset parameter in the server function because we are no longer specifying just one data set - in this way, we can allow the user to select the data set inside the application at run-time.

This allows us to remove all parameters from the mod_uniq_values() function definition with the exception of module_id.

# Now, only the module_id parameter is needed
mod_uniq_values <- function(module_id) {
  mod <- list(
    ui = module_ui,
    server = function(afmm) {
      module_server(
        module_id,
        dataset = afmm[["filtered_dataset"]]
      )
    },
    module_id = module_id
  )
  mod
}

Finally, we set up dv.manager in a similar way, but now we have modified the call in module_list and added a new data set to the data set list.

datasets <- list(
  adsl = pharmaverseadam::adsl,
  adae = pharmaverseadam::adae
)

module_list <- list(
  "Row counter" = mod_uniq_values(module_id = "mod1")
)

dv.manager::run_app(
  data = list("DS1" = datasets),
  module_list = module_list,
  filter_data = "adsl"
)

Please, notice that the module resets its interface whenever we change the data table. This occurs because we observe a change in the dataset. A different technique, that escapes the scope of this vignette, should be used to control this issue.