bretauv
bretauv

Reputation: 8506

Access dataframes created through modules with their names and store them in a list

I am creating an app with modules, based on this answer. Basically, it is an app in which it is possible to create identical tabs with different input names, just by clicking on a tab called More.

Now, I would like to give the user the possibility to merge some (or all) of the tables created. To do so, there is a (permanent) tab called Merge in which there is a checkBoxInput. When no tab is created, this checkBoxInput is empty (since there are no tab and hence no table to select). When one tab and therefore one table are created, I would like the checkBoxInput to be updated to display a box and the name corresponding to this table. For example, if I create 3 tabs, then there should be 3 boxes in the checkBoxInput.

My idea so far was to store the tables created in a list and to update checkBoxInput with the content of this list each time a tab and a table are created. However, I don't know how to obtain the name of the tables created in a module. Since the tables are named with x in the module moduleTable, I thought I could just use x but it gives me the following error:

Warning: Error in observeEventHandler: object 'x' not found

Below is a reproducible example:

library(shiny)
library(shinyWidgets)
library(dplyr)

addTab <- function(id) {
  ns <- NS(id)
  tagList(
    selectInput(ns("select"),
                "Choose", 
                choices = colnames(mtcars)),
    tableOutput(ns("table"))
  )
}

moduleTable <- function(input, output, session){
  x <- reactive(select(mtcars, input$select))
  output$table <- renderTable({
    x()
  })
}


ui <- navbarPage(position = "static-top",
                 title = "foo",
                 id = "tabs",
                 tabPanel(title = "Merge",
                          fluidRow(
                            checkboxGroupInput("to_merge",
                                               label = "Tables to merge",
                                               choices = NULL)
                          )),
                 tabPanel(title = "More",
                          icon = icon("plus"),
                          fluidRow()
                          )
)

server <- function(input, output, session) {

  count <- reactiveValues(val=0)

  dfs <- list()

  observeEvent(input$tabs, {
    if (input$tabs == "More"){
      count$val <- count$val+1
      name <- paste0("Name ", count$val)
      insertTab(inputId = "tabs",
                tabPanel(title = name,
                         addTab(paste0("select", count$val))
                ), 
                target = "More", 
                position = "before",
                select = TRUE)

      callModule(moduleTable, paste0("select", count$val))

      dfs[[count$val]] <- paste0("mtcars$select", count$val)
      # UNCOMMENT THE LINE BELOW AND COMMENT THE LINE ABOVE TO SEE THE PROBLEM
      # tables[[count$val]] <- x

      names(dfs[count$val]) <- paste0("df", count$val)

      updateCheckboxGroupInput(session = session,
                               inputId = "to_merge",
                               choices = names(dfs))
    }
  })  
}

shinyApp(ui = ui, server = server)

How can I obtain the names of the dataframes created and store them in a reactive list?

Upvotes: 0

Views: 57

Answers (2)

raytong
raytong

Reputation: 51

You may try the following code to return the reactive expression and join the table.

library(shiny)
library(shinyWidgets)
library(tidyverse)

addTab <- function(id) {
  ns <- NS(id)
  tagList(
    selectInput(ns("select"),
                "Choose", 
                choices = colnames(mtcars[, -1])),
    tableOutput(ns("table"))
  )
}

moduleTable <- function(input, output, session){
  x <- reactive(select(mtcars, c(mpg, input$select)))
  output$table <- renderTable({
    x()
  })
  return(x)
}


ui <- navbarPage(position = "static-top",
                 title = "foo",
                 id = "tabs",
                 tabPanel(title = "Merge",
                          fluidRow(
                            checkboxGroupInput("to_merge",
                                               label = "Tables to merge",
                                               choices = NULL),
                            tableOutput("table")
                          )),
                 tabPanel(title = "More",
                          icon = icon("plus"),
                          fluidRow()
                 )
)

server <- function(input, output, session) {

  count <- reactiveValues(val=0)
  tables <- reactiveValues()

  dfs <- list()

  observeEvent(input$tabs, {
    if (input$tabs == "More"){
      count$val <- count$val+1
      name <- paste0("Name ", count$val)
      insertTab(inputId = "tabs",
                tabPanel(title = name,
                         addTab(paste0("select", count$val))
                ), 
                target = "More", 
                position = "before",
                select = TRUE)

      x <- callModule(moduleTable, paste0("select", count$val))
      tables[[name]] <- x
    }
  })

  observe({
    updateCheckboxGroupInput(session = session,
                             inputId = "to_merge",
                             choices = names(tables),
                             selected = input$to_merge)
  })

  observe({
    req(input$to_merge)
    output$table <- renderTable({
      if(!is.null(input$to_merge)) {
        tabs <- map(input$to_merge, ~{tables[[.x]]()})
        reduce(tabs, full_join)
      }
    })
  })
}

shinyApp(ui = ui, server = server)

Upvotes: 1

Valeri Voev
Valeri Voev

Reputation: 2242

UPDATE (getting a bit closer but not there yet):

I know that modules can return data (as reactiveValues). So my challenge now is to return the data in your x(). With some random non-reactive values, it works in the sense that I can collect (append) the output of a module call in a variable and then show these values as a string for example. Here is where I am:

library(shiny)
library(shinyWidgets)
library(dplyr)

addTab <- function(id) {
    ns <- NS(id)
    tagList(
        selectInput(ns("select"),
                    "Choose", 
                    choices = colnames(mtcars)),
        tableOutput(ns("table"))
    )
}

moduleTable <- function(input, output, session){
    x <- reactive(select(mtcars, input$select))
    output$table <- renderTable({
        x()
    })

    table_list <- reactiveValues()

    table_list$test <- sample(letters, 1)
    table_list$first_value <- x()[1,1]

    return(table_list)
}

ui <- navbarPage(position = "static-top",
                 title = "foo",
                 id = "tabs",
                 tabPanel(title = "Merge",
                    fluidRow(
                            checkboxGroupInput("to_merge",
                                               label = "Tables to merge",
                                               choices = NULL),
                            textOutput("string"),
                            textOutput("first_value")
                        )),
                    tabPanel(title = "More",
                             icon = icon("plus"),
                             fluidRow()
                    )
)

server <- function(input, output, session) {

    vals <- reactiveValues(val = 0, name = "", string_output = NULL, first_value = NULL)

    observeEvent(input$tabs, {
        if (input$tabs == "More"){
            vals$val <- vals$val+1
            name <- paste0("Name ", vals$val)
            insertTab(inputId = "tabs",
                                tabPanel(title = name,
                                         addTab(paste0("select", vals$val))
                                ), 
                                target = "More", 
                                position = "before",
                                select = TRUE)

            m_output <- callModule(moduleTable, paste0("select", vals$val))

            vals$string_output <- c(vals$string_output, m_output$test)
            vals$first_value <- c(vals$first_value, m_output$first_value)

            vals$name[vals$val] <- paste0("mtcars$select", vals$val, "_", m_output$test)

            updateCheckboxGroupInput(session = session,
                                     inputId = "to_merge",
                                     choices = vals$name)

            output$string <- renderText({
                req(input$tabs)
                paste(vals$string_output, collapse = ", ")  
            })

            output$first_value <- renderText({
                req(input$tabs)
                paste(vals$first_value, collapse = ", ")        # This doesn't work as expected 
            })

            }
    })  
    }

shinyApp(ui = ui, server = server)

How about this?


library(shiny)
library(shinyWidgets)
library(dplyr)

addTab <- function(id) {
    ns <- NS(id)
    tagList(
        selectInput(ns("select"),
                                "Choose", 
                                choices = colnames(mtcars)),
        tableOutput(ns("table"))
    )
}

moduleTable <- function(input, output, session){
    x <- reactive(select(mtcars, input$select))
    output$table <- renderTable({
        x()
    })
}

ui <- navbarPage(position = "static-top",
                                 title = "foo",
                                 id = "tabs",
                                 tabPanel(title = "Merge",
                                                 fluidRow(
                                                    checkboxGroupInput("to_merge",
                                                                                         label = "Tables to merge",
                                                                                         choices = NULL)
                                                 )),
                                 tabPanel(title = "More",
                                                 icon = icon("plus"),
                                                 fluidRow()
                                 )
)

server <- function(input, output, session) {

    vals <- reactiveValues(val=0, name = "")
    dfs <- list()

    observeEvent(input$tabs, {
        if (input$tabs == "More"){
            vals$val <- vals$val+1
            name <- paste0("Name ", vals$val)
            insertTab(inputId = "tabs",
                                tabPanel(title = name,
                                                 addTab(paste0("select", vals$val))
                                ), 
                                target = "More", 
                                position = "before",
                                select = TRUE)

            callModule(moduleTable, paste0("select", vals$val))

            vals$name[vals$val] <- paste0("mtcars$select", vals$val)

            print(vals$name)
            updateCheckboxGroupInput(session = session,
                                     inputId = "to_merge",
                                     choices = vals$name)
        }
    })  
}

shinyApp(ui = ui, server = server)

Upvotes: 1

Related Questions