Home > Enterprise >  Shiny app module: Extract an input created in server function with leaflet
Shiny app module: Extract an input created in server function with leaflet

Time:11-07

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, outputs should not be assigned inside observe() or observeEvent()

  • Related