7

I'm trying to incorporate the easyPrint plugin into my shiny leaflet app. What I want is something that looks like the demo, but in shiny.

I have tried to mimic the examples, but have been unsuccessful.

Here's my code for my R code so far:

    library(shiny)
    library(shinydashboard)
    library(shinyjs)
    library(htmlwidgets)
    library(htmltools)
    library(leaflet)
    library(leaflet.extras)
    library(sp)

    shinyApp(
  ui = fluidPage(
    leafletOutput("map", height = 750)
  ),
  server = function(input, output) {

    registerPlugin <- function(map, plugin) {
      map$dependencies <- c(map$dependencies, list(plugin))
      map
    }

    easyPrintPlugin <- htmlDependency("leaflet-easyprint", "2.1.8",
                                      src = c(href = "https://github.com/rowanwins/leaflet-easyPrint/blob/gh-pages/dist/"),
                                      script = "index.js")

    # Map
    output$map <- renderLeaflet({
      leaflet() %>%
        addProviderTiles(providers$CartoDB.Positron) %>%
        registerPlugin(easyPrintPlugin) %>%
        onRender("function(el, x) {
                 L.easyPrint({
                 position: 'topleft',
                 sizeModes: ['A4Portrait', 'A4Landscape']
                 }).addTo(map);}")
    })

  }
)

However, nothing is happening. It's literally a white screen. If I remove the onRender part, the leaflet acts normal.

Unfortunately, I'm relatively new to Shiny, leaflet, .js, and github, so I'm struggling to identify which aspect is causing the problem.

zx8754
  • 52,746
  • 12
  • 114
  • 209
rfineman
  • 128
  • 2
  • 9

1 Answers1

9

Solution

  library(leaflet)
  library(shiny)
  library(htmlwidgets)

  jsfile <- "https://rawgit.com/rowanwins/leaflet-easyPrint/gh-pages/dist/bundle.js" 
  ui <- fluidPage(
    tags$head(tags$script(src = jsfile)),
    leafletOutput("map")
  )
  
  server <- function(input, output, session) {
    
    output$map <- renderLeaflet({
      leaflet() %>% 
        addProviderTiles("OpenStreetMap.Mapnik") %>%
        setView(-122.23, 37.75, zoom = 10) %>%
        onRender(
          "function(el, x) {
            L.easyPrint({
              sizeModes: ['Current', 'A4Landscape', 'A4Portrait'],
              filename: 'mymap',
              exportOnly: true,
              hideControlContainer: true
            }).addTo(this);
            }"
        )
      })
    
    }
  
  shinyApp(ui, server)

enter image description here

Note: leaflet-easyPrint depends on dom-to-image. Per the dom-to-image Readme, Safari and Internet Explorer are not supported. However, the print button will work in Chrome and Firefox.

Troubleshooting Process

If we run the app and inspect element, we see the following errors:

enter image description here

Let's start with the second and third errors.

Failed to load resource

This error is pretty self-explanatory: the URL https://github.com/rowanwins/leaflet-easyPrint/blob/gh-pages/dist//index.js doesn’t exist. The path is wrong: index.js doesn’t exist in the dist directory.

We want to use bundle.js with this path: https://github.com/rowanwins/leaflet-easyPrint/blob/gh-pages/dist/bundle.js.

Did not load script

GitHub uses strict MIME type checking, so the browser isn’t using the file as intended. We need to use a rawgit.com path instead. Read more here. To write a rawgit.com path, follow these steps (from the linked answer):

  1. Find your link on GitHub, and click to the "Raw" version of the file.
  2. Copy the URL, and link to it.
  3. Change raw.githubusercontent.com to rawgit.com (non-production) or cdn.rawgit.com (production)

We should use this path: https://rawgit.com/rowanwins/leaflet-easyPrint/gh-pages/dist/bundle.js

TypeError: L.easyPrint is not a function

The error occurred before the errors from loading leaflet-easyPrint. This tells us that onRender is getting called before leaflet-easyPrint is loaded and attached to the widget. Per Joe Cheng in this thread, htmldependency injection at runtime can be asynchronous. He recommends against using htmlDependency(src = c(href = "http://...")) for any dependency that's intended to be used with Shiny.

Instead, we can just include the remote JS file in the header of the app. Then leaflet-easyPrint will be loaded before onRender is called.

Community
  • 1
  • 1
Hallie Swan
  • 2,714
  • 1
  • 15
  • 23
  • First I really appreciate the detailed description of the troubleshooting process but when I try the same approach with other leaflet plugins (restoreView, L.Map.Sync and leaflet-side-by-side) I get the error "Uncaught ReferenceError: L is not defined" which suggests the opposite problem - the plugin is being loaded too early, before leaflet javascript has been loaded by R-Shiny. Is there something about how the easyPrint plugin has been built that makes it pull in the leaflet js? – Ryan Nov 23 '20 at 20:37
  • 1
    @Ryan I wasn't able to reproduce the issue -- I could get a small example working with leaflet-side-by-side. If you open a new question and link it here, I can take a look at your case! Might also be worth checking that you're using `this` instead of `map` in the `onRender` function, since that would cause problems. – Hallie Swan Nov 23 '20 at 23:01
  • Turns out somewhere along the way I started using uiOutput and renderUI and not leafletOutput and renderLeaflet. Your feedback helped me find the error. Thank you! – Ryan Nov 23 '20 at 23:42
  • @HallieSwan This is really nice. Can you help me what to do when the `leafletOutput("map")` is inside a renderUI like `output$UI_map <- renderUI({leafletOutput("map")})`? The button will not show on the map. – Roman Sep 13 '22 at 08:15