Search code examples
rshinybslib

Unable to identify where to specify header/footer in an R shiny app


Problem

I have one module in my shiny app that resides in the R directory. Without modularizing, the app.R does not throw any errors. But with the module I get this error:

Error: Navigation containers expect a collection of `bslib::nav_panel()`/`shiny::tabPanel()`s and/or `bslib::nav_menu()`/`shiny::navbarMenu()`s. Consider using `header` or `footer` if you wish to place content above (or below) every panel's contents.

Code

app.R

library(shiny)
library(shinyWidgets)
library(bslib)

# Names
name_inputs <- layout_columns(

  textInput(
    inputId = "iname",
    label = "Name of Instructor:",
    value = ""
  ),

  textInput(
    inputId = "pname1",
    label = "Name1:",
    value = ""
  ),

  textInput(
    inputId = "pname2",
    label = "Name2:",
    value = ""
  ),

  col_widths = c(4, 4, 4)
)



# UI

ui <- page_fluid(
  tags$head(

    tags$style(HTML("
      .btn-group-label {
        display: flex;
        align-items: center;
        flex-wrap: wrap;
      }
      .label-spacing {
        margin-right: 10px;
        min-width: 150px;
        display: inline-block;
      }"))
  ),

  name_inputs,
  mod_part_A_ui("part_A")

)


# Server

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

  mod_part_A_server("part_A")
}

shinyApp(ui, server)

Module

mod_part_A_ui <- function(id) {
 ns <- NS(id)
  tagList(
    actionButton(ns("add"), "New"),
    br(),
    br(),
    tabsetPanel(ns("tabs"))
  )
}

mod_part_A_server <- function(id) {
  moduleServer(
    id,
    function(input, output, session) {

      ns <- session$ns

      observeEvent(input$add, {
        showModal(
          modalDialog(
            textInput(
              inputId = ns("e_name"),
              label = "E name:",
              value = ""
            ),
            actionButton(ns("submit_e_name"), "Submit"),
            title = "Enter e name",
            footer = NULL,
            size = "s",
            easyClose = FALSE
          )
        )

      })

      observeEvent(input$submit_e_name, {
        removeModal()

        e_name <- input$e_name

        insertTab(inputId = "tabs",
                  tabPanel(
                    title = e_name,
                    tagList(
                      br(),
                      layout_columns(
                        tagList(
                          lapply(
                            c("F", "H", "T",
                              "R", "E", "A", "C",
                              "A", "C"),
                            function(x) {
                              div(
                                class = "btn-group-label",
                                span(class = "label-spacing", paste0(x, ":")),
                                radioGroupButtons(
                                  inputId = ns(paste0("A_", gsub("\\s", "", x))),
                                  choiceNames = c("+", "-", "x"),
                                  choiceValues = c("p and p",
                                                   "p and n",
                                                   "not p"),
                                  status = "primary",
                                  size = "normal",
                                  selected = character(0)
                                )
                              )
                            }
                          )
                        ),

                        textAreaInput(inputId = ns("A_comments"),
                                      label = "Notes:",
                                      placeholder = "Add your notes here.",
                                      width = "600px", height = "500px"),

                        col_widths = c(4, 8)
                      ),
                      actionButton(ns("save"), "Save")
                    )

                  )
        )
      })

    }
  )
}

Traceback

The error traceback:

30.
stop(msg, call. = FALSE)
29.
FUN(X[[i]], ...)
28.
lapply(seq_len(length(tabs)), buildTabItem, tabsetId = tabsetId,
foundSelected = foundSelected, tabs = tabs, textFilter = textFilter)
27.
buildTabset(..., ulClass = paste0("nav nav-", type), id = id,
selected = selected)
26.
tabsetPanel_(..., type = "tabs", id = id, selected = selected,
header = header, footer = footer)
25.
new(...)
24.
func(..., id = id, selected = selected, header = header, footer = footer)
23.
remove_first_class(func(..., id = id, selected = selected, header = header,
footer = footer))
22.
tabsetPanel(ns("tabs"))
21.
dots_list(...)
20.
tagList(actionButton(ns("add"), "New"), br(), br(), tabsetPanel(ns("tabs"))) at mod_part_A.R#3
19.
mod_part_A_ui("part_A")
18.
dots_list(...)
17.
div(class = "container-fluid", ...)
16.
list2(...)
15.
bootstrapPage(div(class = "container-fluid", ...), title = title,
theme = theme, lang = lang)
14.
shiny::fluidPage(..., title = title, theme = theme, lang = lang,
if (isTRUE(theme_version(theme) >= 5)) component_dependencies())
13.
as_page(shiny::fluidPage(..., title = title, theme = theme, lang = lang,
if (isTRUE(theme_version(theme) >= 5)) component_dependencies()),
theme = theme)
12.
page_fluid(tags$head(tags$style(HTML("\n .btn-group-label {\n display: flex;\n align-items: center;\n flex-wrap: wrap;\n }\n .label-spacing {\n margin-right: 10px;\n min-width: 150px;\n display: inline-block;\n }"))),
name_inputs, mod_part_A_ui("part_A"))
11.
..stacktraceon..({
library(shiny)
library(shinyWidgets)
library(bslib) ...
10.
eval(exprs, envir)
9.
eval(exprs, envir)
8.
sourceUTF8(fullpath, envir = new.env(parent = sharedEnv))
7.
func(fname, ...)
6.
appObj()
5.
shinyAppDir_appR("app.R", appDir, options = options)
4.
shinyAppDir(x)
3.
as.shiny.appobj.character(appDir)
2.
as.shiny.appobj(appDir)
1.
runApp("for_help")

I am unable to identify what component needs to be wrapped in header and/or footer in the tabsetPanel. Please guide me what I'm missing.


Solution

  • Let's first look at the docs: ?tabsetPanel. Under the Usage section we see:

    tabsetPanel(
      ...,
      id = NULL,
      selected = NULL,
      type = c("tabs", "pills", "hidden"),
      header = NULL,
      footer = NULL
    )
    

    The first parameter to tabsetPanel() is ... and not id.

    As such, when you pass the id by position as the first argument, it goes in as .... Since ... expect tabPanel() elements, the error you see is thrown.

    The solution is to pass id as a named argument to tabsetPanel():

    tabsetPanel(id = ns("tabs"))