Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FR] Pseudo-modules: a function to create a pseudo-module that shares the same namespace as it's parent module environment. #995

Closed
yogat3ch opened this issue Feb 28, 2023 · 8 comments
Labels
enhancement New feature or request

Comments

@yogat3ch
Copy link
Contributor

yogat3ch commented Feb 28, 2023

In a deeply nested modular shiny app, there is often a need to functionalize pieces of UI/Server functionality that will generate inputs & outputs for which there can be additional interactivity in the top-level module.
Current practice usually involves making this UI/Server functionality into a module using add_module that then changes the namespace. Inputs are then passed from the module up to the parent via the session$userData object, an object in the global or package namespace environment, or via a reactiveVal/ues passed into the module as an argument.

Wouldn't it be easier in many cases to just have the UI/Server functionality create additional input/outputs in the parent modules namespace?

Describe the solution you'd like
Here's an example of what a pseudo-module might look like using a convenience function that finds the ns function automatically shinyVirga::ns_find:

#' test_pseudo UI Function
#'
#' @description A shiny Module.
#'
#' @param id,input,output,session Internal parameters for {shiny}.
#'
#' @noRd
#'
#' @importFrom shiny NS tagList
mod_test_pseudo_ui <- function(.ns = shinyVirga::ns_find()){
  ns <- .ns
  tagList(
    radioButtons(
      ns("radio"),
      "Radio Buttons",
      choices = rlang::set_names(1:3, letters[1:3])
    ),
    textOutput(ns("text"))
  )
}

#' test_pseudo Server Functions
#'
#' @noRd
mod_test_pseudo_server <- function(session = shiny::getDefaultReactiveDomain()){
  ns <- session$ns
  input <- session$input
  output <- session$output
  observeEvent(input$radio, {
    req(input$radio)
    print(input$radio)
  })

  output$text <- renderText({
    req(input$radio)
    paste0("Your selection: ", input$radio)
  })

}

## To be copied in the UI
# mod_test_pseudo_ui("test_pseudo_1")

## To be copied in the server
# mod_test_pseudo_server("test_pseudo_1")

Such a pseudo-module will create input/output directly in it it's parent UI/server module namespace and therefore be accessible in the parent namespace for further reactivity.

I propose (and would be happy to PR) a golem::add_pseudo_module if we think this is functionality that would be useful to have in the golem package? ns_find currently uses stringr, purrr and UU but these functions could be substituted such that golem doesn't need any additional dependencies if that is preferred?
@VincentGuyader @ColinFay

@yogat3ch yogat3ch added the enhancement New feature or request label Feb 28, 2023
@yogat3ch
Copy link
Contributor Author

yogat3ch commented Mar 2, 2023

A rough draft is here, borrowing from golem:::module_template

@yogat3ch
Copy link
Contributor Author

yogat3ch commented Aug 25, 2023

Hey @VincentGuyader & @ColinFay,
Thoughts on adding this?

Also wondering if y'all would be up for adding session = shiny::getDefaultReactiveDomain() to the UI module template's arguments? Specifically because session$userData is a great way to store state variables on a per-session basis. It would be super useful to add an additional line to both ui and server as well:
ud <- session$userData

I can create a PR for this request if y'all think it would be a nice addition?
If not, please lmk so I can add these enhancements to shinyVirga

@ilyaZar
Copy link
Contributor

ilyaZar commented Aug 25, 2023

I think this pseudo-module idea is really nice and interesting, @yogat3ch!

It seems tied to a design pattern for specific scenarios which is cool.

But, could you maybe add a simple example in the function's roxygen documentation if that is not too complicated?

This would help people like me get a better grasp on when and how to use this approach.

I get the abstract part:

  1. The pseudo-module using the parent's namespace might simplify data passing between nested modules, making the code more straightforward.
  2. It could also make the structure transparent and less reliant on things like session$userData.

But a concrete example would be super helpful! 😅

This extra context might not only help me but also others less familiar with {golem} or {shiny}. (though, modules can be a bit tricky anyways and it probably addresses larger apps i.,e. intermediate users so ...).

@mikael04
Copy link

mikael04 commented Sep 6, 2023

I already needed something like this, and would be nice to have some solution like yours @yogat3ch.
I agree with ilyZar that one example would be good too.

@yogat3ch
Copy link
Contributor Author

yogat3ch commented Sep 8, 2023

Hi @ilyaZar and @mikael04,
Thanks for the encouragement! I've just added an extended description to the roxygen that describes the use case for pseudo modules in more depth. It's not something easily demonstrated with an example because it's usefulness shines when developing numerous interacting inputs, say for a complex plot, and those inputs need be modular but also must interact with one another in a confluence fashion to determine how the plot renders. The description goes into more depth.

I've also modified add_pseudo_module and added add_module with a use_ud option to include session$userData automatically in the UI/Server environment as a shorthand variable ud since this environment is especially useful for storing a user's state variables.

Hope this is helpful!

@ColinFay
Copy link
Member

Hey folks,

I feel like this is a good use case for the module_template template argument of the add_module function, as described in https://thinkr-open.github.io/golem/articles/f_extending_golem.html#module-templates ?

@yogat3ch
Copy link
Contributor Author

@ColinFay Ahh, that looks like the solution we're looking for! Thanks!

@ColinFay
Copy link
Member

@yogat3ch awesome!

Please let me know if you open source this, I'll be happy to link it as an example in the doc :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

4 participants