From 20f1425cbe2530cc4b338b51c0dd1232d46dc65b Mon Sep 17 00:00:00 2001 From: Davide Alberani Date: Sun, 29 Mar 2015 09:58:25 +0200 Subject: [PATCH] add angular-file-upload --- static/js/angular-file-upload.js | 578 +++++++++++++++++++++++++++ static/js/angular-file-upload.min.js | 2 + 2 files changed, 580 insertions(+) create mode 100644 static/js/angular-file-upload.js create mode 100644 static/js/angular-file-upload.min.js diff --git a/static/js/angular-file-upload.js b/static/js/angular-file-upload.js new file mode 100644 index 0000000..279b823 --- /dev/null +++ b/static/js/angular-file-upload.js @@ -0,0 +1,578 @@ +/**! + * AngularJS file upload/drop directive and service with progress and abort + * @author Danial + * @version 3.2.4 + */ +(function () { + +var key, i; +function patchXHR(fnName, newFn) { + window.XMLHttpRequest.prototype[fnName] = newFn(window.XMLHttpRequest.prototype[fnName]); +} + +if (window.XMLHttpRequest && !window.XMLHttpRequest.__isFileAPIShim) { + patchXHR('setRequestHeader', function (orig) { + return function (header, value) { + if (header === '__setXHR_') { + var val = value(this); + // fix for angular < 1.2.0 + if (val instanceof Function) { + val(this); + } + } else { + orig.apply(this, arguments); + } + } + }); +} + +var angularFileUpload = angular.module('angularFileUpload', []); + +angularFileUpload.version = '3.2.4'; +angularFileUpload.service('$upload', ['$http', '$q', '$timeout', function ($http, $q, $timeout) { + function sendHttp(config) { + config.method = config.method || 'POST'; + config.headers = config.headers || {}; + config.transformRequest = config.transformRequest || function (data, headersGetter) { + if (window.ArrayBuffer && data instanceof window.ArrayBuffer) { + return data; + } + return $http.defaults.transformRequest[0](data, headersGetter); + }; + var deferred = $q.defer(); + var promise = deferred.promise; + + config.headers['__setXHR_'] = function () { + return function (xhr) { + if (!xhr) return; + config.__XHR = xhr; + config.xhrFn && config.xhrFn(xhr); + xhr.upload.addEventListener('progress', function (e) { + e.config = config; + deferred.notify ? deferred.notify(e) : promise.progress_fn && $timeout(function () { + promise.progress_fn(e) + }); + }, false); + //fix for firefox not firing upload progress end, also IE8-9 + xhr.upload.addEventListener('load', function (e) { + if (e.lengthComputable) { + e.config = config; + deferred.notify ? deferred.notify(e) : promise.progress_fn && $timeout(function () { + promise.progress_fn(e) + }); + } + }, false); + }; + }; + + $http(config).then(function (r) { + deferred.resolve(r) + }, function (e) { + deferred.reject(e) + }, function (n) { + deferred.notify(n) + }); + + promise.success = function (fn) { + promise.then(function (response) { + fn(response.data, response.status, response.headers, config); + }); + return promise; + }; + + promise.error = function (fn) { + promise.then(null, function (response) { + fn(response.data, response.status, response.headers, config); + }); + return promise; + }; + + promise.progress = function (fn) { + promise.progress_fn = fn; + promise.then(null, null, function (update) { + fn(update); + }); + return promise; + }; + promise.abort = function () { + if (config.__XHR) { + $timeout(function () { + config.__XHR.abort(); + }); + } + return promise; + }; + promise.xhr = function (fn) { + config.xhrFn = (function (origXhrFn) { + return function () { + origXhrFn && origXhrFn.apply(promise, arguments); + fn.apply(promise, arguments); + } + })(config.xhrFn); + return promise; + }; + + return promise; + } + + this.upload = function (config) { + config.headers = config.headers || {}; + config.headers['Content-Type'] = undefined; + config.transformRequest = config.transformRequest ? + (Object.prototype.toString.call(config.transformRequest) === '[object Array]' ? + config.transformRequest : [config.transformRequest]) : []; + config.transformRequest.push(function (data) { + var formData = new FormData(); + var allFields = {}; + for (key in config.fields) { + if (config.fields.hasOwnProperty(key)) { + allFields[key] = config.fields[key]; + } + } + if (data) allFields['data'] = data; + + if (config.formDataAppender) { + for (key in allFields) { + if (allFields.hasOwnProperty(key)) { + config.formDataAppender(formData, key, allFields[key]); + } + } + } else { + for (key in allFields) { + if (allFields.hasOwnProperty(key)) { + var val = allFields[key]; + if (val !== undefined) { + if (Object.prototype.toString.call(val) === '[object String]') { + formData.append(key, val); + } else { + if (config.sendObjectsAsJsonBlob && typeof val === 'object') { + formData.append(key, new Blob([val], {type: 'application/json'})); + } else { + formData.append(key, JSON.stringify(val)); + } + } + + } + } + } + } + + if (config.file != null) { + var fileFormName = config.fileFormDataName || 'file'; + + if (Object.prototype.toString.call(config.file) === '[object Array]') { + var isFileFormNameString = Object.prototype.toString.call(fileFormName) === '[object String]'; + for (i = 0; i < config.file.length; i++) { + formData.append(isFileFormNameString ? fileFormName : fileFormName[i], config.file[i], + (config.fileName && config.fileName[i]) || config.file[i].name); + } + } else { + formData.append(fileFormName, config.file, config.fileName || config.file.name); + } + } + return formData; + }); + + return sendHttp(config); + }; + + this.http = function (config) { + return sendHttp(config); + }; +}]); + +angularFileUpload.directive('ngFileSelect', ['$parse', '$timeout', '$compile', + function ($parse, $timeout, $compile) { + return { + restrict: 'AEC', + require: '?ngModel', + link: function (scope, elem, attr, ngModel) { + linkFileSelect(scope, elem, attr, ngModel, $parse, $timeout, $compile); + } + } + }]); + +function linkFileSelect(scope, elem, attr, ngModel, $parse, $timeout, $compile) { + function isInputTypeFile() { + return elem[0].tagName.toLowerCase() === 'input' && elem.attr('type') && elem.attr('type').toLowerCase() === 'file'; + } + + var isUpdating = false; + function changeFn(evt) { + if (!isUpdating) { + isUpdating = true; + try { + var fileList = evt.__files_ || (evt.target && evt.target.files); + var files = [], rejFiles = []; + + var accept = $parse(attr.ngAccept); + for (i = 0; i < fileList.length; i++) { + var file = fileList.item(i); + if (isAccepted(scope, accept, file, evt)) { + files.push(file); + } else { + rejFiles.push(file); + } + } + updateModel($parse, $timeout, scope, ngModel, attr, + attr.ngFileChange || attr.ngFileSelect, files, rejFiles, evt); + if (files.length == 0) evt.target.value = files; + if (evt.target && evt.target.getAttribute('__ngf_gen__')) { + angular.element(evt.target).remove(); + } + } finally { + isUpdating = false; + } + } + } + + function bindAttrToFileInput(fileElem) { + if (attr.ngMultiple) fileElem.attr('multiple', $parse(attr.ngMultiple)(scope)); + if (attr['accept']) fileElem.attr('accept', attr['accept']); + if (attr.ngCapture) fileElem.attr('capture', $parse(attr.ngCapture)(scope)); + if (attr.ngDisabled) fileElem.attr('disabled', $parse(attr.ngDisabled)(scope)); + + fileElem.bind('change', changeFn); + } + + function createFileInput(evt) { + if (elem.attr('disabled')) { + return; + } + var fileElem = angular.element(''); + + for (var i = 0; i < elem[0].attributes.length; i++) { + var attribute = elem[0].attributes[i]; + fileElem.attr(attribute.name, attribute.value); + } + + if (isInputTypeFile()) { + elem.replaceWith(fileElem); + elem = fileElem; + } else { + fileElem.css('width', '0px').css('height', '0px').css('position', 'absolute') + .css('padding', 0).css('margin', 0).css('overflow', 'hidden') + .attr('tabindex', '-1').css('opacity', 0).attr('__ngf_gen__', true); + if (elem.__ngf_ref_elem__) elem.__ngf_ref_elem__.remove(); + elem.__ngf_ref_elem__ = fileElem; + elem.parent()[0].insertBefore(fileElem[0], elem[0]); + elem.css('overflow', 'hidden'); + } + + bindAttrToFileInput(fileElem); + + return fileElem; + } + + function resetModel(evt) { + updateModel($parse, $timeout, scope, ngModel, attr, + attr.ngFileChange || attr.ngFileSelect, [], [], evt, true); + } + + function clickHandler(evt) { + var fileElem = createFileInput(evt); + if (fileElem) { + resetModel(evt); + + fileElem[0].click(); + } + if (isInputTypeFile()) { + elem.bind('click', clickHandler); + evt.preventDefault() + } + } + + if (window.FileAPI && window.FileAPI.ngfFixIE) { + window.FileAPI.ngfFixIE(elem, createFileInput, changeFn, resetModel); + } else { + elem.bind('click', clickHandler); + } +} + +angularFileUpload.directive('ngFileDrop', ['$parse', '$timeout', '$location', function ($parse, $timeout, $location) { + return { + restrict: 'AEC', + require: '?ngModel', + link: function (scope, elem, attr, ngModel) { + linkDrop(scope, elem, attr, ngModel, $parse, $timeout, $location); + } + } +}]); + +angularFileUpload.directive('ngNoFileDrop', function () { + return function (scope, elem) { + if (dropAvailable()) elem.css('display', 'none') + } +}); + +//for backward compatibility +angularFileUpload.directive('ngFileDropAvailable', ['$parse', '$timeout', function ($parse, $timeout) { + return function (scope, elem, attr) { + if (dropAvailable()) { + var fn = $parse(attr['ngFileDropAvailable']); + $timeout(function () { + fn(scope); + }); + } + } +}]); + +function linkDrop(scope, elem, attr, ngModel, $parse, $timeout, $location) { + var available = dropAvailable(); + if (attr['dropAvailable']) { + $timeout(function () { + scope.dropAvailable ? scope.dropAvailable.value = available : + scope.dropAvailable = available; + }); + } + if (!available) { + if ($parse(attr.hideOnDropNotAvailable)(scope) == true) { + elem.css('display', 'none'); + } + return; + } + var leaveTimeout = null; + var stopPropagation = $parse(attr.stopPropagation); + var dragOverDelay = 1; + var accept = $parse(attr.ngAccept); + var disabled = $parse(attr.ngDisabled); + var actualDragOverClass; + + elem[0].addEventListener('dragover', function (evt) { + if (disabled(scope)) return; + evt.preventDefault(); + if (stopPropagation(scope)) evt.stopPropagation(); + // handling dragover events from the Chrome download bar + if (navigator.userAgent.indexOf("Chrome") > -1) { + var b = evt.dataTransfer.effectAllowed; + evt.dataTransfer.dropEffect = ('move' === b || 'linkMove' === b) ? 'move' : 'copy'; + } + $timeout.cancel(leaveTimeout); + if (!scope.actualDragOverClass) { + actualDragOverClass = calculateDragOverClass(scope, attr, evt); + } + elem.addClass(actualDragOverClass); + }, false); + elem[0].addEventListener('dragenter', function (evt) { + if (disabled(scope)) return; + evt.preventDefault(); + if (stopPropagation(scope)) evt.stopPropagation(); + }, false); + elem[0].addEventListener('dragleave', function () { + if (disabled(scope)) return; + leaveTimeout = $timeout(function () { + elem.removeClass(actualDragOverClass); + actualDragOverClass = null; + }, dragOverDelay || 1); + }, false); + elem[0].addEventListener('drop', function (evt) { + if (disabled(scope)) return; + evt.preventDefault(); + if (stopPropagation(scope)) evt.stopPropagation(); + elem.removeClass(actualDragOverClass); + actualDragOverClass = null; + extractFiles(evt, function (files, rejFiles) { + updateModel($parse, $timeout, scope, ngModel, attr, + attr.ngFileChange || attr.ngFileDrop, files, rejFiles, evt) + }, $parse(attr.allowDir)(scope) != false, attr.multiple || $parse(attr.ngMultiple)(scope)); + }, false); + + function calculateDragOverClass(scope, attr, evt) { + var accepted = true; + var items = evt.dataTransfer.items; + if (items != null) { + for (i = 0; i < items.length && accepted; i++) { + accepted = accepted + && (items[i].kind == 'file' || items[i].kind == '') + && isAccepted(scope, accept, items[i], evt); + } + } + var clazz = $parse(attr.dragOverClass)(scope, {$event: evt}); + if (clazz) { + if (clazz.delay) dragOverDelay = clazz.delay; + if (clazz.accept) clazz = accepted ? clazz.accept : clazz.reject; + } + return clazz || attr['dragOverClass'] || 'dragover'; + } + + function extractFiles(evt, callback, allowDir, multiple) { + var files = [], rejFiles = [], items = evt.dataTransfer.items, processing = 0; + + function addFile(file) { + if (isAccepted(scope, accept, file, evt)) { + files.push(file); + } else { + rejFiles.push(file); + } + } + + if (items && items.length > 0 && $location.protocol() != 'file') { + for (i = 0; i < items.length; i++) { + if (items[i].webkitGetAsEntry && items[i].webkitGetAsEntry() && items[i].webkitGetAsEntry().isDirectory) { + var entry = items[i].webkitGetAsEntry(); + if (entry.isDirectory && !allowDir) { + continue; + } + if (entry != null) { + traverseFileTree(files, entry); + } + } else { + var f = items[i].getAsFile(); + if (f != null) addFile(f); + } + if (!multiple && files.length > 0) break; + } + } else { + var fileList = evt.dataTransfer.files; + if (fileList != null) { + for (i = 0; i < fileList.length; i++) { + addFile(fileList.item(i)); + if (!multiple && files.length > 0) break; + } + } + } + var delays = 0; + (function waitForProcess(delay) { + $timeout(function () { + if (!processing) { + if (!multiple && files.length > 1) { + i = 0; + while (files[i].type == 'directory') i++; + files = [files[i]]; + } + callback(files, rejFiles); + } else { + if (delays++ * 10 < 20 * 1000) { + waitForProcess(10); + } + } + }, delay || 0) + })(); + + function traverseFileTree(files, entry, path) { + if (entry != null) { + if (entry.isDirectory) { + var filePath = (path || '') + entry.name; + addFile({name: entry.name, type: 'directory', path: filePath}); + var dirReader = entry.createReader(); + var entries = []; + processing++; + var readEntries = function () { + dirReader.readEntries(function (results) { + try { + if (!results.length) { + for (i = 0; i < entries.length; i++) { + traverseFileTree(files, entries[i], (path ? path : '') + entry.name + '/'); + } + processing--; + } else { + entries = entries.concat(Array.prototype.slice.call(results || [], 0)); + readEntries(); + } + } catch (e) { + processing--; + console.error(e); + } + }, function () { + processing--; + }); + }; + readEntries(); + } else { + processing++; + entry.file(function (file) { + try { + processing--; + file.path = (path ? path : '') + file.name; + addFile(file); + } catch (e) { + processing--; + console.error(e); + } + }, function () { + processing--; + }); + } + } + } + } +} + +function dropAvailable() { + var div = document.createElement('div'); + return ('draggable' in div) && ('ondrop' in div); +} + +function updateModel($parse, $timeout, scope, ngModel, attr, fileChange, files, rejFiles, evt, noDelay) { + function update() { + if (ngModel) { + $parse(attr.ngModel).assign(scope, files); + $timeout(function () { + ngModel && ngModel.$setViewValue(files != null && files.length == 0 ? null : files); + }); + } + if (attr.ngModelRejected) { + $parse(attr.ngModelRejected).assign(scope, rejFiles); + } + if (fileChange) { + $parse(fileChange)(scope, { + $files: files, + $rejectedFiles: rejFiles, + $event: evt + }); + + } + } + if (noDelay) { + update(); + } else { + $timeout(function () { + update(); + }); + } +} + +function isAccepted(scope, accept, file, evt) { + var val = accept(scope, {$file: file, $event: evt}); + if (val == null) { + return true; + } + if (angular.isString(val)) { + var regexp = new RegExp(globStringToRegex(val), 'gi') + val = (file.type != null && file.type.match(regexp)) || + (file.name != null && file.name.match(regexp)); + } + return val; +} + +function globStringToRegex(str) { + if (str.length > 2 && str[0] === '/' && str[str.length - 1] === '/') { + return str.substring(1, str.length - 1); + } + var split = str.split(','), result = ''; + if (split.length > 1) { + for (i = 0; i < split.length; i++) { + result += '(' + globStringToRegex(split[i]) + ')'; + if (i < split.length - 1) { + result += '|' + } + } + } else { + if (str.indexOf('.') == 0) { + str = '*' + str; + } + result = '^' + str.replace(new RegExp('[.\\\\+*?\\[\\^\\]$(){}=!<>|:\\' + '-]', 'g'), '\\$&') + '$'; + result = result.replace(/\\\*/g, '.*').replace(/\\\?/g, '.'); + } + return result; +} + +var ngFileUpload = angular.module('ngFileUpload', []); + +for (key in angularFileUpload) { + if (angularFileUpload.hasOwnProperty(key)) { + ngFileUpload[key] = angularFileUpload[key]; + } +} + +})(); diff --git a/static/js/angular-file-upload.min.js b/static/js/angular-file-upload.min.js new file mode 100644 index 0000000..b5b5c4f --- /dev/null +++ b/static/js/angular-file-upload.min.js @@ -0,0 +1,2 @@ +/*! 3.2.4 */ +!function(){function a(a,b){window.XMLHttpRequest.prototype[a]=b(window.XMLHttpRequest.prototype[a])}function b(a,b,c,d,g,h){function j(){return"input"===b[0].tagName.toLowerCase()&&b.attr("type")&&"file"===b.attr("type").toLowerCase()}function k(b){if(!p){p=!0;try{var j=b.__files_||b.target&&b.target.files,k=[],l=[],m=g(c.ngAccept);for(i=0;i'),c=0;c0&&"file"!=k.protocol())for(i=0;i0)break}else{var r=b.dataTransfer.files;if(null!=r)for(i=0;i0));i++);}var t=0;!function u(a){j(function(){if(o)10*t++<2e4&&u(10);else{if(!e&&l.length>1){for(i=0;"directory"==l[i].type;)i++;l=[l[i]]}c(l,m)}},a||0)}()}var n=d();if(c.dropAvailable&&j(function(){a.dropAvailable?a.dropAvailable.value=n:a.dropAvailable=n}),!n)return 1==h(c.hideOnDropNotAvailable)(a)&&b.css("display","none"),void 0;var o,p=null,q=h(c.stopPropagation),r=1,s=h(c.ngAccept),t=h(c.ngDisabled);b[0].addEventListener("dragover",function(d){if(!t(a)){if(d.preventDefault(),q(a)&&d.stopPropagation(),navigator.userAgent.indexOf("Chrome")>-1){var e=d.dataTransfer.effectAllowed;d.dataTransfer.dropEffect="move"===e||"linkMove"===e?"move":"copy"}j.cancel(p),a.actualDragOverClass||(o=l(a,c,d)),b.addClass(o)}},!1),b[0].addEventListener("dragenter",function(b){t(a)||(b.preventDefault(),q(a)&&b.stopPropagation())},!1),b[0].addEventListener("dragleave",function(){t(a)||(p=j(function(){b.removeClass(o),o=null},r||1))},!1),b[0].addEventListener("drop",function(d){t(a)||(d.preventDefault(),q(a)&&d.stopPropagation(),b.removeClass(o),o=null,m(d,function(b,f){e(h,j,a,g,c,c.ngFileChange||c.ngFileDrop,b,f,d)},0!=h(c.allowDir)(a),c.multiple||h(c.ngMultiple)(a)))},!1)}function d(){var a=document.createElement("div");return"draggable"in a&&"ondrop"in a}function e(a,b,c,d,e,f,g,h,i,j){function k(){d&&(a(e.ngModel).assign(c,g),b(function(){d&&d.$setViewValue(null!=g&&0==g.length?null:g)})),e.ngModelRejected&&a(e.ngModelRejected).assign(c,h),f&&a(f)(c,{$files:g,$rejectedFiles:h,$event:i})}j?k():b(function(){k()})}function f(a,b,c,d){var e=b(a,{$file:c,$event:d});if(null==e)return!0;if(angular.isString(e)){var f=new RegExp(g(e),"gi");e=null!=c.type&&c.type.match(f)||null!=c.name&&c.name.match(f)}return e}function g(a){if(a.length>2&&"/"===a[0]&&"/"===a[a.length-1])return a.substring(1,a.length-1);var b=a.split(","),c="";if(b.length>1)for(i=0;i|:\\-]","g"),"\\$&")+"$",c=c.replace(/\\\*/g,".*").replace(/\\\?/g,".");return c}var h,i;window.XMLHttpRequest&&!window.XMLHttpRequest.__isFileAPIShim&&a("setRequestHeader",function(a){return function(b,c){if("__setXHR_"===b){var d=c(this);d instanceof Function&&d(this)}else a.apply(this,arguments)}});var j=angular.module("angularFileUpload",[]);j.version="3.2.4",j.service("$upload",["$http","$q","$timeout",function(a,b,c){function d(d){d.method=d.method||"POST",d.headers=d.headers||{},d.transformRequest=d.transformRequest||function(b,c){return window.ArrayBuffer&&b instanceof window.ArrayBuffer?b:a.defaults.transformRequest[0](b,c)};var e=b.defer(),f=e.promise;return d.headers.__setXHR_=function(){return function(a){a&&(d.__XHR=a,d.xhrFn&&d.xhrFn(a),a.upload.addEventListener("progress",function(a){a.config=d,e.notify?e.notify(a):f.progress_fn&&c(function(){f.progress_fn(a)})},!1),a.upload.addEventListener("load",function(a){a.lengthComputable&&(a.config=d,e.notify?e.notify(a):f.progress_fn&&c(function(){f.progress_fn(a)}))},!1))}},a(d).then(function(a){e.resolve(a)},function(a){e.reject(a)},function(a){e.notify(a)}),f.success=function(a){return f.then(function(b){a(b.data,b.status,b.headers,d)}),f},f.error=function(a){return f.then(null,function(b){a(b.data,b.status,b.headers,d)}),f},f.progress=function(a){return f.progress_fn=a,f.then(null,null,function(b){a(b)}),f},f.abort=function(){return d.__XHR&&c(function(){d.__XHR.abort()}),f},f.xhr=function(a){return d.xhrFn=function(b){return function(){b&&b.apply(f,arguments),a.apply(f,arguments)}}(d.xhrFn),f},f}this.upload=function(a){return a.headers=a.headers||{},a.headers["Content-Type"]=void 0,a.transformRequest=a.transformRequest?"[object Array]"===Object.prototype.toString.call(a.transformRequest)?a.transformRequest:[a.transformRequest]:[],a.transformRequest.push(function(b){var c=new FormData,d={};for(h in a.fields)a.fields.hasOwnProperty(h)&&(d[h]=a.fields[h]);if(b&&(d.data=b),a.formDataAppender)for(h in d)d.hasOwnProperty(h)&&a.formDataAppender(c,h,d[h]);else for(h in d)if(d.hasOwnProperty(h)){var e=d[h];void 0!==e&&("[object String]"===Object.prototype.toString.call(e)?c.append(h,e):a.sendObjectsAsJsonBlob&&"object"==typeof e?c.append(h,new Blob([e],{type:"application/json"})):c.append(h,JSON.stringify(e)))}if(null!=a.file){var f=a.fileFormDataName||"file";if("[object Array]"===Object.prototype.toString.call(a.file)){var g="[object String]"===Object.prototype.toString.call(f);for(i=0;i