ExtJs: легкий способ мультизагрузки и валидации файлов (используем HTML5 File API)

от автора

Если вам нужно валидировать загружаемые пользователем файлы не только на сервере, но и на клиенте, или вы хотите позволить пользователю выбрать сразу несколько файлов для загрузки (multiple=”multiple”) то, скорее всего, вы столкнетесь с некоторыми трудностями при использовании Ext.form.field.File.

Суть проблемы

Проверить расширение загружаемого файла, если пользователь выбрал только один файл, не составит труда — просто объявляем vtype и смотрим value input-а:

Ext.apply(Ext.form.field.VTypes, {     file: function(val, field) {         var types = ['rtf', 'pdf', 'doc'],             ext = val.substring(val.lastIndexOf('.') + 1);         if(Ext.Array.indexOf(types, ext) === -1) {             return false;         }     }     ,fileText: 'Invalid file' }); 


Проблемы начинаются тогда, когда файлов несколько, и/или вам нужно проверить «вес» файла. Все дело в том, что в коде выше field ссылается не на input, в котором «лежат» выбранные файлы, а на table, в которой лежит input.
Достучаться до самого input-а достаточно легко:

var input = Ext.get(field.id+'-fileInputEl'); - и мы можем работать с input.files. 

Если кому-то непонятно, то строка выше на vanilla.js будет звучать так:

var input = document.getElementById(field.id+'-fileInputEl'); 

Наверное ванилла даже сэкономила бы несколько спичек, но я не очень люблю когда начинается каша, а разницы в работе мой организм не ощущает.

Если файл один, и вы не против использования не нативного input-а загрузки файлов, то проблема решена (можно сказать ее и не было) — получаем поле загрузки файла и с помощью File API валидируем вес файла. Иначе — некоторые проблемы остаются:

1. При выборе нескольких файлов (после некоторых манипуляций) поле на форме покажет вам только первый выбранный файл.
2. Если вы хотите нативный input — его атрибут size будет равен 1, после установки inputType: ‘file’ в конфигурации поля, и установка свойства size: 50 вам не поможет.

К вопросу об использовании нативных элементов браузера — нет никакого желания холливарить на эту тему — просто я предпочитаю их использовать, если элемент обладает каким нибудь поведением, и если есть такая возможность. Просто для пользователя это привычнее — он знает чего ожидать от этой штуки.

Хотя, если немного отвлечься от темы, когда ФФ в одном из недавних обновлений перенял Chrome-style поведение атрибута placeholder (текст перестал исчезать при фокусе на поле) — меня это слегка покоробило. Я не понимаю в чем плюсы такого поведения — всегда хочется стереть этот текст перед вводом.

Решение

Если погуглить, интернет, в большинстве случаев, посоветует вам валидировать файлы только на сервере, или использовать flash. Если хочется и можется — можно так и поступить. Еще я видел ux, который умеет грузить много файлов и валидировать их — тоже может быть вариантом.

А можно обойтись буквально несколькими строками, и использовать File API. При этом у нас пока отваливаются всякие IE, но лично я особых переживаний по этому поводу не испытываю — возможность загрузки все равно остается, а на сервере все равно валидировать. К тому же, если вы используете экст и пишите более-менее сложное приложение, то, скорее всего, вы можете диктовать браузеры конечному пользователю.

Поступим следующим образом — унаследуемся от textfield, выставим ему inputType: ‘file’, позволим устанавливать в конфигурации size input-а и атрибут multiple. Назначим всему этому делу alias для «короткого» доступа (xtype). Правила валидации опишем vtype-ом. Все это позволит использовать поле с нужным поведением много раз в любой вьюхе приложения, и гибко его конфигурировать (например, задавать разрешенные типы файлов и их максимальный размер).

Описываем поле:

    Ext.define('fileupload',{         extend: 'Ext.form.field.Text'         ,alias: 'widget.fileupload' // теперь мы можем написать xtype: 'fileupload'         ,inputType: 'file'         ,listeners: {             render: function (me, eOpts) {                 var el = Ext.get(me.id+'-inputEl');                 el.set({                     size: me.inputSize || 1                 });                 if(me.multiple) {                     el.set({                         multiple: 'multiple'                     });                 }             }         }     }); 

Тут даже не знаю, нуждается ли что-то в пояснениях. Если в двух словах — после «отрисовки» нативного input-а, для него устанавливается атрибут size (установленный при конфигурации или по дефолту — 1) и атрибут multiple, если он выставлен в конфигурации. Таким образом, теперь у нас есть нативный конфигурируемый input.

Описываем валидатор:

    Ext.apply(Ext.form.field.VTypes, {         file: function(val, field) {             var input, files, file             ,acceptSize = field.acceptSize || 4096 //  Максимальный вес файлов             ,acceptMimes = field.acceptMimes || ['rtf', 'pdf', 'doc', 'xls', 'xlsx', 'zip', 'rar']; // Разрешенные типы              input = Ext.get(field.id+'-inputEl');             files = input.getAttribute('files');             if ( ! files || ! window.FileReader) {                 return true; // Мы имеем дело с неправильным браузером. Из вредности можно вернуть и false             } 	             for(var i = 0, l = files.length; i < l; i++) { // смотрим размеры файлов                 file = files[i];                 if(file.size > acceptSize * 1024) {                     this.fileText = (file.size / 1048576).toFixed(1) + ' MB: invalid file size ('+(acceptSize / 1024).toFixed(1)+' MB max)';                     return false;                 }                  var ext = file.name.substring(file.name.lastIndexOf('.') + 1);                 if(Ext.Array.indexOf(acceptMimes, ext) === -1) { // смотрим расширения файлов                     this.fileText = 'Invalid file type ('+ext+')';                     return false;                 }             }             return true;         }     }); 

Тут, думаю, тоже все достаточно понятно. Просто устанавливаем дефолтные типы/размер файлов, получаем input и пробегаемся по его files — проверяем их тип/размер. Все очень просто.

Теперь мы можем грузить и валидировать файлы, описывая поле формы следующим образом:

        {             xtype: 'fileupload'             ,vtype: 'file'             ,multiple: true // разрешаем выбрать несколько файлов             ,acceptMimes: ['doc', 'xls', 'xlsx', 'pdf', 'zip', 'rar'] // Устанавливаем разрешенные типы             ,acceptSize: 2048 // Максимальный размер             ,inputSize: 76 // Атрибут size             ,fieldLabel: 'File <span class="gray">(doc, xls, xlsx, pdf, zip, rar; 2 MB max)</span>'             ,msgTarget: 'under'             ,name: 'filesToUpload[]'         } 

В действии все это хозяйство можно посмотреть тут — все собрано в один файл для наглядности, хотя вообще я люблю мвц 4го экста.

P.S.

Долго думал стоит ли мне тут что-то писать, хотя периодически возникает такое желание — хабр таки экспертная тусовка, а я не считаю себя js/extJs джедаем — просто по долгу службы пишу приложения которые работают. К тому же писатель из меня не очень, нету во мне креативности, а экст достоин куда более искусного рассказчика чем я. Да и вообще — экст на хабре мелькает достаточно редко, не знаю, есть ли тут к нему широкий интерес.
Потом дай, думаю, рискну. Авось — кому-то, да пригодится. Картинок нет специально, т.к. я считаю что картинки нужны тогда, когда нужно что-то изобразить, а не когда хочтеся привлечь внимание, а тут изображать ничего не требуется. Ну, если что не так — вы знаете что делать.

ссылка на оригинал статьи http://habrahabr.ru/post/158513/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *