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

Viewed 322

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)
2 Answers

Here's a MWE that, I think, gives you what you want.

library(shiny)

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

modtab1_server <- function(id) {
  moduleServer(id,
                  function(input, output, session) {
                    retVal <- reactiveValues(count=0)
                    
                    observeEvent(input$nexttab, retVal$count <- retVal$count + 1)
                    return(reactive(retVal$count))
                 })
}

modtab2_ui <- function(id) {
  ns <- NS(id)
  tabPanel(
    title = 'Tab 2',
    value = ns('tab'),
    h4('This is the second tab'),
    actionButton(ns('firsttab'), 'First Tab')
  ) # tabPanel
}

modtab2_server <- function(id) {
  moduleServer(id,
               function(input, output, session) {
                 retVal <- reactiveValues(count=0)
                 
                 observeEvent(input$firsttab, retVal$count <- retVal$count + 1)
                 return(reactive(retVal$count))
               })
}

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

server <- function(input, output, session) {
  
  tab1val <- modtab1_server('tab1')
  tab2val <- modtab2_server('tab2')
  
  observeEvent(tab1val(), {
    updateTabsetPanel(session, 'tabs', selected = 'tab2-tab')
  })  
  
  observeEvent(tab2val(), {
    updateTabsetPanel(session, 'tabs', selected = 'tab1-tab')
  })
}

shinyApp(ui = ui, server = server)

Note the changes to your syntax, particularly regarding the use of ns and NS and the arguments passed to the functions.

Also, note the use of return values from the module server functions, and how they are accessed within the main server function.

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)
Related