Глобальные события или drag & drop
Нужно сделать поле для перетаскивания для загрузки файлов. В реальности кнопка выбора файла и поле для перетаскивания это разные не вложенные друг в друга теги. Нужно сделать так, чтобы оба этих способа запускали один и тот же обработчик загрузки файлов.
Пока сделал две директивы ng-file="upload1" и ng-droparea="upload1". Как лучше их связать? Как создать глобальное событие когда, например, на форму перетаскивается файл и где это событие читать? P.S. Пока видятся два варианта: общаться через модель в корневой области видимости (вотчить ее изменения). Или использовать emit в корневую область и из нее рассылать всем broadcast |
Цитата:
<element ng-file="upload1" ng-upload><element> ... <element ng-droparea="upload1" ng-upload><element> ? |
постом выше ерудну сморозил :) дело было утром )
Цитата:
Цитата:
В моем примере, сервис аккумулирует коллбеки и запускает их по вызову метода сервиса:
<!DOCTYPE HTML>
<html id="ng-app" ng-app="app"> <!-- id="ng-app" IE<8 -->
<head>
<script src="http://code.angularjs.org/1.1.5/angular.min.js"></script>
<script>
var app = angular.module( 'app', []);
app.factory( '$drag', function() {
return {
callbacks: [],
action: function() {
this.callbacks.forEach(function( callback ) {
callback();
});
}
};
});
app.controller( 'scope2', function( $scope, $drag ) {
$scope.action = function() { $drag.action() };
$drag.callbacks.push(function() {
alert( 'scope2' );
});
});
app.controller( 'scope3', function( $scope, $drag ) {
$scope.action = function() { $drag.action() };
$drag.callbacks.push(function() {
alert( 'scope3' );
});
});
app.controller( 'scope4', function( $scope, $drag ) {
$scope.action = function() { $drag.action() };
$drag.callbacks.push(function() {
alert( 'scope4' );
});
});
</script>
</head>
<body ng-controller="scope2">
<input type="button" ng-click="action()" value="$scope2.action()">
<div ng-controller="scope3">
<input type="button" ng-click="action()" value="$scope3.action()">
<div ng-controller="scope4">
<input type="button" ng-click="action()" value="$scope4.action()">
</div>
</div>
</body>
</html>
|
Как вариант. Пока, правда пробую вариант с общим контроллером, который можно указать при определении директивы, но там непонятно...
|
случайно наткнулся http://odetocode.com/blogs/scott/arc...directive.aspx
|
Познавательно! У меня нечто похожее. Мой пример в последнем сообщении: https://github.com/angular/angular.js/issues/1236
Думаю, лучший вариант, сделать универсальную директиву для загрузки файлов. Применяешь на кнопку — открывается диалоговое окно, на область — драг энд дроп. |
Еще придумал вариант с общим сервисом. Создается сервис Global = {files: [...]}
Директивы выбора файла отправляют объекты файлов в него, а к нему уже обращаешься из разных контроллеров. |
Цитата:
|
Попался фиддл на эту тему: http://jsfiddle.net/simpulton/huKHQ/
И здесь немного про сервис http://plnkr.co/edit/fQMwfI5d7nikjhXTXSC6?p=preview |
Написал заготовку для загрузки файлов (html5, drag-n-drop, multimple)
Директивы: - ngFileSelect - вешается на <input type="file" /> - ngFileDrop - вешается на DOM элемент, кот. будет ловить файлы (как правило, это весь документ) - ngFileOver - вешается на элемент, кот. будет реагировать (добавляться класс), когда файлы находятся над drop элементом. По умолчанию добавляется класс "ng-file-over". Другой класс можно задать в параметре атрибута ng-file-over="className" Сервисы: - $fileStorage - файловое хранилище. Туда добавляются файлы, после того, как они были "сброшены и пойманы", либо выбраны в инпуте. Есть возможность фильтровать добавление в хранилище. - $fileUploader - загрузчик файлов. Помещает файлы в очередь, после чего загружает их оттуда. - $fileTransport - транспорт. Планирую xhr2 + iframe. На данный момент реализовано только первое. $fileStorage свойства: - files - массив файлов методы: - add - добавить в хранилище - remove - удалить по индексу - removeAll - удалить все коллбеки: - filter: function( file ) { - вызывается до добавления файла в хранилище. true - добавить, false - нет - onAfterAddingFiles - вызывается после добавления файлов в хранилище. На этом этапе можно читать файлы, делать превьюшки и т.п. $fileUploader свойства: - url: общий для всех файлов. Можно индивидуализировать для каждого файла, после добавления файла в хранилище - alias - псевдоним, под которым сервер увидит файл. Общий для всех файлов. Можно индивидуализировать для каждого файла, после добавления файла в хранилище - queue - очередь файлов методы: - addToQueue - добавляет файлы в очередь загрузки - removeFromQueue - удаляет файл из очереди по индексу - clearQueue - очищает очередь - uploadItem - загружает файл по индексу - uploadAll - загружает все файлы в очереди коллбеки: - progress - индикация загрузки файла - success - успешная загрузка - error - ошибка при загрузке - complete - загрузка завершена Надо подумать над тем, что должно приходить в качестве параметров в эти функции. На данный момент только xhr. В случае выбора >1 файла, файлы будут загружаться по очереди. http://learn.javascript.ru/play/IhSxR p.s.: кроме хрома нигде не проверял |
забыл angular.isArrayLikeObject:
(function() {
'use strict';
angular.extend( angular, {
isArrayLikeObject: isArrayLikeObject
});
/**
* Проверяет, является ли переданное значение массивоподобным объектом
* @param item {*}
* @returns {boolean}
*/
function isArrayLikeObject( item ) {
if ( angular.isObject( item ) ) {
return 'length' in item;
} else {
return false;
}
}
}());
|
Клево! Тоже написал директиву для загрузки, которая работает с ресурсами. Одна проблема, которую пока не смог победить — отмена загрузки. Т.е. при удалении элемента, связанного с загружаемым файлом должна отменяться загрузка (функцией abort). К сожалению, отдельно эту функцию из xhr не перетащить в scope (не будет работать), а при копировании всего объекта xhr в scope постоянно появляются ошибки.
Как разберусь — тоже выложу) |
Если интересно, переписал код (но еще не закончил) http://learn.javascript.ru/play/7VhTcc
упразднил все сервисы, кроме $fileUploader p.s.: работает из песочницы (без сервера) |
У меня всё проще. Существует всего одна директива ui-file, которая накладывается на любой тег, после чего на область в теге становится сразу возможным перетаскивать файлы. Если этим тегом является инпут с типом file, то загружать можно еще и стандартным способом. В будущем можно сделать, чтобы такой невидимый инпут генерировался бы автоматически, если директива будет применена к ссылке или кнопке.
Директива: http://plnkr.co/edit/5Ke7tELWHkSgoCU7IpHm?p=preview Пример надо еще допиливать Стараюсь делать как можно меньше общих функций между директивой и областью видимости. По сути, должна быть только одна createItem, а прогресс, имя файла и т.п. уже в модель записывать. |
Цитата:
Мои вопросы и комментарии: 1. Для чего эта проверка?
var ok = e.dataTransfer && e.dataTransfer.types && e.dataTransfer.types.indexOf('Files') >= 0
в моей реализации есть такая штука var dataTransfer = event.dataTransfer ? event.dataTransfer : event.originalEvent.dataTransfer; // jQuery fix; фиксит подключении жуквери (т.е., если подключаешь жуквери, то получаешь ошибку без этого фикса) в моей реализации
.directive( 'ngFileDrop', function( $fileUploader ) {
return {
// don't use drag-n-drop files in IE9, because not File API support
link: !window.File ? angular.noop : function( $scope, $element ) {
// code
}
};
})
обрати внимание на строку линковки. В IE9 есть событие сброса, но нету файлов :) Будь осторожен ) 2. Почему на мой взгляд не удобно одной директивой Если она висит на инпуте, то и сброс файла необходимо производить в инпут (что удобно далеко не всегда, проще бросать на документ, например) В моей реализации можно: - загружать файлы с дран-н-дропом и с помощью инпута - либо только дран-н-дропом - либо только инпутом В моей реализации ловящих областей может быть много, также как и инпутов. 3. Не понял, можно ли в твоей реализации "загружать по кнопке", т.е. сбросили файлы, а затем нажали кнопку загрузить (у меня для этого есть очередь) 4. валидаторы и генерацию превьюшек я бы вынес из директивы... и я вынес :D 5. благодаря паттерну "наблюдатель" в моей реализации, за событиями загрузки и добавления в очередь могут следить сразу несколько контроллеров |
Цитата:
P.S. У меня с jQuery не глючит, кстати. Цитата:
Цитата:
Цитата:
Цитата:
Пока основной кейс, в котором тестирую, такой: http://tamtakoe.ru/photoalbum/. Включает в себя практически все ситуации (кроме общего прогресса). Еще хочу уйти от функций в области видимости и вынести createItem() в настройку директивы... |
В первом приближении... Вот демка http://tamtakoe.ru/uploader/
А тут демка, где можно с фронтендом пограть (в последних браузерах): http://plnkr.co/edit/HKbvgle4zqfqCKcpLJDi?p=preview. Ключевой файл file.js Апи примерно устаканилось. Осталось сделать отмену загрузки и подтягивать кроссбраузерность |
Shitbox2, молодец ) Ковер и гендольф (или как его там) заслуживают отдельного упоминания :D
у меня пока такая штука получилась http://www.screencapture.ru/file/1190575A Что есть: Директивы: - ngFileSelect - вешается на <input type="file" /> - ngFileDrop - вешается на DOM элемент, кот. будет ловить файлы (как правило, это весь документ) - ngFileOver - вешается на элемент, кот. будет реагировать (добавляться класс), когда файлы находятся над drop элементом. По умолчанию добавляется класс "ng-file-over". Другой класс можно задать в параметре атрибута ng-file-over="className" (в этом плане ничего не менял) Один сервис: - $fileUploader - управление очередью и загрузка файлов $fileUploader свойства: - url: общий для всех файлов. Можно индивидуализировать для каждого файла, после добавления файла в хранилище - alias - псевдоним, под которым сервер увидит файл. Общий для всех файлов. Можно индивидуализировать для каждого файла, после добавления файла в хранилище - autoUpload - автозагрузка (true/false) - removeAfterUpload - удалить из очереди после загрузки (true/false) - filters - фильтры, применяемые к файлам перед добавлением в очередь. По умолчанию присутствует simple filer : ) - queue - очередь методы: - bind - пожалуй, один из наиболее важных. Регистрирует коллбеки. Позволяет повесить на одно событие сколько угодно (в пределах разумного :)) обработчиков. - addToQueue - добавляет файлы в очередь загрузки - removeFromQueue - удаляет файл из очереди по индексу - clearQueue - очищает очередь - uploadItem - загружает файл по индексу - uploadAll - загружает все файлы в очереди коллбеки: - afteraddingfile - после добавления файла в очередь - afteraddingall - после добавления всех файлов в очередь - beforeupload - перед загрузкой - progress - индикация загрузки файла - success - успешная загрузка - error - ошибка при загрузке - complete - загрузка завершена Чтобы было проще понять, приведу кусок кода:
app.controller( 'MainController', function( $scope, $fileUploader ) {
$scope.model = {};
// пользовательские фильтры
$scope.filter1 = function( item ) {
console.log( 'filter1' );
return true;
};
// их может быть много, главное их добавить (push)
$scope.filter2 = function( item ) {
console.log( 'filter2' );
return true;
};
// коллбек "после добавления файла в очередь"
$scope.afterAddingFile = function( item ) {
console.log( item );
};
// коллбек "после добавления всех файлов в очередь"
$scope.afterAddingAll = function( items ) {
$scope.model.items = $fileUploader.queue;
$scope.$apply();
};
// коллбек "перед загрузкой"
$scope.beforeUpload = function( item ) {
console.log( item );
};
// коллбек "progress для файла"
$scope.progress = function( progress ) {
console.log( 'Progress: ' + progress );
$scope.$apply();
};
// коллбек "success для файла"
$scope.success = function( xhr ) {
console.log( 'Success: ' + xhr.response );
};
// коллбек "complete для файла"
$scope.complete = function( xhr ) {
console.log( 'Complete: ' + xhr.response );
$scope.$apply();
};
// управление очередью
$scope.uploadAll = function() {
$fileUploader.uploadAll();
};
// управление очередью
$scope.removeAll = function() {
$fileUploader.clearQueue();
};
$fileUploader.url = '/upload.php';
// добавление фильтров
$fileUploader.filters.push( $scope.filter1 );
$fileUploader.filters.push( $scope.filter2 );
// регистрация наблюдателей
$fileUploader.bind( 'afteraddingfile', $scope.afterAddingFile );
$fileUploader.bind( 'afteraddingall', $scope.afterAddingAll );
$fileUploader.bind( 'beforeupload', $scope.beforeUpload );
$fileUploader.bind( 'progress', $scope.progress );
$fileUploader.bind( 'success', $scope.success );
$fileUploader.bind( 'complete', $scope.complete );
});
т.о. на любое событие может подписаться произвольное количество раз любой контроллер (или несколько контроллеров) код http://learn.javascript.ru/play/DUtgpc Насчет абортов. Говорят, это плохо :D Хочу сделать Progress, Success(may be) и Complete для всей очереди, после чего приступлю к генерации превьюшек. Скорее всего у меня будет для этого отдельный сервис. Как то так :) |
|
Клёво) У меня все руки не доходят свой на Гитхаб выложить. А нужен ли observer? Я делаю проще. Подключаю $rootScope где надо и просто подписываюсь $rootScope.$on('myAction', fn). В любом случае все события до корневой области дойдут)
|
Цитата:
Он еще внутри используется: аккумулирует события и перераспределяет их (aka proxy). Скорее всего создам для этого изолированный scope, чтобы с ним можно было работать подобным образом (on, off, fire). Еще было бы неплохо внедрение в scope контроллера сделать, чтобы обертки не писать. |
|
Странное вынесение колбэков. Почему не сделать так?
var uploader = $fileUploader.create({
url: '/upload.php',
filters: [
function( item ) { // first user filter
console.log( 'filter1' );
return true;
}
],
//Колбэки
afteraddingfile: function () { ... },
afteraddingall: function () { ... },
...
});
Распространенная практика упаковывать колбэки в объект настроек, чтобы всё было в одном месте. Вынесение АПИ в отдельный объект применяется, как правило, для методов, т. к. методы как раз приходится вызывать из разных мест, напр.: uploader.send() |
Цитата:
// create a uploader with options
var uploader = $fileUploader.create({
url: '/upload.php',
filters: [
function( item ) { // first user filter
console.log( 'filter1' );
return true;
}
]
});
// REGISTER HANDLERS
uploader.bind( 'afteraddingfile', function( event, item ) {
console.log( 'After adding a file 1', item );
});
uploader.bind( 'afteraddingfile', function( event, item ) {
console.log( 'After adding a file 2', item );
});
|
А зачем?)
P.S. И еще не очень с очередью понятно. К примеру, у меня, как помнишь, так:
create: function (file, uploadFile) {
//Создаем пустой элемент для будущего файла
$scope.add('after', function (index, fileEl, permit) {
angular.extend(fileEl, file) //Записываем айдишник элемента в объект файла
uploadFile($scope.url + '/' + fileEl.id, fileEl, permit) //Загружаем файл
})
Т.е. fileEl (надо будет переименовать в queue :) содержит информацию об выбранных файлах. Это массив и его можно менять как угодно стандартными методами. Зачем делать очередь с отдельными методами добавления, доступа? |
Цитата:
Цитата:
$scope.uploader = uploader; где шаблон
<ul>
<li ng-repeat="item in uploader.queue">
</ul>
подробнее в разметке Методов доступа к ней не писал (предполагается использование стандартных способов работы с массивами). Есть метод добавления в очередь, удаления из нее (методы загрузчика). Метод addToQueue нужен потому, что: - перед добавлением в очередь, каждый элемент проходит фильтры (что позволяет валидировать очередь) ссылка - после добавления элемента[file|input] в очередь, он становится элементом очереди (приобретает свое api), что позволяет писать разметку вида
<ul>
<li ng-repeat="item in uploader.queue">
<div>Name: {{ item.file.name }}</div>
<div>Size: {{ item.file.size }}</div>
<div>
Progress: {{ item.progress }}
<div class="item-progress-box">
<div class="item-progress" ng-style="{ 'width': item.progress + '%' }"></div>
</div>
</div>
<div>Uploaded: {{ item.isUploaded }}</div>
<div>
<a ng-href="#" ng-click="item.upload()" ng-hide="item.isUploaded" >upload</a>
<a ng-href="#" ng-click="item.remove()">delete</a>
</div>
</li>
</ul>
- вызываются события "afteraddingfile", "afteraddingall", "changedqueue". На примере события "afteraddingfile" покажу процесс кастомизации:
uploader.bind( 'afteraddingfile', function( event, item ) {
// здесь, например, можно менять свойства элемента очереди, такие как alias, url, headers ... etc
// можно дать команду на немедленную загрузку - item.upload()
// или удалить item.remove() и т.п.
});
- на этапе добавления в очередь убираются браузерные различия (xh2 & iframe): item.upload(); uploader.uploadItem( [index|item] ); uploader.uploadAll( [index|item] ); - почему надо делать удаление через методы загрузчика - iframe transport (остаются скрытые инпуты, формы и фреймы) возможно, сделаю очередь защищенным свойством и добавлю пару методов для феншуя, но особых проблем не вижу, если руки прямые :) |
А ты тестировал iframe? Пытаюсь так же добавить поддержку, твой код не работает. В Хроме выдает ответ в модальном окне (с адресом запроса) с ошибкой UPLOAD_ERR_NO_FILE ПХП загрузчика. В IE просто не работает)
|
Цитата:
Цитата:
чтобы протестировать мой код с фреймовым загрузчиком, достаточно сделать это (разумеется выбирать через инпут)
// It is attached to <input type="file" /> element
.directive( 'ngFileSelect', function() {
return {
link: function( $scope, $element ) {
if ( !window.File || !window.FormData ) {
$element.removeAttr( 'multiple' );
}
$element.bind( 'change', function() {
$scope.$emit( 'file:add', this );
// $scope.$emit( 'file:add', this.files ? this.files : this );
});
}
}
});
|
Я тестировал так: http://learn.javascript.ru/play/GLyoJ
Изменения с 255 строки в files.js |
Цитата:
|
Да, мой косяк. Так работает,
|
Shitbox2, выложи потом, что получилось или дай ссылку на гитхаб.
Я заодно один баг у себя поправил (обновил реп) - не работали фильтры, через которые проходили файлы при добавлении (т.е. если false, то файл добавляется был не должен) Ну и полностью автоматизировал обновление разметки. Остались одни коллбеки и настройки :) |
Выложу, конечно. Смотрю сейчас https://github.com/blueimp/jQuery-Fi...e-transport.js и другие файлы. Столько нюансов. Еще и кроссдоменные запросы. Слепо копировать не хочется, а разбираться слишком долго :-)
На счет колбеков/событий. Думаю, в Ангуляре нужно по максимуму обещания использовать. Нет в нем никаких событий, все на промисах построено и $http и $resource. Почему это загрузка файлов должна быть особенной? P.S. Забыл спросить. Зачем написано так var clone = $compile(input.clone())($rootScope.$new(true)); если можно так? var clone = input.clone(); P.P.S. Тут еще интересный подход: https://github.com/uor/angular-file |
Цитата:
Цитата:
Цитата:
|
Цитата:
|
Цитата:
Цитата:
|
Цитата:
P.S.А форму нужно удалять, конечно, после срабатывания колбека |
Цитата:
|
Возможно пользователь (программист) захочет загрузить файл повторно.А что мешает повторно ее создать?) |
Выложил на Гитхаб https://github.com/tamtakoe/oi.file. Может и правда, потом вынесу общие функции в отдельный сервис...
8 эксплорер у меня совсем с катушек съехал, ничего загружать не хочет... А с новой версией Ангуляра, вообще, не запускается У тебя нельзя менять на лету настройки плагина, было бы можно, создание клона поля в изолированной области видимости принесло бы проблем) |
| Часовой пояс GMT +3, время: 18:19. |