fileApi – Stepan Suvorov Blog https://stepansuvorov.com/blog Release 2.0 Sun, 07 Sep 2014 09:09:02 +0000 en-US hourly 1 https://wordpress.org/?v=6.3.1 Пишем свой Uploader с нуля на javascript используя FileApi. Часть5. +AngularJS https://stepansuvorov.com/blog/2014/09/%d0%bf%d0%b8%d1%88%d0%b5%d0%bc-%d1%81%d0%b2%d0%be%d0%b9-uploader-%d1%81-%d0%bd%d1%83%d0%bb%d1%8f-%d0%bd%d0%b0-javascript-%d0%b8%d1%81%d0%bf%d0%be%d0%bb%d1%8c%d0%b7%d1%83%d1%8f-fileapi-%d1%87%d0%b0-5/ https://stepansuvorov.com/blog/2014/09/%d0%bf%d0%b8%d1%88%d0%b5%d0%bc-%d1%81%d0%b2%d0%be%d0%b9-uploader-%d1%81-%d0%bd%d1%83%d0%bb%d1%8f-%d0%bd%d0%b0-javascript-%d0%b8%d1%81%d0%bf%d0%be%d0%bb%d1%8c%d0%b7%d1%83%d1%8f-fileapi-%d1%87%d0%b0-5/#comments Sun, 07 Sep 2014 09:09:02 +0000 http://stepansuvorov.com/blog/?p=367 Continue reading ]]> В этой части хочу рассказать о том, как можно все эти операции с файлами завернуть в AngularJS.

Рекомендую пролистать предыдущие части перед началом разбора этой:

Разберем несколько ключевых поментов подключения FileApi к AngularJS:

  • проблема ng-model и input file
  • сервис для FileApi
  • превью директива

Проблема ng-model

Проблема заключается в том, что ng-model не работает с input-file, то есть он не обновит связанную модель, когда через компонент буду выбраны файлы.

Более подробно о проблеме можно почитать тут.

Приходиться писать свое решение – директиву расширяющую возможности ng-model для данного элемента. Что должна делать директива? Обновлять модель по событию change (это родное браузерное событие, которое нормально отрабатывает с input-file), то есть:

[javascript]
directive(‘fileChanged’, function() {
return {
restrict: ‘A’,
link: function($scope, element) {

element.bind(‘change’, function(event) {
//…
});
}
};
});
[/javascript]

теперь нам нужно подгрузить модель, для этого используем свойство директивы require:

[javascript]
directive(‘fileChanged’, function() {
return {
restrict: ‘A’,
require: ‘?ngModel’,
link: function($scope, element, attrs, ngModel) {
if (!ngModel) {
return;
}

element.bind(‘change’, function(event) {
//…
});
}
};
});
[/javascript]

Используем “?” при загрузке, чтобы избежать ошибки(иметь возможность самим обработать) при отсутствии ng-model директивы на элементе.
Зададим значение модели используя метод $setViewValue и обновим скоуп используя $scope.$apply:

[javascript]
directive(‘fileChanged’, function() {
return {
restrict: ‘A’,
require: ‘?ngModel’,
link: function($scope, element, attrs, ngModel) {
if (!ngModel) {
return;
}

element.bind(‘change’, function(event) {
ngModel.$setViewValue(event.target.files[0]);
$scope.$apply();
});
}
};
[/javascript]

И последний штрих – переопределим метод $render для ngModel на angular.noop, чтобы, когда мы обновляли значение модели, он не пытался ничего обновить во вью(мы сами контролируем этот процесс):

[javascript]
directive(‘fileChanged’, function() {
return {
restrict: ‘A’,
require: ‘?ngModel’,
link: function($scope, element, attrs, ngModel) {
if (!ngModel) {
return;
}

ngModel.$render = angular.noop;

element.bind(‘change’, function(event) {
ngModel.$setViewValue(event.target.files[0]);
$scope.$apply();
});
}
};
});
[/javascript]

Пример полностью.

Сервис для FileApi

Так как работаем с AngularJS, то использовать FileReader напрямую не комильфо: необходимо создать AngularJS-сервис, который будет оберткой над window.FileReader:

[javascript]
factory(‘FileReader’, function($window) {

if (!$window.FileReader) {
throw new Error(‘Browser does not support FileReader’);
}

function readAsDataUrl(file) {
var reader = new $window.FileReader();

reader.onload = function() {
//…
};

reader.onerror = function() {
//…
};

reader.readAsDataURL(file);

return reader;
}

return {
readAsDataUrl: readAsDataUrl
};
}
[/javascript]

как-то так. Обязательно через $window, а не window, как минимум для того чтобы потом удобнее было покрывать юнит-тестами.

Так как операции работы с файлами у нас асинхронные, то без промисов нам не обойтись – добавляем $q:

[javascript]
factory(‘FileReader’, function($q, $window) {

if (!$window.FileReader) {
throw new Error(‘Browser does not support FileReader’);
}

function readAsDataUrl(file) {
var deferred = $q.defer(),
reader = new $window.FileReader();

reader.onload = function() {
deferred.resolve(reader.result);
};

reader.onerror = function() {
deferred.reject(reader.error);
};

reader.readAsDataURL(file);

return deferred.promise;
}

return {
readAsDataUrl: readAsDataUrl
};
}
[/javascript]

Код.

Превью директива

Ну и в заключение для того, чтобы использовать только что написаный сервис FileReader сделаем директиву, которая будет отображать превью загруженной картинки. HTML-представление будет выглядеть где-то так:

[javascript]
<input type="file" ng-model="newImage" file-changed />
<img file-preview="newImage" />
[/javascript]

  • file-changed – ранее описанная директива-фикс ng-model
  • file-preview – наша новая директива, которая будет отвечать за отобразжение превью при выборе файла через модель newImage

а код директивы:

[javascript]
directive(‘filePreview’, function (FileReader) {
return {
restrict: ‘A’,
scope: {
filePreview: ‘=’
},
link: function (scope, element, attrs) {
scope.$watch(‘filePreview’, function (filePreview) {
if (filePreview && Object.keys(filePreview).length !== 0) {
FileReader.readAsDataUrl(filePreview).then(function (result) {
element.attr(‘src’, result);
});
}
});
}
};
});
[/javascript]

FileReader – раннее созданный сервис оболочка на window.FileReader с промисами
filePreview: ‘=’ – создаем изолированный скоуп и линкуем модель
scope.$watch(‘filePreview’ – отслеживаем изменение модели
FileReader.readAsDataUrl(filePreview) – считываем файл
element.attr(‘src’, result) – задаем картинку

Поиграться с примером можно тут.

]]>
https://stepansuvorov.com/blog/2014/09/%d0%bf%d0%b8%d1%88%d0%b5%d0%bc-%d1%81%d0%b2%d0%be%d0%b9-uploader-%d1%81-%d0%bd%d1%83%d0%bb%d1%8f-%d0%bd%d0%b0-javascript-%d0%b8%d1%81%d0%bf%d0%be%d0%bb%d1%8c%d0%b7%d1%83%d1%8f-fileapi-%d1%87%d0%b0-5/feed/ 4
Пишем свой Uploader с нуля на javascript используя FileApi. Часть4 https://stepansuvorov.com/blog/2012/07/%d0%bf%d0%b8%d1%88%d0%b5%d0%bc-%d1%81%d0%b2%d0%be%d0%b9-uploader-%d1%81-%d0%bd%d1%83%d0%bb%d1%8f-%d0%bd%d0%b0-javascript-%d0%b8%d1%81%d0%bf%d0%be%d0%bb%d1%8c%d0%b7%d1%83%d1%8f-fileapi-%d1%87%d0%b0-4/ https://stepansuvorov.com/blog/2012/07/%d0%bf%d0%b8%d1%88%d0%b5%d0%bc-%d1%81%d0%b2%d0%be%d0%b9-uploader-%d1%81-%d0%bd%d1%83%d0%bb%d1%8f-%d0%bd%d0%b0-javascript-%d0%b8%d1%81%d0%bf%d0%be%d0%bb%d1%8c%d0%b7%d1%83%d1%8f-fileapi-%d1%87%d0%b0-4/#respond Mon, 30 Jul 2012 11:17:21 +0000 http://stepansuvorov.com/blog/?p=341 Continue reading ]]> В частях 1, 2, 3 мы научились читать файлы с диска.Теперь попробуем разобрать процесс отправки файлов на сервер с помощью технологии ajax.

В начале вспомним как работать с аяксом для передачи файла на сервер:

  1. Метод передачи будет POST
  2. Обязательно нужно будет указать Content-Type, а именно multipart/form-data
  3. Правильно сформировать само тело сообщения

Получим следующий код(по пунктам):

var request = new XMLHttpRequest(); 
request.onreadystatechange = ajaxReady; 
request.open('POST', 'uploader.php', true); // (1)
request.setRequestHeader('Content-Type', contentType); // (2)
request.sendAsBinary(createTestMsg()); // (3)

ajaxReady – это просто callback-функция, которая вызовется при ajax-ответе. Для нее пока установим простую заглушку:

function ajaxReady() {
    if (request.readyState == 4 && request.status == 200) {
        alert(request.responseText);
    }
}

uploader.php  – тоже пока файл-заглушка на стороне сервера, следующего содержания:

<?php

var_dump($_POST);
var_dump($_FILES);

Теперь перейдем к более важным моментам. contentType определяем следующим образом:

var boundary = "AJAX-----------------------" + (new Date).getTime();
var contentType = "multipart/form-data; boundary=" + boundary;

boundary – это случайная последовательность байт, которые не должны встречаться в самом файле. Вы можете придумать свой алгоритм для генерации данной последовательности, т.к. это лишь пример.

Ну и самое основное – формирование сообщения:

function createTestMsg(){
    var fieldName = 'testfile';
    var fileName  = '4.jpg';
    var CRLF = "\r\n";

    var msg = "--" + boundary + CRLF;
    msg += 'Content-Disposition: form-data; ';
    msg += 'name="' + fieldName + '"; ';
    msg += 'filename="'+ fileName + '"' + CRLF;
    msg += 'Content-Type: application/octet-stream';
    msg += CRLF + CRLF; // marks end of the headers part
    msg += Array(9999).join(7) + CRLF;
    msg += "--" + boundary + "--" + CRLF;
    return msg;
}

Array(9999).join(7) – это такой вариант генерации фэйкового контента для файла.

Вот тут можно посмотреть весь код: http://learn.javascript.ru/play/E1MhM, но в песочнице он работать не будет, т.к. нет файла uploader.php на строне сервера, так что в любом случае нужно копировать локально и смотреть.

]]>
https://stepansuvorov.com/blog/2012/07/%d0%bf%d0%b8%d1%88%d0%b5%d0%bc-%d1%81%d0%b2%d0%be%d0%b9-uploader-%d1%81-%d0%bd%d1%83%d0%bb%d1%8f-%d0%bd%d0%b0-javascript-%d0%b8%d1%81%d0%bf%d0%be%d0%bb%d1%8c%d0%b7%d1%83%d1%8f-fileapi-%d1%87%d0%b0-4/feed/ 0
Пишем свой Uploader с нуля на javascript используя FileApi. Часть2 https://stepansuvorov.com/blog/2012/06/%d0%bf%d0%b8%d1%88%d0%b5%d0%bc-%d1%81%d0%b2%d0%be%d0%b9-uploader-%d1%81-%d0%bd%d1%83%d0%bb%d1%8f-%d0%bd%d0%b0-javascript-%d0%b8%d1%81%d0%bf%d0%be%d0%bb%d1%8c%d0%b7%d1%83%d1%8f-fileapi-%d1%87%d0%b0-2/ https://stepansuvorov.com/blog/2012/06/%d0%bf%d0%b8%d1%88%d0%b5%d0%bc-%d1%81%d0%b2%d0%be%d0%b9-uploader-%d1%81-%d0%bd%d1%83%d0%bb%d1%8f-%d0%bd%d0%b0-javascript-%d0%b8%d1%81%d0%bf%d0%be%d0%bb%d1%8c%d0%b7%d1%83%d1%8f-fileapi-%d1%87%d0%b0-2/#respond Sat, 30 Jun 2012 10:21:10 +0000 http://stepansuvorov.com/blog/?p=255 Continue reading ]]> Вот и созрело продолжение первой части, где мы разобрались как можно использовать родной объект FileReader для чтения файла.

Давайте еще добавим прогресс чтения файла для нашего загрузчика. Для этого зададим callback метод для FileReader:

reader.onprogress = updateProgress;

Рассмотрим что из себя представляет метод updateProgress:

function updateProgress(event) {
    if (event.lengthComputable) {
        var progress = Math.round((event.loaded / event.total) * 100);
        document.getElementById('buffer').innerHTML = progress + '%';
    }
}

Все просто. event.lengthComputable нам необходимо чтобы убедится что event – объект того события, которое мы ждем, а именно ProgressEvent.

Math.round((event.loaded / event.total) * 100)

Примитивная математика для вычисления процента.

document.getElementById('buffer').innerHTML = progress + '%';

Тут мы использовали уже имеющийся textarea элемент с id = buffer, чтобы вывести проценты туда.

Вот что в итоге получилось:  http://learn.javascript.ru/play/u0bbrb
Советую использовать большие файлы( от 100МБ) для проверки работоспособности.

!Внимание: мы рассмотрели пока прогресс загрузки файла локально, т.е. непосредственно чтения содержимого файла скриптом, но еще не саму загрузку данных на сервер.

]]>
https://stepansuvorov.com/blog/2012/06/%d0%bf%d0%b8%d1%88%d0%b5%d0%bc-%d1%81%d0%b2%d0%be%d0%b9-uploader-%d1%81-%d0%bd%d1%83%d0%bb%d1%8f-%d0%bd%d0%b0-javascript-%d0%b8%d1%81%d0%bf%d0%be%d0%bb%d1%8c%d0%b7%d1%83%d1%8f-fileapi-%d1%87%d0%b0-2/feed/ 0
Пишем свой Uploader с нуля на javascript используя FileApi. Часть1 https://stepansuvorov.com/blog/2012/04/%d0%bf%d0%b8%d1%88%d0%b5%d0%bc-%d1%81%d0%b2%d0%be%d0%b9-uploader-%d1%81-%d0%bd%d1%83%d0%bb%d1%8f-%d0%bd%d0%b0-javascript-%d0%b8%d1%81%d0%bf%d0%be%d0%bb%d1%8c%d0%b7%d1%83%d1%8f-fileapi-%d1%87%d0%b0/ https://stepansuvorov.com/blog/2012/04/%d0%bf%d0%b8%d1%88%d0%b5%d0%bc-%d1%81%d0%b2%d0%be%d0%b9-uploader-%d1%81-%d0%bd%d1%83%d0%bb%d1%8f-%d0%bd%d0%b0-javascript-%d0%b8%d1%81%d0%bf%d0%be%d0%bb%d1%8c%d0%b7%d1%83%d1%8f-fileapi-%d1%87%d0%b0/#comments Sun, 29 Apr 2012 12:33:20 +0000 http://stepansuvorov.com/blog/?p=150 Continue reading ]]> Отметим ключевые моменты, которые нас интересуют по FileApi:
– у input с атрибутом type=”file” теперь есть свойство files(массив объектов класса File)
а объект класса File содержит следующие свойства:
name — имя файла
type — MIME тип файла
size — размер в байтах

Для чтения файла мы теперь можем использовать класс FileReader, который имеет следующие методы:
readAsBinaryString(file) — чтение в бинарном режиме.
readAsText(file[, encoding]) — чтение в текстовом режиме. Дополнительным аргументом указывается кодировка (по-умолчанию UTF-8).
readAsDataURL(/forum/file) — чтение в бинарном режиме с последующей перекодировкой в Data:URL.

Основные моменты теории разобрали, остальное – по ходу дела.

Создадим простую HTMLку:

<input type=”file” name=”file” id=”file-field”/>
<textarea id=”buffer”></textarea>

Для наглядности мы не будем сразу отправлять файл, а разберем пошагово процесс и загрузим содержимое файла в тег textarea:

Для этого повесим обработчик изменения состояния на поле ввода файла:

document.getElementById(‘file-field’).onchange = function(){
var reader = new FileReader;
reader.onload = function(e){
document.getElementById(‘buffer’).innerHTML = e.target.result;
}
reader.readAsDataURL(this.files[0]);
}

Если мы все правильно сделали, то после выбора файла его содержимое будет загружено в textarea.

Вот тут можно поиграться с кодом: http://learn.javascript.ru/play/esPXY

Подробнее можно почитать в спецификации: http://www.w3.org/TR/FileAPI/

]]>
https://stepansuvorov.com/blog/2012/04/%d0%bf%d0%b8%d1%88%d0%b5%d0%bc-%d1%81%d0%b2%d0%be%d0%b9-uploader-%d1%81-%d0%bd%d1%83%d0%bb%d1%8f-%d0%bd%d0%b0-javascript-%d0%b8%d1%81%d0%bf%d0%be%d0%bb%d1%8c%d0%b7%d1%83%d1%8f-fileapi-%d1%87%d0%b0/feed/ 3