This is the first time I try to reorganize a shiny app into shiny modules, so, some help would be welcome.
My goal is to create a clickable map module with leaflet
that would store the latitude and longitude in an input
that I could re-use in other modules. Currently the module works by creating in the ui
a leafletOutput
(id="mymap") and using in the server
function a observeEvent
function that reacts to click on the map. The click event generates an input
vector of the longitude and latitude (input$input$mymap_click$lat[1]
& input$mymap_click$lng[1]
)which is used to place a marker on the map. But I struggle to extract those to values to use it externally by other modules or render*
functions. (it works without the "module method" but the code is a bit messy)
For the sake of clarity in my example I try to use the latitude and longitude in a textOutput
with renderText
instead of in a module.
# clickable leaflet module ----------------------------------------------------------
## loads leaflet library
library(leaflet)
##ui function
clicMapOutput <- function(id) {
ns <- NS(id)
tagList(leafletOutput(ns("mymap")),
textOutput(ns("text")))
}
## serverfunction
clicMapServer <- function(id) {
moduleServer(id,
function(input, output, session) {
# outputs a map
output$mymap <-
leaflet::renderLeaflet({
leaflet() %>% addTiles() %>% setView(lat = 0,
lng = 0,
zoom = 2)
})
# makes map clickable to obtain a marker and a longitude latitude vector
observeEvent(input$mymap_click, {
output$mymap <-
leaflet::renderLeaflet({
leaflet() %>% addTiles() %>% addMarkers(lat = input$mymap_click$lat[1],
lng = input$mymap_click$lng[1])
})
})
})
}
# Calling modules ---------------------------------------------------------
library(shiny)
ui<-fluidPage(
clicMapOutput("map"),
textOutput("lng")
)
server<-function(input,output,session){
clicMapServer("map")
output$lng<-renderText({
input$mymap_click$lng[1]
})
}
shinyApp(ui=ui,server=server)
CodePudding user response:
the ususal way of doing this is to define a return value in the server part of the module and then using it in the module consumer
## module server
clicMapServer <- function(id) {
moduleServer(id, function(input, output, session) {
## ...
return(reactive(input$mymap_click$lng[1])))
}
}
## consumer server
server <- function(input, output, session) {
lng <- clicMapServer("map")
output$lng <- renderText({ lng() })
}
You should always make sure to wrap the return values with reactives and use the return value like a function. If you want to return more than one variable, see my answer to this question for details.
library(leaflet)
library(shiny)
## module ui
clicMapOutput <- function(id) {
ns <- NS(id)
leafletOutput(ns("mymap"))
}
## module server
clicMapServer <- function(id) {
moduleServer(id, function(input, output, session) {
output$mymap <- renderLeaflet({
leaflet() %>% addTiles() %>% setView(
lat = 0, lng = 0, zoom = 2)
})
# handle click events
observeEvent(input$mymap_click, {
output$mymap <- renderLeaflet({
leaflet() %>% addTiles() %>% addMarkers(
lat = input$mymap_click$lat[1],
lng = input$mymap_click$lng[1])
})
})
return(reactive(input$mymap_click$lng[1]))
})
}
# main ui
ui <- fluidPage(
clicMapOutput("map"),
textOutput("lng")
)
# main server
server <- function(input, output, session) {
lng <- clicMapServer("map")
output$lng <- renderText({ lng() })
}
shinyApp(ui = ui, server = server)
Another thing I noticed is that you are updating the leaflet widget by overwriting output$mymap
. It would be better to use leaflet::leafletProxy()
instead. Generally speaking, output
s should not be assigned inside observe()
or observeEvent()