12/08/2018, 14:43

Xây dựng form Multiple File Upload sử dụng gem cocoon và CarrierWave

Lời mở đầu Ở Tutorial này mình sẽ hướng dẫn các bạn xây dựng form upload 1 lúc nhiều ảnh sử dụng kỹ thuật nested attributes . Ích lợi của tutorial này : Upload nhiều file cùng lúc Thêm hoặc remove input fields một cách thoải mái Xem các ảnh trước khi được upload Khi xảy ra lỗi có thể ...

Lời mở đầu

Ở Tutorial này mình sẽ hướng dẫn các bạn xây dựng form upload 1 lúc nhiều ảnh sử dụng kỹ thuật nested attributes .

Ích lợi của tutorial này :

  • Upload nhiều file cùng lúc
  • Thêm hoặc remove input fields một cách thoải mái
  • Xem các ảnh trước khi được upload
  • Khi xảy ra lỗi có thể lưu lại cái field đã nhập hay cái ảnh đã chọn.

Chuẩn bị

Ta có 2 bảng như sau :
    class Image < ApplicationRecord
  belongs_to :target, polymorphic: true

  mount_uploader :image, ImageUploader,
    reject_if: proc{ |param| param[:image].blank? && param[:image_cache].blank? &&
    param[:id].blank? }, allow_destroy: true
end

    class Project < ApplicationRecord
  belongs_to :category

      has_many :images
      
      accepts_nested_attributes_for :images, allow_destroy: true

Gem

gem "carrierwave" gem "cocoon" // hỗ trợ nested form

Config

    app/assets/js/application.js
//= require cocoon

Chúng ta bắt tay vào code

Chúng ta lấy params ở view như sau :

 // Projects_Controller
 def project_params
    params.require(:project).permit images_attributes: [:id, :image, :image_cache, :_destroy]
  end

Tiếp theo ở view ta tạo 1 view là new.htmt.erb:

    <%= form_for @project do |f| %>
    ...
    
    <div class="form-group">
      <%= f.label :images, class: "col-md-4 control-label"%>
      <div class="col-md-4">
        <table class="user-photo-form">
          <%= f.fields_for :images do |image| %>
            <%= render "image_fields", f: image %>
          <% end %>
          <%= link_to_add_association (t "add_a_photo"), f, :images, class: "btn btn-default" %>
        </table>
      </div>
    </div>
    
    ...
    <% f.submit "Save "%>

Sau đó ta tạo 1 Partial _image_fields.html.erb (image là 1 trường của bảng 1 images )

    <tr class="nested-fields"> // class này dùng để gem cocoon nhận biết đây là phần để nó append vào mỗi khi ấn nút
  <td>
    <%= f.file_field :image %>
    <%= f.hidden_field :image_cache, value: f.object.image_cache %>
  </td>
  <td class="thumb"> // để js nhận biết append vào các preview image
    <% if f.object.image.url.present? %>
      <%= image_tag f.object.image.url %>
    <% end %>
  </td>
  <td>
    <%= link_to_remove_association (t "remove"), f %>
  </td>
</tr>

Phần cuối cùng , chúng ta chỉ cần tạo 1 file project.js.coffee như sau :

$ ->
  onAddFile = (event) ->
    file = event.target.files[0]
    url = URL.createObjectURL(file)
    thumbContainer = $(this).parent().siblings('td.thumb')
    if thumbContainer.find('img').length == 0
      thumbContainer.append '<img src="' + url + '" />'
    else
      thumbContainer.find('img').attr 'src', url

  $('input[type=file]').each ->
    $(this).change onAddFile
  $('body').on 'cocoon:after-insert', (e, addedPartial) ->
    $('input[type=file]', addedPartial).change onAddFile
    // Nói vs cocoon nơi để insert partial
  $('a.add_fields').data 'association-insertion-method', 'append'
  $('a.add_fields').data 'association-insertion-node', 'table.user-photo-form tbody'

Vậy là xong , rất đơn giản phải ko             </div>
            
            <div class=

0