Accessing parent namespace inside a Shiny Module
Took me a while, but I found a way to get the sub-module to update the super-module.
Shiny is designed so that access to other modules must be done via module arguments or returned values. We can not pass the widget ID between modules, but we can pass the session information of the parent.
library(shiny)
moduleUI <- function(id) {
ns <- NS(id)
uiOutput(ns("my_link"))
}
module <- function(input, output, session, number, parent) {
output$my_link <- renderUI({
actionLink(session$ns("link"), paste0("This is a link to ", number))
})
observeEvent(input$link,{
updateSelectInput(session = parent,"selectInput",selected = number) ### use parent session
})
}
ui <- fluidPage(
selectInput("selectInput","Choose one option",choices=seq(1,10),selected=1),
moduleUI("5"),
moduleUI("10")
)
server <- function(session,input, output) {
callModule(module = module, id = "5", 5, parent = session) ### pass session information
callModule(module = module, id = "10", 10, parent = session) ### pass session information
}
shinyApp(ui = ui, server = server)
In particular note that:
- we pass the current session information when the sub-module is called
- we use the parent session when updating the input selector
Module inside module shiny
Change
callModule(serverForModal, ns("test2"))
to
callModule(serverForModal, "test2")
Using function inside module in Shiny - working with namespace
Do not use ns
in the function:
tabpanel_content <- function(radio_id, plotly_id) {
div(
id = "div_1",
style = "",
div(
class = "inputs ",
style = "width: 100%",
radioGroupButtons(
inputId = radio_id,
label = "",
choices = c("Input_1", "Input_2"),
status = "success"
)
),
div(
style = "",
plotlyOutput(plotly_id, height = 200, width = "auto")
)
)
}
Use it in the module:
mod_1_UI <- function(id) {
ns <- NS(id)
tagList(
tabsetPanel(
tabPanel(
strong("TabPanel - Title"),
tabpanel_content(
radio_id = ns("radio_input_1"), plotly_id = ns("plotly_chart_1")
),
tabpanel_content(
radio_id = ns("radio_input_2"), plotly_id = ns("plotly_chart_2")
),
tabpanel_content(
radio_id = ns("radio_input_3"), plotly_id = ns("plotly_chart_3")
)
)
)
)
}
Is it possible to refer to namespace from another module?
As I mentioned in my comment above, the canonical way would be to capture the button push as an output of module moduleServer2
and use this as an input for test-btn
where you perform the action.
However, if you want to mess around with the namespaces by yourself (not recommended), you can use the following solution. I had to adapt the leafletProxy
function, because the normal implementation automatically adds the namespace of the calling module. This is what you don't want, because you want to use the namespace of a different module.
Now with code adapted to the edit:
library(shiny)
library(mapboxer)
library(dplyr)
library(sf)
library(leaflet)
leafletProxy2 <- function (mapId, session = shiny::getDefaultReactiveDomain(),
data = NULL, deferUntilFlush = TRUE)
{
if (is.null(session)) {
stop("leafletProxy must be called from the server function of a Shiny app")
}
structure(list(session = session, id = mapId, x = structure(list(),
leafletData = data), deferUntilFlush = deferUntilFlush,
dependencies = NULL), class = "leaflet_proxy")
}
# UI 1 #
mod_btn_UI1 <- function(id) {
ns <- NS(id)
tagList(
actionButton(ns("btn1"), "Button 1"),
mod_btn_UI2(ns("moduleServer2")),
leafletOutput(ns("map"))
)
}
# Server 1 #
mod_btn_server1 <- function(id){
moduleServer(id, function(input, output, session) {
coords <- quakes %>%
sf::st_as_sf(coords = c("long","lat"), crs = 4326)
mod_btn_server2("moduleServer2", coords) # here is nested module2
output$map <- leaflet::renderLeaflet({
leaflet::leaflet() %>%
leaflet::addTiles() %>%
leaflet::setView(172.972965,-35.377261, zoom = 4) %>%
leaflet::addCircleMarkers(
data = coords,
stroke = FALSE,
radius = 6)
})
observeEvent(input$btn1, {
leaflet::leafletProxy("map", data = coords) %>%
leaflet::addCircles()
})
})
}
# Module 2 - UI #
mod_btn_UI2 <- function(id){
ns <- NS(id)
actionButton(ns("btn2"), "Button 2")
}
# Module 2 - server #
mod_btn_server2 <- function(id, dataMod, btn){
moduleServer(id, function(input, output, session) {
# and here I refer to 'map' located in the first module
observeEvent(input$btn2, {
leafletProxy2("test-btn-map", data = dataMod) %>%
leaflet::addCircles(stroke = TRUE,
color = "red")
})
})
}
# App #
ui <- fluidPage(
tagList(
mod_btn_UI1("test-btn"))
)
server <- function(input, output, session) {
mod_btn_server1("test-btn")
}
shinyApp(ui = ui, server = server)
Here is a more canonical form that works with the correct input/output of modules and doesn't mess with namespaces:
library(shiny)
library(mapboxer)
library(dplyr)
library(sf)
library(leaflet)
# UI 1 #
mod_btn_UI1 <- function(id) {
ns <- NS(id)
tagList(
actionButton(ns("btn1"), "Button 1"),
mod_btn_UI2(ns("moduleServer2")),
leafletOutput(ns("map"))
)
}
# Server 1 #
mod_btn_server1 <- function(id){
moduleServer(id, function(input, output, session) {
ns <- NS(id)
coords <- quakes %>%
sf::st_as_sf(coords = c("long","lat"), crs = 4326)
external_btn <- mod_btn_server2("moduleServer2", coords) # here is nested module2
output$map <- leaflet::renderLeaflet({
leaflet::leaflet() %>%
leaflet::addTiles() %>%
leaflet::setView(172.972965,-35.377261, zoom = 4) %>%
leaflet::addCircleMarkers(
data = coords,
stroke = FALSE,
radius = 6)
})
observeEvent(input$btn1, {
leaflet::leafletProxy("map", data = coords) %>%
leaflet::addCircles()
})
observeEvent(external_btn(), {
leaflet::leafletProxy("map", data = coords) %>%
leaflet::addCircles(stroke = TRUE,
color = "red")
})
})
}
# Module 2 - UI #
mod_btn_UI2 <- function(id){
ns <- NS(id)
actionButton(ns("btn2"), "Button 2")
}
# Module 2 - server #
mod_btn_server2 <- function(id, dataMod){
moduleServer(id, function(input, output, session) {
return(reactive(input$btn2))
})
}
# App #
ui <- fluidPage(
tagList(
mod_btn_UI1("test-btn"))
)
server <- function(input, output, session) {
mod_btn_server1("test-btn")
}
shinyApp(ui = ui, server = server)
Inserting UI into R shiny moduleServer
Here is the working code. A lot you need to notice. Here are some key points.
- Remember to add
ns
for all modules. renderUI
only returns one object, if you want to return more than one components, usetagList
ordiv
.insertUI
selector inside module also needs to addns
.
Check the code yourself for minor bugs.
library(shiny)
library(shinydashboard)
library(shinydashboardPlus)
library(shinyWidgets)
library(dplyr)
ui_module2 = function(id) {
ns = NS(id)
uiOutput(ns("finalOut"))
}
server_module2 = function(id) {
moduleServer(id,
function(input, output, session) {
output$finalOut = renderUI({
ns = session$ns
div(
# not sure what you want with next line, commented, fix your own
# textOutput(p(style = "color: red", paste0("Package Num:", id, sep = "-"))),
sliderInput(ns("n"), "N", 1, 1000, 500),
textInput(ns("label"), "Label")
)
})
})
}
ui_module1 = function(id) {
ns = NS(id)
actionBttn(ns("actionbutton1"), "Press Button For New UI")
}
server_module1 = function(id) {
moduleServer(id,
function(input, output, session) {
ns <- session$ns
observeEvent(input$actionbutton1, {
i = sprintf('%04d', input$actionbutton1)
id = sprintf('static%s', i)
print(id)
insertUI(selector = paste0("#", ns("actionbutton1")),
where = "afterEnd",
immediate = TRUE,
ui = ui_module2(ns(id)))
server_module2(id)
})
})
}
ui = fluidPage(ui_module1("opt"))
server = function(input, output, session) {
server_module1("opt")
}
shinyApp(ui, server)
understand how it works
This case is an example of nested modules. To understand this, you need to know how ns
namespace works in Shiny.
As Shiny's official document, they tell you that you need to add ns(id)
on the module UI and you don't need to do so for the module server, but they didn't tell you why.
When you call the moduleServer(id, function(input, output, session) {...})
, the session here is different than your top-level session. If you check the session here, it's a session_proxy
class object, no longer the ShinySession
class object. This is how shiny internally knows how expressions under moduleServer
are inside the module. When you call input
, output
, renderXX
, updateXX
methods under the module scope, it will first query the session
object, and if it is session_proxy
, call the session$ns
to attach namespace first. This is why you don't need to add ns()
on the server, shiny did it for you.
Ok, let's go back to the nested module problem. Let's see what's namespace on both UI and server with this simple example. All it does is print out the namespace on UI and server.
library(shiny)
uiMod2 <- function(id) {
ns <- NS(id)
div(id = ns(""))
}
serverMod2 <- function(id) {
moduleServer(
id,
function(input, output, session) {
ns <- session$ns
print(ns(""))
})
}
uiMod1 <- function(id) {
ns <- NS(id)
tagList(
div(id = ns("")),
uiMod2(ns("level2"))
)
}
serverMod1 <- function(id) {
moduleServer(
id,
function(input, output, session) {
ns <- session$ns
print(ns(""))
serverMod2("level2")
})
}
ui <- fluidPage(uiMod1("level1"))
server <- function(input, output, session) {serverMod1("level1")}
shinyApp(ui, server)
Results UI
Server
[1] "level1-"
[1] "level1-level2-"
See the difference? I didn't call ns
for second-level server, only serverMod2("level2")
, but the namespace is automatically attached for me. However, shiny doesn't know to apply this to the UI, so I have to use uiMod2(ns("level2"))
.
The reason why shiny doesn't know is all about session
. When you create a module, you need to pass function(input, output, session)
, and when you create nested modules, this session
for the second level module is not the ShinySession
, but a session_proxy
, so parent namespace(s) will be appended automatically.
Things are different with UI. Normally When you create module UI like uiMod2 <- function(id) ...
, you don't pass the session object, so it doesn't know what namespace to inherit.
Communicate from JS to Shiny in a module
I have to admit that I don't fully get the scope of your question, so please tell me if I misunderstood your intentions/reasons. I think what in your design makes problems is that you try to define a button that is the main server scope but is defined from within a module; this is not how the shiny module system is designed (additionally, the button ui has a different id than the shiny input).
If you respect the namespaces of the module system and use the same id for the button ui & shiny input, you can simplify your my_button
function because the namespace is automatically added to the id:
library(shiny)
js_handler <- HTML("$(function() {
$(document).on('click', '.my-button', function() {
$me = $(this);
var bttn_id = $me.attr('id');
var id = Math.random();
Shiny.setInputValue(bttn_id, id);
});
})")
my_button <- function(id, label) {
tagList(
tags$button(id = id,
type = "button",
class = "btn btn-default my-button",
label),
tags$head(singleton(tags$script(js_handler)))
)
}
test_ui <- function(id) {
ns <- NS(id)
tagList(
my_button(ns("btn1"), "Send To R (readable only from module)"),
verbatimTextOutput(ns("output"))
)
}
test_server <- function(id) {
moduleServer(id, function(input, output, session) {
output$output <- renderPrint(req(input$btn1))
})}
shinyApp(ui = fluidPage(h4("Module"), test_ui("test"),
h4("Main"),
my_button("btn2", "Send To R (readable only at main)"),
verbatimTextOutput("output")),
server = function(input, output, session) {
test_server("test")
output$output <- renderPrint(req(input$btn2))
})
Would that work for you?
Call updateTabItems from inside Shiny module
You can find the answer in this post: Accessing parent namespace inside a shiny module
Basically, in updateTabItems() inside a moulde, you need to call the parent's session, not the session of the modul.
Thus, add a variable for your session to callModule() and call it in updateTabItems().
Related Topics
Extracting Indices for Data Frame Rows That Have Max Value for Named Field
Ggplot Bar Plot Side by Side Using Two Variables
Robust and Clustered Standard Error in R for Probit and Logit Regression
Dygraph in R Multiple Plots at Once
Use Sprintf() to Add Trailing Zeros
Tidy Data.Frame with Repeated Column Names
Shift a Column of Lists in Data.Table by Group
Topic Models: Cross Validation with Loglikelihood or Perplexity
Manipulating Files with Non-English Names in R
Counting Occurrence of Particular Letter in Vector of Words in R
Splitting String Based on Letters Case
Install.Packages R on Ubuntu 12.04 Downloads But Does Not Install Packages