Глобальные события или 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, время: 15:08. |