如何在Rails的Active Storage中使用FilePond
FilePond是一个由Rik Schennink编写的漂亮的JavaScript文件上传处理库。
我在各种Ruby on Rails项目中使用过这个库,当我想要流畅的上传体验配合出色的UI视觉效果时。
在本文中,我将介绍如何将FilePond与Rails集成,并在过程中描述和解释我的一些技术选择。本教程也假设你对Active Storage有基本的了解。如果你不熟悉Rails的这部分,请查看官方Rails指南:https://guides.rubyonrails.org/active_storage_overview.html。
本教程的演示代码位于https://github.com/Code-With-Rails/filepond-demo。我建议你克隆该仓库并从那里运行演示。
我们要构建什么
要将FilePond与我们的应用集成,有两部分:
- 首先,我们需要添加FilePond JavaScript库并在文件输入HTML标签上启用它。
- 其次,我们需要将FilePond与Rails应用集成。具体来说,我们希望Active Storage处理文件上传。
为了完成第一部分,我们将使用原生JavaScript。对于第二部分,我们将利用现有的Active Storage JavaScript库并启用直接上传。为了适应FilePond所需的一些特定服务器端点,我们将创建一个自定义控制器。
FilePond安装
对于本教程,我们将从Rails 7.0.x应用程序开始。这意味着我们将使用importmap-rails来添加JavaScript依赖。
bin/bundle add importmap-rails
bin/rails importmap:install
接下来,我们需要使用以下命令添加FilePond依赖:
bin/rails importmap pin filepond
我们想要预加载FilePond,所以在config/importmap.rb文件中,像这样修改文件:
# 之前
pin 'filepond', to: 'https://ga.jspm.io/npm:filepond@4.30.4/dist/filepond.js'
# 之后
pin 'filepond', to: 'https://ga.jspm.io/npm:filepond@4.30.4/dist/filepond.js', preload: true
FilePond基础
要初始化FilePond,我们首先需要有一个HTML文件输入元素。我们的表单看起来像这样:
<%= form_with model: @user, url: update_avatar_path, method: :post do |f| %>
<%= f.label :avatar, '更新你的头像' %>
<!-- 我们将把这个输入转换成pond -->
<%= f.file_field :avatar, class: 'filepond', direct_upload: true %>
<%= f.button '更新' %>
<% end %>
相应的JavaScript代码看起来像这样:
// application.js
const input = document.querySelector('.filepond')
FilePond.create(input)
这是让FilePond将简单的文件输入标签转换为小部件所需的最低要求。
针对我们在这里构建的内容,我们将实现以下功能:
- 配置FilePond使用Active Storage的JavaScript库执行直接上传到云提供商
- 配置FilePond和我们的应用程序允许仅使用远程URL上传
- 配置我们的应用程序在用户点击FilePond小部件中的取消(撤销)时清除未附加的文件(blob)
简单的Rails应用程序
我们首先设置数据模型,以便Active Storage可以关联文件附件。
bin/rails g model User name:string
bin/rails db:migrate
在user.rb文件中,让我们将模型更新为:
class User < ApplicationRecord
has_one_attached :avatar do |attachable|
attachable.variant :thumb, resize_to_limit: [100, 100]
end
end
集成FilePond
让我们配置FilePond使用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(`出错了:${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
}
}
})
对于fetch和revert端点,我们需要一个自定义控制器:
# 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
以及路由:
# config/routes.rb
Rails.application.routes.draw do
post 'filepond/fetch', to: 'filepond#fetch'
delete 'filepond/remove', to: 'filepond#remove'
end
结论
FilePond为用户上传提供了出色的用户界面反馈。将它与Rails的Active Storage集成并不困难,但需要一些自定义。
本教程展示了一种集成FilePond的方法,并尽可能应用原生Rails。在下一部分中,我将使用上述实现方法,将所有这些转换为一个gem,这样我们可以重用而无需每次都重新实现。