Search code examples
rshinyr-leaflet

On map click, copy coordinates to clipboard


I would like this app to immediately copy to clipboard the coordinates upon click.

In other words, I want to get rid of the copy button.

If this is not possible, I would like to have the copy button appear in the leaflet popup.

library(shiny)
library(bslib)
library(rclipboard)
library(leaflet)

base_map <- leaflet() |> 
  addTiles()
# The UI
ui <- bslib::page_fluid(
  
  rclipboardSetup(),
  # Add a text input
  textInput("copytext", "Copy this:", "Co-Ordinates!"),
  # UI ouputs for the copy-to-clipboard buttons
  uiOutput("clip"),
  # A text input for testing the clipboard content.
  textInput("paste", "Paste here:"),
  leafletOutput("map")
  
)

# The server
server <- function(input, output, session) {
  
  # Add clipboard buttons
  output$clip <- renderUI({
    rclipButton(
      inputId = "clipbtn",
      label = "rclipButton Copy",
      clipText = input$copytext, 
      icon = icon("clipboard"),
    )
  })
  
  output$map <- renderLeaflet(base_map)
  
  observe({
    click <- input$map_click
    text <- paste0(click$lat, ", ", click$lng)
    
    leafletProxy("map") |>
      addPopups(
        lat = click$lat, 
        lng = click$lng, 
        popup = text
      )
    
    updateTextInput(session, "copytext", value = text)
  }) |> 
    bindEvent(input$map_click)
  
}

shinyApp(ui, server)

Solution

  • rclipboard is an R wrapper for clipboard.js, which describes itself as a library for:

    Modern copy to clipboard. No Flash.

    This is a useful library for writing arbitrary data (e.g. images) to the clipboard. However, we can write text without dependencies using the JavaScript navigator.clipboard API and the Shiny to JavaScript API.

    Define a JS function to write text to the clipboard

    In your ui add a Shiny custom message handler. In this case, a function which takes some text and copies it to the clipboard:

    ui <- bslib::page_fluid(
      tags$script("
          Shiny.addCustomMessageHandler('txt', function (txt) {
            navigator.clipboard.writeText(txt);
        });
      "), # <- This is the only new UI element 
      textInput("copytext", "Copy this:", "Co-Ordinates!"),
      textInput("paste", "Paste here:"),
      leafletOutput("map")
    )
    

    Pass the coordinates string created in R to the JS function

    In your server logic, add session$sendCustomMessage("txt", text) to the observe event:

    server <- function(input, output, session) {
    
      output$map <- renderLeaflet(base_map)
      
      observe({
        click <- input$map_click
        text <- paste0(click$lat, ", ", click$lng)
        
        # Only this line is new
        session$sendCustomMessage("txt", text)
        
        leafletProxy("map") |>
          addPopups(
            lat = click$lat, 
            lng = click$lng, 
            popup = text
          )
    
        updateTextInput(session, "copytext", value = text)
      }) |> 
        bindEvent(input$map_click)
      
    }
    

    Output

    enter image description here

    Browser compatibility

    Depending on your IDE this might not work in the preview pane, as the navigator.clipboard API may not be supported. It should work in any modern browser. navigator.clipboard.writeText() has been supported in Chrome, Firefox and Opera since 2018, and in Safari and Edge since 2020. See here for more browser compatibility details.