Comment utiliser FilePond avec Active Storage de Rails

FilePond est une magnifique bibliothèque JavaScript pour gérer les téléchargements de fichiers écrite par Rik Schennink.

J’ai utilisé cette bibliothèque dans divers projets Ruby on Rails lorsque je voulais une expérience de téléchargement fluide associée à d’excellents visuels d’interface.

Dans cet article, je vais expliquer comment intégrer FilePond avec Rails, et je décrirai et expliquerai certains de mes choix techniques au fur et à mesure. Ce tutoriel suppose également une connaissance de base d’Active Storage. Si vous n’êtes pas familier avec cette partie de Rails, veuillez consulter le guide officiel Rails à https://guides.rubyonrails.org/active_storage_overview.html.

Le code de démonstration pour ce tutoriel est situé à https://github.com/Code-With-Rails/filepond-demo. Je vous recommande de cloner ce dépôt et d’exécuter la démo à partir de là.

Ce Que Nous Construisons

Pour intégrer FilePond avec notre application, il y a deux parties :

  • Premièrement, nous devrons ajouter la bibliothèque JavaScript FilePond et l’activer sur notre balise HTML d’entrée de fichier.
  • Deuxièmement, nous devrons intégrer FilePond avec notre application Rails. Spécifiquement, nous voulons qu’Active Storage gère les téléchargements de fichiers.

Pour accomplir la première partie, nous utiliserons du JavaScript vanilla. Pour la deuxième partie, nous tirerons parti de la bibliothèque JavaScript existante d’Active Storage et activerons les Téléchargements Directs. Pour accommoder certains endpoints de serveur spécifiques que FilePond requiert, nous créerons un contrôleur personnalisé.

Installation de FilePond

Pour ce tutoriel, nous commencerons avec une application Rails 7.0.x. Cela signifie que nous utiliserons importmap-rails pour ajouter nos dépendances JavaScript.

bin/bundle add importmap-rails
bin/rails importmap:install

Ensuite, nous devrons ajouter la dépendance pour FilePond avec la commande suivante :

bin/rails importmap pin filepond

Nous voulons précharger FilePond, donc dans le fichier config/importmap.rb, modifiez le fichier comme ceci :

# Avant
pin 'filepond', to: 'https://ga.jspm.io/npm:filepond@4.30.4/dist/filepond.js'

# Après
pin 'filepond', to: 'https://ga.jspm.io/npm:filepond@4.30.4/dist/filepond.js', preload: true

Les Bases de FilePond

Pour initialiser FilePond, nous devrons d’abord avoir un élément HTML d’entrée de fichier. Notre formulaire ressemblera à quelque chose comme ceci :

<%= form_with model: @user, url: update_avatar_path, method: :post do |f| %>
  <%= f.label :avatar, 'Mettez à jour votre avatar' %>
  <!-- Nous transformerons cette entrée en un pond -->
  <%= f.file_field :avatar, class: 'filepond', direct_upload: true %>
  <%= f.button 'Mettre à jour' %>
<% end %>

Le code JavaScript correspondant ressemblera à ceci :

// application.js
const input = document.querySelector('.filepond')
FilePond.create(input)

C’est le minimum de ce dont vous avez besoin pour que FilePond convertisse une simple balise d’entrée de fichier en widget.

Spécifiquement pour ce que nous construisons ici, nous implémenterons les éléments suivants :

  • Configurer FilePond pour effectuer des téléchargements directs (en utilisant la bibliothèque JavaScript d’Active Storage) vers des fournisseurs cloud
  • Configurer FilePond et notre application pour permettre le téléchargement avec une URL distante uniquement
  • Configurer notre application pour purger les fichiers non attachés (blobs) lorsque les utilisateurs cliquent sur annuler (défaire) dans le widget FilePond

Application Rails Simple

Nous allons d’abord configurer notre modèle de données pour qu’Active Storage puisse associer les pièces jointes.

bin/rails g model User name:string
bin/rails db:migrate

Dans le fichier user.rb, mettons à jour le modèle comme ceci :

class User < ApplicationRecord
  has_one_attached :avatar do |attachable|
    attachable.variant :thumb, resize_to_limit: [100, 100]
  end
end

Intégration de FilePond

Configurons FilePond pour utiliser la fonctionnalité de Téléchargement Direct d’Active Storage :

// app/javascript/application.js
FilePond.setOptions({
  server: {
    process: (fieldName, file, metadata, load, error, progress, abort, transfer, options) => {
      const uploader = new DirectUpload(file, directUploadUrl, {
        directUploadWillStoreFileWithXHR: (request) => {
          request.upload.addEventListener(
            'progress',
            event => progress(event.lengthComputable, event.loaded, event.total)
          )
        }
      })

      uploader.create((errorResponse, blob) => {
        if (errorResponse) {
          error(`Quelque chose s'est mal passé : ${errorResponse}`)
        } else {
          const hiddenField = document.createElement('input')
          hiddenField.setAttribute('type', 'hidden')
          hiddenField.setAttribute('value', blob.signed_id)
          hiddenField.name = input.name
          document.querySelector('form').appendChild(hiddenField)
          load(blob.signed_id)
        }
      })

      return { abort: () => abort() }
    },
    fetch: {
      url: './filepond/fetch',
      method: 'POST'
    },
    revert: {
      url: './filepond/remove'
    },
    headers: {
      'X-CSRF-Token': document.head.querySelector("[name='csrf-token']").content
    }
  }
})

Pour les endpoints fetch et revert, nous avons besoin d’un contrôleur personnalisé :

# app/controllers/filepond_controller.rb
require 'open-uri'

class FilepondController < ApplicationController
  def fetch
    uri = URI.parse(raw_post)
    url = uri.to_s

    blob = ActiveStorage::Blob.create_and_upload!(
      io: URI.open(uri),
      filename: URI.parse(url).path.parameterize
    )

    if blob.persisted?
      redirect_to rails_service_blob_path(blob.signed_id, blob.filename)
    else
      head :unprocessable_entity
    end
  end

  def remove
    signed_id = raw_post
    blob = ActiveStorage::Blob.find_signed(signed_id)

    if blob
      blob.purge
      head :ok
    else
      head :not_found
    end
  end

  private

  def raw_post
    request.raw_post
  end
end

Et les routes :

# config/routes.rb
Rails.application.routes.draw do
  post 'filepond/fetch', to: 'filepond#fetch'
  delete 'filepond/remove', to: 'filepond#remove'
end

Conclusion

FilePond offre une excellente interface utilisateur pour fournir un retour d’information aux téléchargements des utilisateurs. L’intégrer avec Active Storage de Rails n’est pas difficile, mais nécessite un peu de personnalisation.

Ce tutoriel présente une façon d’intégrer FilePond et applique autant de Rails vanilla que possible. Dans la prochaine partie, j’utiliserai les méthodes d’implémentation ci-dessus et transformerai tout cela en une gem que nous pourrons réutiliser sans avoir à réimplémenter cela à chaque fois.