Khi nói về việc upload file trong một ứng dụng web, cách mà hầu như chúng ta đều nghĩ đến là sử dụng form với một thẻ <input type="file">
:
1 2 3 4 5 | <form action="/action_page.php"> <input type="file" name="pic" accept="image/*"> <input type="submit"> </form> |
Cách làm trên tạo ra một trải nghiệm upload file khá tẻ nhạt như thế này:
So với cách làm trên, thư viện Dropzone.js cung cấp cho người dùng một trải nghiệm upload file thân thiện hơn rất nhiều như thế này:
Thư viện này cung cấp một hệ thống settings rất rõ ràng và dễ để config lại từ view template, quá trình upload fiile, validate file, …. Các bạn có thể nghiên cứu cách sử dụng nó tại đây .
Còn ở bài viết lần này, mình sẽ hướng dẫn các bạn sử dụng dropzone.js và carrierwave để tạo một media gallery(lưu trữ file video, audio và image) cơ bản dựa trên framework rails.
Tạo rails app
Mình sẽ tạo một rails app đơn giản với thông tin như sau:
- Có một bảng Media để lưu lại thông tin các file tải lên.
- Có một trang để thực hiện tải file lên.
- Có một trang để hiển thị tất cả các file đã tải lên.
Đầu tiên, các bạn tạo một rails app cơ bản có cài 2 gem sau
1 2 3 4 | <span class="token comment">#Gemfile</span> gem <span class="token string">'carrierwave'</span><span class="token punctuation">,</span> <span class="token string">'~> 2.0'</span> gem <span class="token string">'dropzonejs-rails'</span> |
Tạo model Media và một FileUploader
1 2 3 4 5 6 7 8 | <span class="token keyword">class</span> <span class="token class-name">ImageUploader</span> <span class="token operator"><</span> <span class="token constant">CarrierWave</span><span class="token punctuation">:</span><span class="token punctuation">:</span><span class="token constant">Uploader</span><span class="token punctuation">:</span><span class="token punctuation">:</span><span class="token constant">Base</span> storage <span class="token symbol">:file</span> <span class="token keyword">def</span> store_dir <span class="token string">"uploads/<span class="token interpolation"><span class="token delimiter tag">#{</span>model<span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">.</span>to_s<span class="token punctuation">.</span>underscore<span class="token delimiter tag">}</span></span>/<span class="token interpolation"><span class="token delimiter tag">#{</span>mounted_as<span class="token delimiter tag">}</span></span>/<span class="token interpolation"><span class="token delimiter tag">#{</span>model<span class="token punctuation">.</span>id<span class="token delimiter tag">}</span></span>"</span> <span class="token keyword">end</span> <span class="token keyword">end</span> |
1 2 3 4 5 6 7 8 9 | class CreateSessions < ActiveRecord::Migration[5.1] def change create_table :medias do |t| t.string :data t.timestamps end end end |
1 2 3 4 | <span class="token keyword">class</span> <span class="token class-name">Media</span> <span class="token operator"><</span> <span class="token constant">ApplicationRecord</span> mount_uploader <span class="token symbol">:data</span><span class="token punctuation">,</span> <span class="token constant">FileUploader</span> <span class="token keyword">end</span> |
Bây giờ mình sẽ bắt đầu tạo trang tải file lên. Tạo đường dẫn cho trang đó trước trong file routes.rb
:
1 2 | resoure :medias, only: [:index, :new] |
Tạo controller với action new
tương ứng cho trang tải file lên:
1 2 3 4 5 | <span class="token keyword">class</span> <span class="token class-name">MediasController</span> <span class="token operator"><</span> <span class="token constant">ApplicationController</span> <span class="token keyword">def</span> <span class="token keyword">new</span> <span class="token class-name">end</span> <span class="token keyword">end</span> |
Tạo view cho trang tải file: new.html.erb
1 2 3 4 5 | <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>mydropzone<span class="token punctuation">"</span></span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>dropzone col-md-8<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>p</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>row text-center<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>Kéo, thả file vào đây hoặc<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>p</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>text-center<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>button</span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>button<span class="token punctuation">"</span></span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>upload_button<span class="token punctuation">"</span></span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>row<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>chọn nút này để tải file lên<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>button</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span> |
Và chúng ta sẽ được 1 cái view như thế này.
Bây giờ chúng ta sẽ xử lý upload file thông qua các hàm của thư viện dropzone trong 1 file javascript, mình đêt tên nó là upload.js
.
Đầu tiên, thêm file upload.js
vào asset pipelines:
1 2 | //= require upload |
Tạo file app/assets/javascripts/upload.js
1 2 3 4 5 6 | <span class="token keyword">var</span> dropzone <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Dropzone</span> <span class="token punctuation">(</span><span class="token string">"#mydropzone"</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> dictDefaultMessage<span class="token punctuation">:</span> <span class="token string">""</span><span class="token punctuation">,</span> uploadMultiple<span class="token punctuation">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span> url<span class="token punctuation">:</span> <span class="token string">"/medias"</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> |
Ở file js trên, ta khởi tạo một đối tượng Dropzone với các thuộc tính:
dictDefaultMessage
: Là message hiển thị trong các element có class là .dropzone. Ở đây mình để trống để custom lại view.uploadMultiple: true
. Cho phép bạn lựa chọn upload nhiều file.url:
là đường dẫn để thực hiện POST request sau khi file tải lên.
Hiện tại khi click vàodiv#mydropzone
sẽ có cửa sổ upload file hiện lên như thế này:
Nhưng mình muốn chỉ khi click vàobutton
chọn nút này để tải file lên
thì mới có cửa sổ chọn file hiện lên, nên mình sẽ custom thêm 1 thuộc tínhclickable
:
1 2 3 4 5 6 7 | <span class="token keyword">var</span> dropzone <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Dropzone</span> <span class="token punctuation">(</span><span class="token string">"#mydropzone"</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> dictDefaultMessage<span class="token punctuation">:</span> <span class="token string">""</span><span class="token punctuation">,</span> uploadMultiple<span class="token punctuation">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span> clickable<span class="token punctuation">:</span> <span class="token string">"#upload_button"</span><span class="token punctuation">,</span> url<span class="token punctuation">:</span> <span class="token string">"/medias"</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> |
Giờ thì có vẻ nó đã hoạt động theo đúng ý mình :
Bây giờ, mặc định khi bạn chọn file trong cửa sổ vừa hiện lên hoặc kéo/thả file vào div#dropzone
như thế này thì một request POST /medias
sẽ được thực hiện:
Và dữ liệu mà nó truyền lên server trong params sẽ như thế này:
1 2 | Parameters: {"file"=>{"0"=>#<ActionDispatch::Http::UploadedFile:0x00007f04db706b10 @tempfile=#<Tempfile:/tmp/RackMultipart20191120-8117-eh06il.png>, @original_filename="2.png", @content_type="image/png", @headers="Content-Disposition: form-data; name="file[0]"; filename="2.png"rnContent-Type: image/pngrn">, "1"=>#<ActionDispatch::Http::UploadedFile:0x00007f04db7068e0 @tempfile=#<Tempfile:/tmp/RackMultipart20191120-8117-1qp6iy5.png>, @original_filename="3.png", @content_type="image/png", @headers="Content-Disposition: form-data; name="file[1]"; filename="3.png"rnContent-Type: image/pngrn">, "2"=>#<ActionDispatch::Http::UploadedFile:0x00007f04db7067c8 @tempfile=#<Tempfile:/tmp/RackMultipart20191120-8117-pgbwmo.png>, @original_filename="4.png", @content_type="image/png", @headers="Content-Disposition: form-data; name="file[2]"; filename="4.png"rnContent-Type: image/pngrn">, "3"=>#<ActionDispatch::Http::UploadedFile:0x00007f04db706700 @tempfile=#<Tempfile:/tmp/RackMultipart20191120-8117-1d6xdsn.png>, @original_filename="5.png", @content_type="image/png", @headers="Content-Disposition: form-data; name="file[3]"; filename="5.png"rnContent-Type: image/pngrn">}} |
Bây giờ mình sẽ xử lý phía server để có thể lưu dữ liệu này vào trong db
1 2 3 4 5 6 7 8 9 10 11 | <span class="token keyword">class</span> <span class="token class-name">MediasController</span> <span class="token operator"><</span> <span class="token constant">ApplicationController</span> <span class="token keyword">def</span> <span class="token keyword">new</span> <span class="token class-name">end</span> <span class="token keyword">def</span> create params<span class="token punctuation">[</span><span class="token symbol">:file</span><span class="token punctuation">]</span><span class="token punctuation">.</span><span class="token keyword">each</span> <span class="token keyword">do</span> <span class="token operator">|</span>key<span class="token punctuation">,</span> file<span class="token operator">|</span> <span class="token constant">Media</span><span class="token punctuation">.</span>create data<span class="token punctuation">:</span> params<span class="token punctuation">[</span><span class="token symbol">:file</span><span class="token punctuation">]</span> <span class="token keyword">end</span> <span class="token keyword">end</span> <span class="token keyword">end</span> |
Như vậy là dữ liệu, sau khi được upload sẽ được lưu trực tiếp vào database. Giờ mình muốn xử lý , để ngay sau khi upload tất cả các file thành công thì redirect đến trang hiển thị list tất cả các ảnh .
Đầu tiên, ta xử lý sự kiện successmultiple
cho biến dropzone
như sau:
1 2 3 4 5 6 7 8 9 | var dropzone = new Dropzone ("#mydropzone", { dictDefaultMessage: "", uploadMultiple: true, url: "/medias", successmultiple: { window.location.href = '/medias'; } }); |
Với việc xử lý như trên, sau khi tất cả các file được upload thành công, trình duyệt sẽ tự động redirect sang trang hiển thị list /medias
.
Ta tạo cho trang hiển thị list:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | <span class="token keyword">class</span> <span class="token class-name">MediasController</span> <span class="token operator"><</span> <span class="token constant">ApplicationController</span> <span class="token keyword">def</span> <span class="token keyword">new</span> <span class="token class-name">end</span> <span class="token keyword">def</span> index <span class="token variable">@medias</span> <span class="token operator">=</span> <span class="token constant">Media</span><span class="token punctuation">.</span>all <span class="token keyword">end</span> <span class="token keyword">def</span> create params<span class="token punctuation">[</span><span class="token symbol">:file</span><span class="token punctuation">]</span><span class="token punctuation">.</span><span class="token keyword">each</span> <span class="token keyword">do</span> <span class="token operator">|</span>key<span class="token punctuation">,</span> file<span class="token operator">|</span> <span class="token constant">Media</span><span class="token punctuation">.</span>create data<span class="token punctuation">:</span> params<span class="token punctuation">[</span><span class="token symbol">:file</span><span class="token punctuation">]</span> <span class="token keyword">end</span> <span class="token keyword">end</span> <span class="token keyword">end</span> |
1 2 3 4 5 | <span class="token comment">#medias/index.html.erb</span> <span class="token operator"><</span><span class="token operator">%</span> <span class="token variable">@medias</span><span class="token punctuation">.</span><span class="token keyword">each</span> <span class="token keyword">do</span> <span class="token operator">|</span>media<span class="token operator">|</span> <span class="token string">%> <%= image_tag media.url, size: "100x100"%></span> <span class="token operator"><</span><span class="token operator">%</span> <span class="token keyword">end</span> <span class="token operator">%</span><span class="token operator">></span> |
Và cùng xem phần xử lý:
Validate ở client_size với dropzone.js
Ở đây, mình chỉ muốn upload các file với định dạng ".jpg, .png, .mp4, .mp3"
và file có size nhỏ hơn 10Mb thì có thể thêm 2 thuộc tính vào biến dropzone là acceptedFiles
và maxFilesize
1 2 3 4 5 6 7 8 9 10 11 | <span class="token keyword">var</span> dropzone <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Dropzone</span> <span class="token punctuation">(</span><span class="token string">"#mydropzone"</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> dictDefaultMessage<span class="token punctuation">:</span> <span class="token string">""</span><span class="token punctuation">,</span> uploadMultiple<span class="token punctuation">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span> url<span class="token punctuation">:</span> <span class="token string">"/medias"</span><span class="token punctuation">,</span> acceptedFiles<span class="token punctuation">:</span> <span class="token string">".jpg, .png, .mp4, .mp3"</span><span class="token punctuation">,</span> maxFilesize<span class="token punctuation">:</span> <span class="token number">10</span><span class="token punctuation">,</span> successmultiple<span class="token punctuation">:</span> <span class="token punctuation">{</span> window<span class="token punctuation">.</span>location<span class="token punctuation">.</span>href <span class="token operator">=</span> <span class="token string">'/medias'</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> |
Và error message sẽ được hiển thị như sau:
Ngoài ra, còn rất nhiều thuộc tính và sự kiện khác mà bạn có thể custom tại đây .
Bài viết của mình tạm dừng ở đây.
Tài liệu tham khảo: https://www.dropzonejs.com/