Search code examples
rshinypopupgoogle-maps-markersr-leaflet

How to add a new marker on the map with the popup already opened?


I have a shiny app that displays a leaflet map. One of the functionalities of the app allows users to add a new geographic marker on the map. However, I would like to make the popup of such marker already opened as soon as it is added on the map, but always keeping the possibility to close it with the "x" button or by clicking on the map.

I've read the "Marker" guide of the leaflet documentation (sub-section "Popup methods inherited from Layer", the very detailed guide is, apparently, not available in the R language) and I've found this option openPopup(). Unfortunately, this doesn't seem to work for my code. Here's a simple example:

library(leaflet)

leaflet() %>% 
  addTiles() %>% 
  addMarkers(lng = -100,
             lat = 50,
             popup = "ALE",
             options = popupOptions(openPopup = TRUE))

The expected behaviour is the one shown in this online tool that uses leaflet. Please try to search for an address and look as the new marker has its own popup immediately open.


Solution

  • Code

    I guess a bit of JavaScript will be needed as the interface seems not to support this feature.

    Below a working shiny example:

    library(shiny)
    library(leaflet)
    library(shinyjs)
    
    js_save_map_instance <- HTML(
       paste(
          "var mapsPlaceholder = [];",
          "L.Map.addInitHook(function () {",
          "   mapsPlaceholder.push(this); // Use whatever global scope variable you like.",
          "});", sep = "\n"
       )
    )
    
    js_open_popup <- HTML(
       paste("function open_popup(id) {",
             "   console.log('open popup for ' + id);",
             "   mapsPlaceholder[0].eachLayer(function(l) {",
             "      if (l.options && l.options.layerId == id) {",
             "         l.openPopup();",
             "      }",
             "   });",
             "}", sep = "\n"
       )
    )
    
    ui <- fluidPage(
       tags$head(
          tags$script(type = "text/javascript",
                      js_save_map_instance,
                      js_open_popup),
          useShinyjs()
       ),
       wellPanel(
          fluidRow(
             sliderInput("lng", "Longitude:", 10, 60, 35, 0.01),
             sliderInput("lat", "Latitude:", 35, 75, 42.5, 0.01),
             actionButton("add", "Add Marker")
          )
       ),
       fluidRow(
          leafletOutput("map")
       )
    )
    
    server <- function(input, output, session) {
       map <- leaflet() %>% 
          addTiles() %>%
          fitBounds(10, 35, 60, 75)
       
       output$map <- renderLeaflet(
          map
       )
       
       observeEvent(input$add, {
          id <- paste0("marker", input$add)
          leafletProxy("map") %>%
             addMarkers(input$lng, input$lat, id,
                        "mymarkers", 
                        popup = sprintf("%.2f / %.2f: %s", input$lng, input$lat, 
                                        id))
          runjs(sprintf("setTimeout(() => open_popup('%s'), 10)", id))
       })
    }
    
    shinyApp(ui, server)
    

    Remarks

    • leaflet.js does not provide an out-of-the-box functionality to retrieve the map instance after the initialization. Thus, we have to use a workaround to save the map explicitly upon initialization. This is done in the JS code stored in js_save_map_instance. The idea is taken from Find Leaflet map object after initialisation.
    • We create a JS function, which takes the id of the marker for which we want to open the popup. In this function we take the first element of our array, where we stored all map instances (if there should be more than one leaflet map on the page, this needs to be adapted.
    • On this map instance we can loop through all layers and if the layerId equals the one we are looking for, we can call openPopup on this layer. (N.B. ideally we could access the layer directly (again I did not find any function in the API for that) and we could use the private _layer property, but since it is private, I use the loop apprach.)
    • All what is left, is to call open_popup in the right place on the shiny side. I needed to add a small delay to avoid that we call the function before the marker is added.

    Results

    Popup on creation