Search code examples
rshinyshinymodules

Shiny modules: switch tabs from within modules that have different namespaces


I have a shiny app with multiple tabs, and I would like to have action buttons within the tabs that allow the user to switch tabs. I previously asked this question: R Shiny: Change tabs from within a module and got an answer that helped, but didn't completely solve my problem.

When I use the same id (tab.1) to call modtab1 and modtab2, it allows me to switch tabs, but it doesn't distinguish between the two input$userids; when I use different id's it distinguishes between the two input$userids but doesn't allow me to switch tabs.

library(shiny)
modtab1_ui <- function(id) {
  ns <- NS(id)
  tabPanel(title = 'Tab 1',
           value = NS(id, 'tab.1'),
           h4('This is the first tab'),
           actionButton(NS(id, 'nexttab'), 'Next Tab'),

           textInput(NS(id, 'userid'), 'User ID'),
           textOutput(outputId = NS(id, 'id'))
          ) # tabPanel
}

modtab1_server <- function(id) {
  moduleServer(id,
               function(input, output, session) {
                 observeEvent(input$nexttab, {
                   print(paste('switching to tab 2', input$userid))
                   updateTabsetPanel(session = session, inputId =  'tabs', 
                                     # selected = NS('tab2', 'tab.2')
                                     # selected = 'tab.2'
                                     selected = ns('tab.2')
                                    )
                 })
                 
                 output$id <- renderText(input$userid)
               })
}

modtab2_ui <- function(id) {
  ns <- NS(id)
  tabPanel(title = 'Tab 2',
           value = NS(id, 'tab.2'),

           h4('This is the second tab'),
           actionButton(NS(id, 'firsttab'), 'First Tab'),

           textInput(NS(id, 'userid'), 'User ID'),
           textOutput(outputId = NS(id, 'useridout'))
          ) # tabPanel
}

modtab2_server <- function(id) {
  moduleServer(id,
               function(input, output, session) {
                 observeEvent(input$firsttab, {
                   print(paste('switching to tab 1', input$userid))
                   updateTabsetPanel(session = session, inputId =  'tabs', 
                                     # selected = NS('tab1', 'tab.1')
                                     # selected = 'tab.1'
                                     selected = ns('tab.1')
                                     )
                 })
                 
                 output$id <- renderText(input$userid)
               })
}


ui <- fluidPage(
  tabsetPanel(
    'tabs',
             modtab1_ui('tab1'),
             modtab2_ui('tab2')
  )
)

server <- function(input, output, session) {
  modtab1_server('tab1')
  modtab2_server('tab2')
}

shinyApp(ui = ui, server = server)

Solution

  • Building off of Limey's response. You can streamline to one module by additional formals to the UI module.

    library(shiny)
    
    modTabUi <- function(id, panelTitle = 'Tab 1', headding = 'This is the first tab', buttonLabel = 'Next Tab') {
      ns <- NS(id)
      tabPanel(
        title = panelTitle,
        value = ns('tab'),
        h4(headding),
        actionButton(ns('nexttab'), buttonLabel)
      )
    }
    
    modTabServer <- function(id) {
      moduleServer(id,
                   function(input, output, session) {
                     retVal <- reactiveValues(count = 0)
                     
                     observeEvent(input$nexttab, retVal$count <- retVal$count + 1)
                     return(reactive(retVal$count))
                   })
    }
    
    
    ui <- fluidPage(
      tabsetPanel(
        id='tabs',
        modTabUi('tab1', panelTitle = 'Tab 1', headding = 'This is the first tab', buttonLabel = 'Next Tab'),
        modTabUi('tab2', panelTitle = 'Tab 2', headding = 'This is the second tab', buttonLabel = 'Back to First Tab')
      )
    )
    
    server <- function(input, output, session) {
      
      tab1val <- modTabServer('tab1')
      tab2val <- modTabServer('tab2')
      
      observeEvent(tab1val(), {
        updateTabsetPanel(session, 'tabs', selected = 'tab2-tab')
      })  
      
      observeEvent(tab2val(), {
        updateTabsetPanel(session, 'tabs', selected = 'tab1-tab')
      })
    }
    
    shinyApp(ui = ui, server = server)