HTML5 File API

html5-file-api.png

Giới thiệu

Trong các ứng dụng web hầu hết chúng ta cũng đã từng làm việc với files, ví dụ chức năng cập nhật Avatar của User chẳng hạn. Một flow đơn giản đó là tạo một input với type=file, người dùng sẽ chọn hình ảnh muốn upload, sau đó ảnh sẽ được upload lên server và lưu thông tin database cho đường dẫn của ảnh.

Tuy nhiên đôi khi không đơn giản như vậy, chúng ta có thể gặp phải những yêu cầu như: tôi muốn xem thumbnail của ảnh trước khi được upload lên Server? tôi muốn chỉnh sửa và crop vùng ảnh mong muốn? tôi muốn cho người dùng xem thông tin của ảnh trước khi họ chọn upload?...

Một trong những cách làm truyền thống đó là:

  • Tạo một request upload ảnh sau khi User chọn sử dụng AJAX.
  • Xử lý trên Server và trả về các thông tin cần thiết để hiển thị phía Client.
  • Thêm một số thao tác xử lý hiển thị bằng Javascript.
  • Cho phép người dùng nhập thêm một số thông tin như: tiêu đề, mô tả,...
  • Tạo một request để cập nhật thay đổi thông tin trên Server.

Ta có thể thấy chúng ta phải request lên Server hai lần. Đôi khi người dùng chọn ảnh xong nhưng họ không ưng ý và muốn chọn ảnh khác thì việc upload ảnh chọn trước đó là thừa và chiếm bộ nhớ trên Server. Lúc đó ta sẽ phải chọn giải pháp giải phóng (xóa ảnh) hoặc là để kệ đó ảnh thừa cũng được chẳng đáng bao nhiêu.

Điều bất cập mà tôi vừa đề cập đến chính là lý do tôi muốn giới thiệu với các bạn về HTML5 File API trong bài viết này.

HTML5 File API là gì?

HTML5 File API cung cấp cho chúng ta các phương thức tương tác với file ở phía Client để truy cập các thông tin cơ bản của file. Có thể hiểu một cách đơn giản là chúng ta có thể tương tác với các thông tin của file, render ở ngay trình duyệt mà không cần bất kỳ một thao tác upload lên Server nào.

Trình duyệt hỗ trợ

Hãy cùng nhau xem xét qua các trình duyệt hỗ trợ HTML5 File API trước khi đi vào tìm hiểu những phương thức mà nó cung cấp: browser_support.png (Nguồn: http://caniuse.com/#feat=fileapi)

Trong nội dung bài viết này tôi chỉ lấy các ví dụ tương tác với file ảnh để demo, với các định dạng file khác có thể sẽ có xử lý riêng các bạn vui lòng tìm hiểu thêm (bow).

HTML5 File Objects

Các đối tượng chính cần chú ý khi làm việc với HTML5 File API:

  • File: Object File chứa các thông tin cơ bản của File.
  • FileList: Danh sách các File Object.
  • FileReader: Object dùng để tương tác với File, bao gồm các phương thức và các sự kiện xử lý (Event Handler)

Đối tượng FileFileList

Hãy bắt đầu với một ví dụ đơn giản, tôi có một input dạng file và cho phép chọn nhiều file:

<input type=file id="files" multiple />

Đồng thời tôi đưa ra đoạn code javascript để tương tác với input này:

function handleFileSelect(event) {
  // Lấy danh sách các files vừa chọn, nhận được đối tượng `FileList`
  var files = event.target.files;

  console.log(files);
}

// Lắng nghe sự kiện `change` của input và gọi hàm `handleFileSelect` để xử lý
document.getElementById("files").addEventListener('change', handleFileSelect, false);

Chúng ta lắng nghe sự kiện thay đổi (change) của input và hàm handleFileSelect đảm nhận xử lý kết quả sau khi thay đổi. Ta sẽ nhận được danh sách files đã chọn hết sức đơn giản và thao tác cuối cùng là ghi log ra màn hình console để xem kết quả. Ta có các kết quả như sau:

  • Trường hợp chọn 1 file:

single_file_upload.png

  • Trường hợp chọn nhiều file:

multiple_files_upload.png

Việc lấy ra thông tin file từ phía Client các bạn có thể thấy hết sức đơn giản. Ta đã lấy được đối tượng FileList chứa danh sách các đối tượng File như tôi đã đề cập ở phần trước. Trong đối tượng File ta có thể lấy các thông tin cơ bản của file như:

  • lastModified: thời gian cuối cùng chỉnh sửa (dưới dạng unix time).
  • lastModifieđate: thời gian cuối cùng chỉnh sửa (dưới dạng DateTime của Javascript).
  • name: tên original của file sau khi chọn.
  • size: kích thước file (có thể dùng để validate luôn phía client nếu hệ thống của bạn có yêu cầu giới hạn kích thước file upload).
  • type: định dạng file (có thể dùng để validate phía client nếu hệ thống của bạn yêu cầu chỉ cho phép định dạng file ảnh, hoặc file csv,...).

Đối tượng FileReader

Đến đây chúng ta đã lấy được thông tin đối tượng FileList, qua đó lấy được thông tin các đối tượng File đơn lẻ. Ta sẽ truyền đối tượng File vào đối tượng FileReader xử lý để lấy được thông tin local url và hiển thị lên trình duyệt thông qua thuộc tính src của thẻ img.

FileReader làm nhiệm vụ nhận đầu vào là đối tượng File, đọc thông tin và lưu vào bộ nhớ. FileReader fire sự kiện onload khi hoàn thành load file và sau đó chúng ta có thể truy cập dữ liệu của file thông qua thuộc tính result.

FileReader Properties

Các thuộc tính được phép truy cập khi sử dụng đối tượng FileReader:

  • FileReader.error: trả về đối tượng DomeError khi có bất kỳ lỗi gì xảy ra trong quá trình đọc file.
  • FileReader.readyState: trả về số nguyên cho ta biết trạng thái hiện tại của FileReader, ta sẽ nhận được một trong những giá trị sau:
    • EMPTY: 0: Không có dữ liệu nào được load.
    • LOADING: 1: Dữ liệu đang được load.
    • DONE: 2: Dữ liệu đã được load thành công.
  • FileReader.result: trả về nội dung của file được truyền vào FileReader, tùy từng phương thức đọc file mà nội dung sẽ được trả về các định dạng khác nhau. Các phương thức sẽ được giới thiệu ngay dưới đây.

FileReader Methods

FileReader cung cấp 4 phương thức để đọc file (bất đồng bộ - asynchronously):

  • FileReader.readAsBinaryString(Blob|File): thuộc tính result chứa dữ liệu của file dưới dạng chuỗi nhị phân (binary string). Mỗi byte được biểu diễn bởi một số nguyên trong khoảng từ 0 đến 255.
  • FileReader.readAsText(Blob|File, opt_encoding): thuộc tính result chứa dữ liệu file dưới dạng chuỗi ký tự (text string). Mặc định chuỗi ký tự được decoded dạng 'UTF-8', chúng ta có thể thay đổi định dạng format này bằng việc truyền vào opt_encoding.
  • FileReader.readAsDataURL(Blob|File): thuộc tính result chứa dữ liệu file dưới dạng data URL.
  • FileReader.readAsArrayBuffer(Blob|File): thuộc tính result chứa dữ liệu file dưới dạng đối tượng ArrayBuffer.

FileReader Event Handlers

Các sự kiện xử lý mà FileReader cung cấp:

  • FileReader.onabort: tùy biến hàm xử lý khi chủ động hủy quá trình đọc file (có thể tưởng tượng là chọn xong file rồi nhưng có button để chủ động bấm hủy để không đọc thông tin các file đã chọn nữa).
  • FileReader.onerror: tùy biến hàm xử lý khi trong quá trình đọc file có lỗi nào đó xảy ra.
  • FileReader.onload: tùy biến hàm xử lý khi quá trình đọc file hoàn thành và đảm bảo không có lỗi gì xảy ra.
  • FileReader.onloadstart: tùy biến hàm xử lý khi bắt đầu quá trình đọc file.
  • FileReader.onloadend: tùy biến hàm xử lý khi quá trình đọc file kết thúc (có thể quá trình đọc file thành công hoặc thất bại).
  • FileReader.onprogress: tùy biến hàm xử lý khi đang trong quá trình đọc file.

Demo

Để làm quen hơn với đối tượng FileReader chúng ta sẽ cùng nhau xây dựng một Demo nho nhỏ sử dụng phương thức và thuộc tính mà tôi đã mô tả ở trên. Bài toán đặt ra là:

  • Đầu vào: Danh sách các file chọn từ máy của Client.
  • Đầu ra: Hiển thị thông tin chi tiết của File cho User.
  • Yêu cầu:
    • Không thực hiện upload các file lên Server.
    • Với những file ảnh thì hiển thị thêm thumbnail.

Các thành phần HTML, CSS, JS được thiết kế như sau:

<input type=file id="files" multiple />
<!-- Div trống phục vụ hiển thị danh sách các files sau khi chọn  -->
<div id="files-list"></div>
// Hàm thực hiện việc xử lý files sau khi User chọn
function handleFileSelect(event) {
  // Lấy danh sách các files mà User chọn
  var files = event.target.files;

  for (var i = 0; i < files.length; i++) {
    var file = files[i];

    // Duyệt từng file trong danh sách và render vào DIV trống đặt sẵn
    renderFileInfo(file);
  }
}

// Hàm thực hiện tạo HTML cho việc hiển thị chi tiết một file nào đó truyền vào
function renderFileInfo(file) {
  // Khởi tạo đối tượng `FileReader`
  var fileReader = new FileReader();

  // Hàm xử lý sự kiện `onload` sau khi file được load
  fileReader.onload = function(event) {
    // Truy cập vào thuộc tính `result` để lấy được dữ liệu File dưới dạng `data URL`
    var imageUrl = event.target.result,
        fileDetailHtml = '',
        detailElement = document.createElement('div');

    // Trường hợp là file ảnh thì hiển thị thêm Thumbnail
    if (file.type.match('image.*')) {
      fileDetailHtml += '<img class="thumbnail" src="' + imageUrl + '" />';
    }

    // Lấy các thông tin liên quan khác của File
    fileDetailHtml += '<ul class="file-info">'
    fileDetailHtml += '<li><span class="title">Name</span>: ' + file.name + '</li>';
    fileDetailHtml += '<li><span class="title">Size</span>: ' + file.size + ' bytes</li>';
    fileDetailHtml += '<li><span class="title">Last Modified</span>: ' + (file.lastModifiedDate ? file.lastModifiedDate.toLocaleDateString() : 'N/A') + '</li>';
    fileDetailHtml += '<li><span class="title">Type</span>: ' + file.type + '</li>';
    fileDetailHtml += '</ul>';

    detailElement.className = 'file-detail';
    detailElement.innerHTML = fileDetailHtml;

    // Sau khi chuẩn bị xong thông tin thì ghi dữ liệu HTML vào DIV trống chuẩn bị sẵn
    document.getElementById('files-list').appendChild(detailElement);
  }

  // Truyền `File` vào đối tượng `FileReader` và chỉ thị đọc ra dữ liệu dưới dạng `data URL`
  // Sau khi load thành công sẽ thực hiện đoạn code trong `onload` function phía trên
  fileReader.readAsDataURL(file);
}

// Gắn sự kiện xử lý files sau khi User chọn
document.getElementById("files").addEventListener('change', handleFileSelect, false);

Thêm chút Style nho nhỏ để trình bày dữ liệu:

#files-list .file-detail {
  border: 1px solid grey;
  padding: 10px;
  border-radius: 3px;
  float: left;
  margin: 0 10px 0 0;
}
#files-list .file-detail .thumbnail {
  height: 200px;
}
#files-list .file-detail ul li span.title {
  font-weight: bold;
}

Và chúng ta có kết quả như sau: multiple_upload_detail.png

Như các bạn thấy thì với những file ảnh chúng ta đã có thumbnail và thông tin chi tiết của ảnh, còn những định dạng khác thì không. Các bạn có thể thử nghiệm Demo tại đây.

Ngoài ra nếu bạn có nhu cầu upload file lên Server sau khi Preview (sử dụng PHP) thì có thể tham khảo thêm tại đây.

Kết luận

Như vậy qua bài viết này tôi đã giới thiệu với các bạn về HTML5 File API để có thể tương tác với file ngay từ phía Client mà không cần phải upload file lên Server và xử lý trích xuất thông tin phía Server nữa, giúp giảm tải công việc xử lý thông tin ảnh phía Server.

Bạn có thể thực hiện các thao tác validate định dạng file, validate kích thước file, preview file, tham gia vào quá trình đọc file phía Client thông qua các thuộc tính, phương thức và sự kiện mà FileReader cung cấp.

Hy vọng bài viết giúp bạn xử lý được phần nào đó trong qúa trình xây dựng ứng dụng với các chức năng tương tác file thân thiện hơn với người dùng.

Tham khảo

  1. HTML5 File API
  2. Use the HTML5 File API to Work with Files Locally in the Browser
  3. Reading files in JavaScript using the File APIs
  4. FileReader Definition