Если вам нужно валидировать загружаемые пользователем файлы не только на сервере, но и на клиенте, или вы хотите позволить пользователю выбрать сразу несколько файлов для загрузки (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 джедаем — просто по долгу службы пишу приложения которые работают. К тому же писатель из меня не очень, нету во мне креативности, а экст достоин куда более искусного рассказчика чем я. Да и вообще — экст на хабре мелькает достаточно редко, не знаю, есть ли тут к нему широкий интерес.
Потом дай, думаю, рискну. Авось — кому-то, да пригодится. Картинок нет специально, т.к. я считаю что картинки нужны тогда, когда нужно что-то изобразить, а не когда хочтеся привлечь внимание, а тут изображать ничего не требуется. Ну, если что не так — вы знаете что делать.
Автор: werdender