乐者为王

Do one thing, and do it well.

使用jQuery、Rails 4和Paperclip进行多文件上传

英文原文:https://5minutenpause.com/blog/2013/09/04/multiple-file-upload-with-jquery-rails-4-and-paperclip/

在最近的项目中,我需要一个方便的文件上传器。我想要多文件上传和进度条。我想要它们都能与Bootstrap一起工作。jQuery File Upload能够满足这些需求。初看上去它似乎不容易使用,在研究后我发现Ryan Bates做过一个关于jQuery文件上传railscast。不幸的是,这个railscast使用了Rails 3和旧的jQuery版本。所以我必须调整它。涉及这个主题的其它博文都开始于2012年甚至更早,在这里是新的Rails 4版本。

我们不是生活在真空中,所以会经常性地使用已有的想法(一切都是混合),因此我没有试图去提出一个完全原创的解决方案,下面我的实现是基于Ryans的工作。

更新:我为这篇文章创建了一个GitHub仓库。你可以将其用作本文中我使用的所有内容的完整工作副本。我使用这些提交来跟踪博客文章,以便你可以使用它们“重播”我的实现。如果有任何问题,请在GitHub打开一个issue,并在那里询问,或者就在本文后面发布你的评论。谢谢。

更新2:Paul Walker在评论中指出了如何解决Turbolinks的问题。如果你也有这种情况,请看他对Turbolinks的评论

更新3:如果你想知道如何在后台完成图片处理,请参阅我的这个主题的新帖子。我会给你展示如何使用Rails 4.2的Active Job和Delayed Job来实现后台处理。

Gem和资源文件

我们从Gemfile开始,添加jquery-fileupload-rails这个gem。

1
gem 'jquery-fileupload-rails'

使用bundle install安装所有gem。

安装后,你需要在你的application.js中引入以下这些文件:

1
2
3
4
5
//= require jquery
//= require jquery_ujs
//= require jquery-fileupload/basic
//= require jquery-fileupload/vendor/tmpl
//= require_tree .

我们在这里使用基本版本,并且包含jquery-fileupload/vendor/tmpl,所以我们可以选择渲染我们自己的模板。

视图

我们有个表单用于上传文件,并将JavaScript模板包含在文件的底部。有件事要注意:模板脚本必须是没有换行或空格的单行程序。否则jQuery会抱怨:Uncaught Syntax error, unrecognized expression: [object Object]。另一个解决方案是使用$.parseHTML();。接下来我会给你展示如何在uploads.js.coffee中做到这点。

1
2
3
4
5
6
7
8
9
10
11
12
<%= form_for Upload.new, :url => uploads_path, html: { multipart: true } do |f| %>
  <%= f.label :uploaded_file, t('.upload_new_file') %>
  <%= f.file_field :uploaded_file, multiple: true, name: 'upload[uploaded_file]' %>
  <%= f.submit t(:save), class: 'btn' %>
<% end %>

<% # jquery upload template # %>
<script id="template-upload" type="text/x-tmpl">
  <div class="upload">
    { %=o.name % }<div class="progress"><div class="bar" style="width: 0%"></div></div>
  </div>
</script>

因为要返回JavaScript脚本,所以:create成功后要渲染的文件如下:

1
2
3
4
5
<% if @upload.new_record? %>
  alert('Failed');
<% else %>
  $('ul.thumbnails').append("<%=j render partial: 'photosets/upload', locals: { upload: @upload } %>");
<% end %>

当你点击提交以上传你的图片时,实际发生的是你上传的每张图片都有多次提交。现在我们需要处理这些提交。我们在CoffeeScript文件里面做这些:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
jQuery ->
  $('#upload_uploaded_file').attr('name','upload[uploaded_file]')
  $('#new_upload').fileupload
    dataType: 'script'
    add: (e, data) ->
      types = /(\.|\/)(gif|jpe?g|png|mov|mpeg|mpeg4|avi)$/i
      file = data.files[0]
      if types.test(file.type) || types.test(file.name)
        data.context = $(tmpl("template-upload", file))
        $('#new_upload').append(data.context)
        data.submit()
      else
        alert("#{file.name} is not a gif, jpg or png image file")
    progress: (e, data) ->
      if data.context
        progress = parseInt(data.loaded / data.total * 100, 10)
        data.context.find('.bar').css('width', progress + '%')

这里我们检查文件的类型是图片还是电影。否则,我们将向用户显示不允许该文件的警告。如果文件被允许,我们使用文件的数据渲染模板,并将其附加到我们的图片列表中(这里没有显示代码——你可以很容易地弄明白)。然后我们提交文件进行实际上传并保存到数据库。此外,我们显示每个上传文件的进度条。

upload.js.coffee中的第2行用于将upload_file的名称从数组改为单个上传(从upload[uploaded_file][]upload[uploaded_file])。否则,上传数组会使Paperclip抛出错误Paperclip::AdapterRegistry::NoHandlerError。你可以使用file_field的name属性设置值,但那对我来说无法可靠地工作。

我在之前说过,你必须避免模板中的换行符。如果你将第9行改为:

1
data.context = $($.parseHTML($(tmpl("template-upload", file)))[1])

应该可以保持你的换行。我在Stack Overflow发现这个,但没有尝试过。但它看起来应该没问题。

控制器动作很简单:

1
2
3
def create
  @upload = Upload.create(upload_params)
end

这是个简单的实现。你可以做的更多。仔细看看jQuery File Upload的文档。如果你有任何问题,可以在twitter或app.net上询问我,我很乐意为你提供帮助。谢谢阅读。

Comments