JohnCoene / cicerone

🏛️ Give tours of your Shiny apps
https://cicerone.john-coene.com
Other
187 stars 7 forks source link

Working with modules #27

Closed shahreyar-abeer closed 3 years ago

shahreyar-abeer commented 3 years ago

How will it be integrated in an app with modules?

etiennebacher commented 3 years ago

Using ns() in modules just append the id of the module to the id of the input/output. Therefore, if you want to use cicerone on single elements (e.g plots or table) that are created in a module, you can specify the full id. For example:

library(shiny)
library(cicerone)

guide <- Cicerone$
  new()$ 
  step(
    "first_module-plot_test",
    'Hello',
    'This is a plot'
  )

mod_1_ui <- function(id) {
  ns <- NS(id)
  tagList(
    plotOutput(ns("plot_test"))
  )
}

mod_1_server <- function(input, output, session) {
  output$plot_test <- renderPlot({
    plot(mtcars)
  })
}

ui <- fluidPage(
  use_cicerone(),
  mod_1_ui("first_module")
)

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

  guide$init()$start()

  callModule(mod_1_server, "first_module")
}

shinyApp(ui, server)

In one of my app, I had one module per tabPanel and I only wanted to highlight the name of the tabPanel (to say "this panel describes.... and this panel describes..., etc). One option for this is to give a value to each tabPanel and then to use data-value in the guide. For example:

library(shiny)
library(cicerone)

guide <- Cicerone$
  new()$ 
  step(
    "[data-value='first_panel']",
    'Hello',
    'This is a panel',
    is_id = FALSE
  )

mod_1_ui <- function(id) {
  ns <- NS(id)
  tabPanel(
    "First panel",
    value = "first_panel",
    plotOutput(ns("plot_test"))
  )

}

mod_1_server <- function(input, output, session) {
  output$plot_test <- renderPlot({
    plot(mtcars)
  })
}

ui <- navbarPage(
  use_cicerone(),
  mod_1_ui("first_module")
)

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

  guide$init()$start()

  callModule(mod_1_server, "first_module")
}

shinyApp(ui, server)

These are the solutions I use but I don't know if there's a cleaner way to do this. Maybe it would useful to include an example for this in the documentation?

JohnCoene commented 3 years ago

It's not ideal, I will re-work cicerone eventually.

When using modules it's easier to define the guide (Cicerone$new()) within the module then use the namespace (ns).


Cicerone$
  new()$
  step(
    ns("id"),
    "Title",
    "Description"
  )
shahreyar-abeer commented 3 years ago

@etiennebacher Thanks! I am doing it just like you said at the moment. But as @JohnCoene just mentioned. It's not ideal. Waiting for some workarounds from @JohnCoene

Oh and by 'within the module', do you mean inside the ui function? @JohnCoene

JohnCoene commented 3 years ago

@shahreyar-abeer

You can use it like this.

library(shiny)
library(cicerone)

mod_ui <- function(id){
  ns <- NS(id)

  tagList(
    actionButton(
      ns("start"),
      "Start tour"
    ),
    textInput(
      ns("text"),
      "Label"
    )
  )
}

mod_server <- function(input, output, session){
  ns <- session$ns

  guide <- Cicerone$
    new()$
    step(
      ns("text"),
      "Text Input",
      "This is a text input"
    )

  observeEvent(input$start, {
    guide$init()$start()
  })
}

ui <- fluidPage(
  use_cicerone(),
  mod_ui("module")
)

server <- function(input, output, session){
  callModule(mod_server, "module")
}

shiny::shinyApp(ui, server)
shahreyar-abeer commented 3 years ago

Thanks!

gueyenono commented 3 years ago

It's not ideal, I will re-work cicerone eventually.

When using modules it's easier to define the guide (Cicerone$new()) within the module then use the namespace (ns).

Cicerone$
  new()$
  step(
    ns("id"),
    "Title",
    "Description"
  )

@JohnCoene

Does that mean that it is currently impossible to use {cicerone} in the following context until the planned re-work of the package?

cicerone_with_modules

Each of highlighted boxes is a module and I would like my guided tour to highlight Box 1, then highlight Box 2. It may be currently impossible to achieve this since guides need to be defined "inside" modules.

My code:

library(shiny)
library(bs4Dash)
library(cicerone)

# Module for box 1 ----

mod_box1_ui <- function(id){
  ns <- NS(id)
  box(
    id = ns("box1"),
    title = "Box 1",
    numericInput(ns("num"), label = "Enter a number", value = 5, min = 0, max = 10)
  )
}

mod_box1_server <- function(input, output, session){
  ns <- session$ns
}

# Module for box 2 ----

mod_box2_ui <- function(id){
  ns <- NS(id)
  box(
    id = ns("box2"),
    title = "Box 2",
    sliderInput(ns("slide"), label = "Choose a number", value = 5, min = 0, max = 10)
  )
}

mod_box2_server <- function(input, output, session){
  ns <- session$ns
}

# Cicerone guide ----

guide <- Cicerone$
  new()$
  step(
    el = "box1",
    title = "Step 1",
    description = "This is box 1."
  )$
  step(
    el = "box2",
    title = "Step 2",
    description = "This is box 2."
  )

# Main app ----

ui <-   dashboardPage(
  dashboardHeader(title = "Basic dashboard"),
  dashboardSidebar(),
  dashboardBody(
    fluidRow(
      mod_box1_ui("box1"),
      mod_box2_ui("box2")
    )
  ),
  dashboardControlbar(),
  dashboardFooter()
)

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

  guide$init()$start()
  callModule(mod_box1_server, "box1")
  callModule(mod_box2_server, "box2")

}

shinyApp(ui = ui, server = server)
gueyenono commented 3 years ago

It's not ideal, I will re-work cicerone eventually. When using modules it's easier to define the guide (Cicerone$new()) within the module then use the namespace (ns).

Cicerone$
  new()$
  step(
    ns("id"),
    "Title",
    "Description"
  )

@JohnCoene

Does that mean that it is currently impossible to use {cicerone} in the following context until the planned re-work of the package?

cicerone_with_modules

Each of highlighted boxes is a module and I would like my guided tour to highlight Box 1, then highlight Box 2. It may be currently impossible to achieve this since guides need to be defined "inside" modules.

My code:

library(shiny)
library(bs4Dash)
library(cicerone)

# Module for box 1 ----

mod_box1_ui <- function(id){
  ns <- NS(id)
  box(
    id = ns("box1"),
    title = "Box 1",
    numericInput(ns("num"), label = "Enter a number", value = 5, min = 0, max = 10)
  )
}

mod_box1_server <- function(input, output, session){
  ns <- session$ns
}

# Module for box 2 ----

mod_box2_ui <- function(id){
  ns <- NS(id)
  box(
    id = ns("box2"),
    title = "Box 2",
    sliderInput(ns("slide"), label = "Choose a number", value = 5, min = 0, max = 10)
  )
}

mod_box2_server <- function(input, output, session){
  ns <- session$ns
}

# Cicerone guide ----

guide <- Cicerone$
  new()$
  step(
    el = "box1",
    title = "Step 1",
    description = "This is box 1."
  )$
  step(
    el = "box2",
    title = "Step 2",
    description = "This is box 2."
  )

# Main app ----

ui <-     dashboardPage(
  dashboardHeader(title = "Basic dashboard"),
  dashboardSidebar(),
  dashboardBody(
    fluidRow(
      mod_box1_ui("box1"),
      mod_box2_ui("box2")
    )
  ),
  dashboardControlbar(),
  dashboardFooter()
)

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

  guide$init()$start()
  callModule(mod_box1_server, "box1")
  callModule(mod_box2_server, "box2")

}

shinyApp(ui = ui, server = server)

This question was answered by @JohnCoene in #29