Shiny modules: switch tabs from within modules that have different namespaces
Asked Answered
E

2

2

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)
Epiglottis answered 3/11, 2021 at 20:48 Comment(1)
You need to have your module server functions return a value to the main server function and then have the main server function update the selected tab on the basis of changes to the values returned by the module servers.Bannock
E
2

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)
Elitism answered 4/11, 2021 at 13:20 Comment(2)
Yep. I thought of that too, but since OP specifically mentioned two modules... That said, this is a useful generalisation.Bannock
My modules are different enough (and I will actually have at least four by the end of the process) that this specifically wouldn't work, but that does help to think about it this way. Thanks!Epiglottis
B
4

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.

Bannock answered 4/11, 2021 at 10:19 Comment(3)
This worked, thanks. I'm curious why I need to create a reactiveValues object rather than just a reactive object?Epiglottis
You don't need to. It's just the way I chose to do it. There are usually lots of ways of skinning a R cat.Bannock
I see. It wasn't working when I tried a reactive object, but I may have been doing something else wrong. Either way, your fix worked, so thank you!Epiglottis
E
2

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)
Elitism answered 4/11, 2021 at 13:20 Comment(2)
Yep. I thought of that too, but since OP specifically mentioned two modules... That said, this is a useful generalisation.Bannock
My modules are different enough (and I will actually have at least four by the end of the process) that this specifically wouldn't work, but that does help to think about it this way. Thanks!Epiglottis

© 2022 - 2024 — McMap. All rights reserved.