Dompter le téléchargement de fichier dans shiny

Author : Vincent Guyader
Tags : shiny
Date :

Mais pourquoi downloadHandler me retourne un fichier vide maintenant!?

Contexte avec downloadHandler

Quand on commence a jouer avec {shiny} on arrive généralement à un moment ou il est nécessaire de proposer à l’utilisateur de récuperer un document généré par l’application.
Ce document peut etre un rapport pdf, un png un jpg ou que ce ce soit d’autre.

La fonction adaptée à cela est shiny::downloadHandler() mais en dehors du cas (relativement) simple de l’exemple de la documentation, des qu’il est question d’utiliser la fonction dans un cas plus complexe il n’est pas rare de trébucher même pour un développeur aguérri.

Ainsi j’aimerais partager avec vous une approche que nous utilisons chez ThinkR et qui permet de gerer à la fois les cas triviaux et les cas un peu plus tordu qu’il nous arrive de mettre en place.

TL;DR :

on va systématiquement utiliser file.copy dans le downloadHandler

Exemple 1 : Utiliser downloadHandler uniquement avec file.copy

Voyons ensemble un cas de figure simple, puiqu’on va partir de l’exemple de la documentation de R pour la rendre plus “robuste”.

AVANT :

ui <- fluidPage(
  downloadButton(outputId = "downloadData",label =  "Download")
)
server <- function(input, output) {
  # Our dataset
  data <- mtcars
  output$downloadData <- downloadHandler(
    filename = function() {
      paste("data-", Sys.Date(), ".csv", sep="")
    },
    content = function(file) {
      write.csv(data, file)
    }
  )
}
shinyApp(ui, server)

APRES :

ui <- fluidPage(
  downloadButton(outputId = "downloadData",label =  "Download")
)
server <- function(input, output) {
  local <- reactiveValues(data = mtcars,
                          export_file = NULL
                          )
  observeEvent(local$data,{
    out <- tempfile(fileext = ".csv")
    write.csv(x = local$data,file = out)
    local$export_file <- out
  })
  output$downloadData <- downloadHandler(
    filename = function() {
      paste("data-", Sys.Date(), ".csv", sep="")
    },
    content = function(file) {
      file.copy(
           from = local$export_file,
           to = file
         )
    }
  )
}
shinyApp(ui, server)

Vous voyez la différence ? on reprend le controle sur le fichier que l’on va mettre derriere le bouton, il est ici fabriqué dans un observeEvent, facile à débuguer, on sait ou il est, ce qu’il ya dedans.

Exemple 2: Gérer le nom de fichier indépendamment

Cette approche rend plus lisible des cas de figure plus complexe :

library(tidyverse)
library(shiny)
library(flextable)
ui <- fluidPage(
  downloadButton(outputId = "downloadData",label =  "Download")
)
server <- function(input, output) {
  local <- reactiveValues(data = mtcars,
                          export_file = NULL
  )
  observeEvent(local$data,{
    out <- tempfile(fileext = ".png",pattern = "table_")
    # write.csv(x = local$data,file = out)
    local$data %>% 
    flextable::flextable() %>% 
      flextable::save_as_image(path = out)
    if (file.exists(out)){
    local$export_file <- out
    } else{
    local$export_file <- NULL
    }
  })
  output$downloadData <- downloadHandler(
    filename = function() {
      basename(local$export_file)
    },
    content = function(file) {
      file.copy(
        from = local$export_file,
        to = file
      )
    }
  )
}
shinyApp(ui, server)

A vous de jouer !


À propos de l'auteur

Vincent Guyader

Vincent Guyader

Codeur fou, formateur et expert logiciel R


Comments


Also read