From 038703694617bff537edfd8733eb38f3b851d8b2 Mon Sep 17 00:00:00 2001 From: Danial Farid Date: Sat, 26 Sep 2015 19:11:20 -0400 Subject: [PATCH] Fixed #1027, #1028, #1023, #948, #855, #1016, #1024, redone data option --- README.md | 47 +++- demo/src/main/webapp/a.html | 22 -- demo/src/main/webapp/index.html | 16 +- demo/src/main/webapp/js/FileAPI.min.js | 2 +- demo/src/main/webapp/js/ng-file-upload-all.js | 249 ++++++++++-------- .../main/webapp/js/ng-file-upload-all.min.js | 4 +- .../src/main/webapp/js/ng-file-upload-shim.js | 11 +- .../main/webapp/js/ng-file-upload-shim.min.js | 4 +- demo/src/main/webapp/js/ng-file-upload.js | 238 +++++++++-------- demo/src/main/webapp/js/ng-file-upload.min.js | 4 +- demo/src/main/webapp/js/upload.js | 37 +-- dist/FileAPI.min.js | 2 +- dist/ng-file-upload-all.js | 249 ++++++++++-------- dist/ng-file-upload-all.min.js | 4 +- dist/ng-file-upload-shim.js | 11 +- dist/ng-file-upload-shim.min.js | 4 +- dist/ng-file-upload.js | 238 +++++++++-------- dist/ng-file-upload.min.js | 4 +- nuget/Package.nuspec | 2 +- package.json | 2 +- src/data-url.js | 34 +-- src/drop.js | 2 +- src/model.js | 9 + src/resize.js | 10 +- src/select.js | 2 +- src/shim-elem.js | 3 +- src/shim-upload.js | 6 +- src/upload.js | 161 +++++------ src/validate.js | 14 +- 29 files changed, 748 insertions(+), 643 deletions(-) delete mode 100644 demo/src/main/webapp/a.html diff --git a/README.md b/README.md index 1446004e..48ba7ea3 100644 --- a/README.md +++ b/README.md @@ -165,6 +165,10 @@ At least one of the `ngf-select` or `ngf-drop` are mandatory for the plugin to l *ngf-capture="'camera'" or "'other'" // allows mobile devices to capture using camera *accept="image/*" // standard HTML accept attribute for the browser specific popup window filtering + *ngf-reset-on-click="boolean" default true, will reset the model value to empty when you click on the + // upload button. This allows removing files when you cancel the popup and + // selecting the same file again. Chrome will empty the files if you cancel the popup by + // default but not the other browsers, so test cross browser is using this. +ngf-allow-dir="boolean" // default true, allow dropping files only for Chrome webkit browser +ngf-drag-over-class="{accept:'acceptClass', reject:'rejectClass', delay:100}" or "myDragOverClass" or @@ -182,6 +186,7 @@ At least one of the `ngf-select` or `ngf-drop` are mandatory for the plugin to l // quality (optional between 0.1 and 1.0) //validations: + ngf-valid-only="boolean" // default false, if true only valid files will be assigned to model or change functions. ngf-pattern="'.pdf,.jpg,video/*'" // comma separated wildcard to filter file names and types allowed // validate error name: pattern ngf-min-size, ngf-max-size="100" in bytes or "'10KB'" or "'10MB'" or "'10GB'" @@ -240,19 +245,28 @@ var upload = Upload.upload({ method: 'POST' or 'PUT'(html5), default POST, headers: {'Authorization': 'xxx'}, // only for html5 /* - map of extra form data fields to send along with file. each field will be sent as a form field. - The values are converted to json string or jsob blob or nested form depending on 'sendFieldsAs' option. */ - fields: {key: $scope.myValue, ...}, + Specify the file and optional data to be sent to the server. + Each field including nested objects will be sent as a form data multipart. + Samples: {pic: file, username: username} + {files: files, otherInfo: {id: id, person: person,...}} multiple files (html5) + {profiles: {[{pic: file1, username: username1}, {pic: file2, username: username2}]} nested array multiple files (html5) + {file: file, info: Upload.json({id: id, name: name, ...})} send fields as json string + {file: file, info: Upload.jsonBlob({id: id, name: name, ...})} send fields as json blob + {picFile: Upload.rename(file, 'profile.jpg'), title: title} send file with picFile key and profile.jpg file name*/ + data: {key: file, otherInfo: uploadInfo}, /* - default is 'json', sends each field as json string plain text content type, 'json-blob' sends object fields - as a blob object with content type 'application/json', 'form' sends fields as nested form fields. see #784 */ - sendFieldsAs: json|json-blob|form, - /* customize how data is added to the formData. See #40#issuecomment-28612000 for sample code. */ - formDataAppender: function(formData, key, val){}, + This is to accomudate server implementations expecting nested data object keys in .key or [key] format. + Example: data: {rec: {name: 'N', pic: file}} sent as: rec[name] -> N, rec[pic] -> file + data: {rec: {name: 'N', pic: file}, objectKey: '.k'} sent as: rec.name -> N, rec.pic -> file */ + objectKey: '[k]' or '.k' // default is '[k]' /* - data will be sent as a separate form data field called "data".*/ - data: {}. - withCredentials: true|false, + This is to accomudate server implementations expecting array data object keys in '[i]' or '[]' or + ''(multiple entries with same key) format. + Example: data: {rec: [file[0], file[1], ...]} sent as: rec[0] -> file[0], rec[1] -> file[1],... + data: {rec: {rec: [f[0], f[1], ...], arrayKey: '[]'} sent as: rec[] -> f[0], rec[] -> f[1],...*/ + /* + arrayKey: '[i]' or '[]' or '.i' or '' //default is '[i]' + withCredentials: boolean, /* See resumable upload guide below the code for more details (html5 only) */ resumeSizeUrl: '/uploaded/size/url?file=' + file.name // uploaded file size so far on the server. @@ -310,6 +324,15 @@ Upload.mediaDuration(file).then(function(durationInSeconds){...}); Upload.isResizeSupported() /* returns boolean showing if resumable upload is supported by this browser*/ Upload.isResumeSupported() + +/* returns a file which will be uploaded with the newName instead of original file name */ +Upload.rename(file, newName) +/* converts the object to a Blob object with application/json content type +for jsob byte streaming support #359 */ +Upload.json(obj) +/* converts the value to json to send data as json string. Same as angular.toJson(obj) */ +Upload.json(obj) + ``` **ng-model** The model value will be a single file instead of an array if all of the followings are true: @@ -356,7 +379,7 @@ On your server you need to keep track of what files are being uploaded and how m * Optionally you can specify `resumeChunkSize` to upload the file in chunks to the server. This will allow uploading to GAE or other servers that have file size limitation and trying to upload the whole request before passing it for internal processing.
If this option is set the requests will have three extra fields: - `chunckSize`, `chunkNumber` (zero starting), and `totalSize` to help the server to write the uploaded chunk to + `_chunckSize`, `_chunkNumber` (zero starting), and `_totalSize` to help the server to write the uploaded chunk to the correct position. Uploading in chunks could slow down the overall upload time specially if the chunk size is too small. diff --git a/demo/src/main/webapp/a.html b/demo/src/main/webapp/a.html deleted file mode 100644 index 84738e34..00000000 --- a/demo/src/main/webapp/a.html +++ /dev/null @@ -1,22 +0,0 @@ -
Username -
-
- Upload on form submit - Username: - - *required -
Profile Picture: - - *required - File too large: max {{picFile.$errorParam}} - -
- - -
-
- Upload Successful -
-
-
-
diff --git a/demo/src/main/webapp/index.html b/demo/src/main/webapp/index.html index 9265fa96..fc46a7ec 100644 --- a/demo/src/main/webapp/index.html +++ b/demo/src/main/webapp/index.html @@ -91,8 +91,8 @@

ngf-multiple="multiple" ngf-pattern="pattern" accept="{{acceptSelect}}" ng-disabled="disabled" ngf-capture="capture" ngf-drag-over-class="{accept:'dragover', reject:'dragover-err', delay:100}" - ngf-validate="validateObj" - ngf-keep="keep" ngf-keep-distinct="keepDistinct" + ngf-validate="validateObj" ngf-valid-only="validOnly" + ngf-keep="keep" ngf-keep-distinct="keepDistinct" ngf-reset-on-click="resetOnClick" ngf-allow-dir="allowDir" class="drop-box" ngf-drop-available="dropAvailable">Select File, Drop File or Paste Image @@ -111,6 +111,9 @@

ng-model)

+
+

@@ -136,13 +139,14 @@

{{f.progress}}%
- {{f.name}} - size: {{f.size}}B - type: {{f.type}} details diff --git a/demo/src/main/webapp/js/FileAPI.min.js b/demo/src/main/webapp/js/FileAPI.min.js index 2485a022..aecff1ce 100644 --- a/demo/src/main/webapp/js/FileAPI.min.js +++ b/demo/src/main/webapp/js/FileAPI.min.js @@ -1,4 +1,4 @@ -/*! 7.3.9 */ +/*! 8.0.0 */ /*! FileAPI 2.0.7 - BSD | git://github.com/mailru/FileAPI.git * FileAPI — a set of javascript tools for working with files. Multiupload, drag'n'drop and chunked file upload. Images: crop, resize and auto orientation by EXIF. */ diff --git a/demo/src/main/webapp/js/ng-file-upload-all.js b/demo/src/main/webapp/js/ng-file-upload-all.js index 3e54a720..05975eb8 100644 --- a/demo/src/main/webapp/js/ng-file-upload-all.js +++ b/demo/src/main/webapp/js/ng-file-upload-all.js @@ -3,7 +3,7 @@ * progress, resize, thumbnail, preview, validation and CORS * FileAPI Flash shim for old browsers not supporting FormData * @author Danial - * @version 7.3.9 + * @version 8.0.0 */ (function () { @@ -24,7 +24,11 @@ window.FileAPI = {}; } - FileAPI.shouldLoad = (window.XMLHttpRequest && !window.FormData) || FileAPI.forceLoad; + if (!window.XMLHttpRequest) { + throw 'AJAX is not supported. XMLHttpRequest is not defined.'; + } + + FileAPI.shouldLoad = !window.FormData || FileAPI.forceLoad; if (FileAPI.shouldLoad) { var initializeUploadListener = function (xhr) { if (!xhr.__listeners) { @@ -265,6 +269,7 @@ } if (FileAPI.shouldLoad) { + FileAPI.hasFlash = hasFlash(); //load FileAPI if (FileAPI.forceLoad) { @@ -291,8 +296,6 @@ if (FileAPI.staticPath == null) FileAPI.staticPath = basePath; script.setAttribute('src', jsUrl || basePath + 'FileAPI.min.js'); document.getElementsByTagName('head')[0].appendChild(script); - - FileAPI.hasFlash = hasFlash(); } FileAPI.ngfFixIE = function (elem, fileElem, changeFn) { @@ -424,7 +427,7 @@ if (!window.FileReader) { * AngularJS file upload directives and services. Supoorts: file upload/drop/paste, resume, cancel/abort, * progress, resize, thumbnail, preview, validation and CORS * @author Danial - * @version 7.3.9 + * @version 8.0.0 */ if (window.XMLHttpRequest && !(window.FileAPI && FileAPI.shouldLoad)) { @@ -445,13 +448,13 @@ if (window.XMLHttpRequest && !(window.FileAPI && FileAPI.shouldLoad)) { var ngFileUpload = angular.module('ngFileUpload', []); -ngFileUpload.version = '7.3.9'; +ngFileUpload.version = '8.0.0'; ngFileUpload.service('UploadBase', ['$http', '$q', '$timeout', function ($http, $q, $timeout) { var upload = this; this.isResumeSupported = function () { - return window.Blob && new Blob().slice; + return window.Blob && (Blob instanceof Function) && new Blob().slice; }; var resumeSupported = this.isResumeSupported(); @@ -594,7 +597,47 @@ ngFileUpload.service('UploadBase', ['$http', '$q', '$timeout', function ($http, return promise; } + this.rename = function (file, name) { + file.ngfName = name; + return file; + }; + + this.jsonBlob = function (val) { + var blob = new Blob([val], {type: 'application/json'}); + blob._ngfBlob = true; + return blob; + }; + + this.json = function (val) { + return angular.toJson(val); + }; + this.upload = function (config) { + function isFile(file) { + return file instanceof Blob || (file.flashId && file.name && file.size); + } + + function toResumeFile(file, formData) { + if (file._ngfBlob) return file; + config._file = config._file || file; + if (config._start != null && resumeSupported) { + if (config._end && config._end >= file.size) { + config._finished = true; + config._end = file.size; + } + var slice = file.slice(config._start, config._end || file.size); + slice.name = file.name; + slice.ngfName = file.ngfName; + if (config._chunkSize) { + formData.append('_chunkSize', config._end - config._start); + formData.append('_chunkNumber', Math.floor(config._start / config._chunkSize)); + formData.append('_totalSize', config._file.size); + } + return slice; + } + return file; + } + function addFieldToFormData(formData, val, key) { if (val !== undefined) { if (angular.isDate(val)) { @@ -602,102 +645,67 @@ ngFileUpload.service('UploadBase', ['$http', '$q', '$timeout', function ($http, } if (angular.isString(val)) { formData.append(key, val); - } else if (config.sendFieldsAs === 'form') { + } else if (isFile(val)) { + var file = toResumeFile(val, formData); + var split = key.split(','); + if (split[1]) { + file.ngfName = split[1].replace(/^\s+|\s+$/g, ''); + key = split[0]; + } + config._fileKey = config._fileKey || key; + formData.append(key, file, file.ngfName || file.name); + } else { if (angular.isObject(val)) { for (var k in val) { if (val.hasOwnProperty(k)) { - addFieldToFormData(formData, val[k], key + '[' + k + ']'); + var objectKey = config.objectKey == null ? '[i]' : config.objectKey; + if (val.length && parseInt(k) > -1) { + objectKey = config.arrayKey == null ? objectKey : config.arrayKey; + } + addFieldToFormData(formData, val[k], key + objectKey.replace(/[ik]/g, k)); } } } else { formData.append(key, val); } - } else { - val = angular.isString(val) ? val : angular.toJson(val); - if (config.sendFieldsAs === 'json-blob') { - formData.append(key, new Blob([val], {type: 'application/json'})); - } else { - formData.append(key, val); - } } } } - function isFile(file) { - return file instanceof Blob || (file.flashId && file.name && file.size); - } - - function addFileToFormData(formData, file, key) { - if (isFile(file)) { - config._file = config._file || file; - if (config._start != null && resumeSupported) { - if (config._end && config._end >= file.size) { - config._finished = true; - config._end = file.size; - } - var slice = file.slice(config._start, config._end || file.size); - slice.name = file.name; - file = slice; - if (config._chunkSize) { - formData.append('chunkSize', config._end - config._start); - formData.append('chunkNumber', Math.floor(config._start / config._chunkSize)); - formData.append('totalSize', config._file.size); - } + function digestConfig() { + config._chunkSize = upload.translateScalars(config.resumeChunkSize); + config._chunkSize = config._chunkSize ? parseInt(config._chunkSize.toString()) : null; + + config.headers = config.headers || {}; + config.headers['Content-Type'] = undefined; + config.transformRequest = config.transformRequest ? + (angular.isArray(config.transformRequest) ? + config.transformRequest : [config.transformRequest]) : []; + config.transformRequest.push(function (data) { + var formData = new FormData(), key; + data = data || config.fields || {}; + if (config.file) { + data.file = config.file; } - formData.append(key, file, file.fileName || file.name); - } else if (angular.isObject(file)) { - for (var k in file) { - if (file.hasOwnProperty(k)) { - var split = k.split(','); - if (split[1]) { - file[k].fileName = split[1].replace(/^\s+|\s+$/g, ''); + for (key in data) { + if (data.hasOwnProperty(key)) { + var val = data[key]; + if (config.formDataAppender) { + config.formDataAppender(formData, key, val); + } else { + addFieldToFormData(formData, val, key); } - addFileToFormData(formData, file[k], split[0]); } } - } else { - throw 'Expected file object in Upload.upload file option: ' + file.toString(); - } - } - config._chunkSize = upload.translateScalars(config.resumeChunkSize); - config._chunkSize = config._chunkSize ? parseInt(config._chunkSize.toString()) : null; - - config.headers = config.headers || {}; - config.headers['Content-Type'] = undefined; - config.transformRequest = config.transformRequest ? - (angular.isArray(config.transformRequest) ? - config.transformRequest : [config.transformRequest]) : []; - config.transformRequest.push(function (data) { - var formData = new FormData(), allFields = {}, key; - for (key in config.fields) { - if (config.fields.hasOwnProperty(key)) { - allFields[key] = config.fields[key]; - } - } - if (data) allFields.data = data; - for (key in allFields) { - if (allFields.hasOwnProperty(key)) { - var val = allFields[key]; - if (config.formDataAppender) { - config.formDataAppender(formData, key, val); - } else { - addFieldToFormData(formData, val, key); - } - } - } + return formData; + }); + } - if (config.file != null) { - if (angular.isArray(config.file)) { - for (var i = 0; i < config.file.length; i++) { - addFileToFormData(formData, config.file[i], 'file'); - } - } else { - addFileToFormData(formData, config.file, 'file'); - } - } - return formData; - }); + if (!config._isDigested) { + config._isDigested = true; + digestConfig(); + } return sendHttp(config); }; @@ -868,6 +876,15 @@ ngFileUpload.service('Upload', ['$parse', '$timeout', '$compile', 'UploadResize' if (noDelay) { update(); } else if (upload.validate(files, ngModel, attr, scope, upload.attrGetter('ngfValidateLater', attr), function () { + if (upload.attrGetter('ngfValidOnly', attr, scope) === true) { + var valids = []; + angular.forEach(files, function(file) { + if (!file.$error) { + valids.push(file); + } + }); + files = valids; + } resize(files, function () { $timeout(function () { update(); @@ -1021,7 +1038,7 @@ ngFileUpload.directive('ngfSelect', ['$parse', '$timeout', '$compile', 'Upload', var fileElem = elem; function resetModel(evt) { - if (fileElem.val()) { + if (attrGetter('ngfResetOnClick', scope) !== false && fileElem.val()) { fileElem.val(null); upload.updateModel(ngModel, attr, scope, fileChangeAttr(), null, evt, true); } @@ -1102,14 +1119,14 @@ ngFileUpload.directive('ngfSelect', ['$parse', '$timeout', '$compile', 'Upload', ngFileUpload.service('UploadDataUrl', ['UploadBase', '$timeout', '$q', function (UploadBase, $timeout, $q) { var upload = UploadBase; upload.dataUrl = function (file, disallowObjectUrl) { - if ((disallowObjectUrl && file.dataUrl != null) || (!disallowObjectUrl && file.blobUrl != null)) { + if ((disallowObjectUrl && file.$ngfDataUrl != null) || (!disallowObjectUrl && file.$ngfBlobUrl != null)) { var d = $q.defer(); $timeout(function () { - d.resolve(disallowObjectUrl ? file.dataUrl : file.blobUrl); + d.resolve(disallowObjectUrl ? file.$ngfDataUrl : file.$ngfBlobUrl); }); return d.promise; } - var p = disallowObjectUrl ? file.$ngfDataUrlPromise : file.$ngfBlobUrlPromise; + var p = disallowObjectUrl ? file.$$ngfDataUrlPromise : file.$$ngfBlobUrlPromise; if (p) return p; var deferred = $q.defer(); @@ -1126,26 +1143,26 @@ ngFileUpload.directive('ngfSelect', ['$parse', '$timeout', '$compile', 'Upload', url = URL.createObjectURL(file); } catch (e) { $timeout(function () { - file.blobUrl = ''; + file.$ngfBlobUrl = ''; deferred.reject(); }); return; } $timeout(function () { - file.blobUrl = url; + file.$ngfBlobUrl = url; if (url) deferred.resolve(url); }); } else { var fileReader = new FileReader(); fileReader.onload = function (e) { $timeout(function () { - file.dataUrl = e.target.result; + file.$ngfDataUrl = e.target.result; deferred.resolve(e.target.result); }); }; fileReader.onerror = function () { $timeout(function () { - file.dataUrl = ''; + file.$ngfDataUrl = ''; deferred.reject(); }); }; @@ -1160,12 +1177,12 @@ ngFileUpload.directive('ngfSelect', ['$parse', '$timeout', '$compile', 'Upload', }); if (disallowObjectUrl) { - p = file.$ngfDataUrlPromise = deferred.promise; + p = file.$$ngfDataUrlPromise = deferred.promise; } else { - p = file.$ngfBlobUrlPromise = deferred.promise; + p = file.$$ngfBlobUrlPromise = deferred.promise; } p['finally'](function () { - delete file[disallowObjectUrl ? '$ngfDataUrlPromise' : '$ngfBlobUrlPromise']; + delete file[disallowObjectUrl ? '$$ngfDataUrlPromise' : '$$ngfBlobUrlPromise']; }); return p; }; @@ -1187,7 +1204,7 @@ ngFileUpload.directive('ngfSelect', ['$parse', '$timeout', '$compile', 'Upload', var disallowObjectUrl = Upload.attrGetter('ngfNoObjectUrl', attr, scope); Upload.dataUrl(file, disallowObjectUrl)['finally'](function () { $timeout(function () { - var src = (disallowObjectUrl ? file.dataUrl : file.blobUrl) || file.dataUrl; + var src = (disallowObjectUrl ? file.$ngfDataUrl : file.$ngfBlobUrl) || file.$ngfDataUrl; if (isBackground) { elem.css('background-image', 'url(\'' + (src || '') + '\')'); } else { @@ -1293,19 +1310,19 @@ ngFileUpload.directive('ngfSelect', ['$parse', '$timeout', '$compile', 'Upload', // if ($compileProvider.aHrefSanitizationWhitelist) $compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|ftp|mailto|tel|local|file|data|blob):/); //}]); // - //ngFileUpload.filter('ngfDataUrl', ['UploadDataUrl', '$sce', function (UploadDataUrl, $sce) { + //ngFileUpload.filter('$ngfDataUrl', ['UploadDataUrl', '$sce', function (UploadDataUrl, $sce) { // return function (file, disallowObjectUrl) { // if (angular.isString(file)) { // return $sce.trustAsResourceUrl(file); // } - // if (file && !file.dataUrl) { - // if (file.dataUrl === undefined && angular.isObject(file)) { - // file.dataUrl = null; - // UploadDataUrl.dataUrl(file, disallowObjectUrl); + // if (file && !file.$ngfDataUrl) { + // if (file.$ngfDataUrl === undefined && angular.isObject(file)) { + // file.$ngfDataUrl = null; + // UploadDataUrl.$ngfDataUrl(file, disallowObjectUrl); // } // return ''; // } - // return (file && file.dataUrl ? $sce.trustAsResourceUrl(file.dataUrl) : file) || ''; + // return (file && file.$ngfDataUrl ? $sce.trustAsResourceUrl(file.$ngfDataUrl) : file) || ''; // }; //}]); @@ -1579,10 +1596,10 @@ ngFileUpload.service('UploadValidate', ['UploadDataUrl', '$q', '$timeout', funct }; upload.imageDimensions = function (file) { - if (file.width && file.height) { + if (file.$ngfWidth && file.$ngfHeight) { var d = $q.defer(); $timeout(function () { - d.resolve({width: file.width, height: file.height}); + d.resolve({width: file.$ngfWidth, height: file.$ngfHeight}); }); return d.promise; } @@ -1601,8 +1618,8 @@ ngFileUpload.service('UploadValidate', ['UploadDataUrl', '$q', '$timeout', funct var width = img[0].clientWidth; var height = img[0].clientHeight; img.remove(); - file.width = width; - file.height = height; + file.$ngfWidth = width; + file.$ngfHeight = height; deferred.resolve({width: width, height: height}); } @@ -1645,10 +1662,10 @@ ngFileUpload.service('UploadValidate', ['UploadDataUrl', '$q', '$timeout', funct }; upload.mediaDuration = function (file) { - if (file.duration) { + if (file.$ngfDuration) { var d = $q.defer(); $timeout(function () { - d.resolve(file.duration); + d.resolve(file.$ngfDuration); }); return d.promise; } @@ -1666,7 +1683,7 @@ ngFileUpload.service('UploadValidate', ['UploadDataUrl', '$q', '$timeout', funct function success() { var duration = el[0].duration; - file.duration = duration; + file.$ngfDuration = duration; el.remove(); deferred.resolve(duration); } @@ -1757,7 +1774,7 @@ ngFileUpload.service('UploadResize', ['UploadValidate', '$q', '$timeout', functi var context = canvasElement.getContext('2d'); context.drawImage(imagenElement, 0, 0, dimensions.width, dimensions.height); deferred.resolve(canvasElement.toDataURL(type || 'image/WebP', quality || 1.0)); - } catch(e) { + } catch (e) { deferred.reject(e); } }; @@ -1777,7 +1794,7 @@ ngFileUpload.service('UploadResize', ['UploadValidate', '$q', '$timeout', functi return new Blob([u8arr], {type: mime}); }; - upload.isResizeSupported = function() { + upload.isResizeSupported = function () { var elem = document.createElement('canvas'); return window.atob && elem.getContext && elem.getContext('2d'); }; @@ -1785,13 +1802,15 @@ ngFileUpload.service('UploadResize', ['UploadValidate', '$q', '$timeout', functi upload.resize = function (file, width, height, quality) { var deferred = $q.defer(); if (file.type.indexOf('image') !== 0) { - $timeout(function() {deferred.resolve('Only images are allowed for resizing!');}); + $timeout(function () { + deferred.resolve('Only images are allowed for resizing!'); + }); return deferred.promise; } upload.dataUrl(file, true).then(function (url) { resize(url, width, height, quality, file.type).then(function (dataUrl) { - var blob= dataURLtoBlob(dataUrl); + var blob = dataURLtoBlob(dataUrl); blob.name = file.name; deferred.resolve(blob); }, function () { @@ -1939,7 +1958,7 @@ ngFileUpload.service('UploadResize', ['UploadValidate', '$q', '$timeout', functi if (clazz.accept || clazz.reject) { var items = evt.dataTransfer.items; if (items != null) { - var pattern = attrGetter('ngfPattern', scope, {$event: evt}); + var pattern = clazz.pattern || attrGetter('ngfPattern', scope, {$event: evt}); for (var i = 0; i < items.length; i++) { if (items[i].kind === 'file' || items[i].kind === '') { if (!upload.validatePattern(items[i], pattern)) { diff --git a/demo/src/main/webapp/js/ng-file-upload-all.min.js b/demo/src/main/webapp/js/ng-file-upload-all.min.js index 88a71bc5..ea8738e9 100644 --- a/demo/src/main/webapp/js/ng-file-upload-all.min.js +++ b/demo/src/main/webapp/js/ng-file-upload-all.min.js @@ -1,2 +1,2 @@ -/*! 7.3.9 */ -!function(){function a(a,b){window.XMLHttpRequest.prototype[a]=b(window.XMLHttpRequest.prototype[a])}function b(a,b,c){try{Object.defineProperty(a,b,{get:c})}catch(d){}}if(window.FileAPI||(window.FileAPI={}),FileAPI.shouldLoad=window.XMLHttpRequest&&!window.FormData||FileAPI.forceLoad,FileAPI.shouldLoad){var c=function(a){if(!a.__listeners){a.upload||(a.upload={}),a.__listeners=[];var b=a.upload.addEventListener;a.upload.addEventListener=function(c,d){a.__listeners[c]=d,b&&b.apply(this,arguments)}}};a("open",function(a){return function(b,d,e){c(this),this.__url=d;try{a.apply(this,[b,d,e])}catch(f){f.message.indexOf("Access is denied")>-1&&(this.__origError=f,a.apply(this,[b,"_fix_for_ie_crossdomain__",e]))}}}),a("getResponseHeader",function(a){return function(b){return this.__fileApiXHR&&this.__fileApiXHR.getResponseHeader?this.__fileApiXHR.getResponseHeader(b):null==a?null:a.apply(this,[b])}}),a("getAllResponseHeaders",function(a){return function(){return this.__fileApiXHR&&this.__fileApiXHR.getAllResponseHeaders?this.__fileApiXHR.getAllResponseHeaders():null==a?null:a.apply(this)}}),a("abort",function(a){return function(){return this.__fileApiXHR&&this.__fileApiXHR.abort?this.__fileApiXHR.abort():null==a?null:a.apply(this)}}),a("setRequestHeader",function(a){return function(b,d){if("__setXHR_"===b){c(this);var e=d(this);e instanceof Function&&e(this)}else this.__requestHeaders=this.__requestHeaders||{},this.__requestHeaders[b]=d,a.apply(this,arguments)}}),a("send",function(a){return function(){var c=this;if(arguments[0]&&arguments[0].__isFileAPIShim){var d=arguments[0],e={url:c.__url,jsonp:!1,cache:!0,complete:function(a,d){a&&angular.isString(a)&&-1!==a.indexOf("#2174")&&(a=null),c.__completed=!0,!a&&c.__listeners.load&&c.__listeners.load({type:"load",loaded:c.__loaded,total:c.__total,target:c,lengthComputable:!0}),!a&&c.__listeners.loadend&&c.__listeners.loadend({type:"loadend",loaded:c.__loaded,total:c.__total,target:c,lengthComputable:!0}),"abort"===a&&c.__listeners.abort&&c.__listeners.abort({type:"abort",loaded:c.__loaded,total:c.__total,target:c,lengthComputable:!0}),void 0!==d.status&&b(c,"status",function(){return 0===d.status&&a&&"abort"!==a?500:d.status}),void 0!==d.statusText&&b(c,"statusText",function(){return d.statusText}),b(c,"readyState",function(){return 4}),void 0!==d.response&&b(c,"response",function(){return d.response});var e=d.responseText||(a&&0===d.status&&"abort"!==a?a:void 0);b(c,"responseText",function(){return e}),b(c,"response",function(){return e}),a&&b(c,"err",function(){return a}),c.__fileApiXHR=d,c.onreadystatechange&&c.onreadystatechange(),c.onload&&c.onload()},progress:function(a){if(a.target=c,c.__listeners.progress&&c.__listeners.progress(a),c.__total=a.total,c.__loaded=a.loaded,a.total===a.loaded){var b=this;setTimeout(function(){c.__completed||(c.getAllResponseHeaders=function(){},b.complete(null,{status:204,statusText:"No Content"}))},FileAPI.noContentTimeout||1e4)}},headers:c.__requestHeaders};e.data={},e.files={};for(var f=0;f-1){e=h.substring(0,g+1);break}null==FileAPI.staticPath&&(FileAPI.staticPath=e),i.setAttribute("src",d||e+"FileAPI.min.js"),document.getElementsByTagName("head")[0].appendChild(i),FileAPI.hasFlash=b()}FileAPI.ngfFixIE=function(d,e,f){if(!b())throw'Adode Flash Player need to be installed. To check ahead use "FileAPI.hasFlash"';var g=function(){d.attr("disabled")?e&&e.removeClass("js-fileapi-wrapper"):(e.attr("__ngf_flash_")||(e.unbind("change"),e.unbind("click"),e.bind("change",function(a){h.apply(this,[a]),f.apply(this,[a])}),e.attr("__ngf_flash_","true")),e.addClass("js-fileapi-wrapper"),a(d)||e.css("position","absolute").css("top",c(d[0]).top+"px").css("left",c(d[0]).left+"px").css("width",d[0].offsetWidth+"px").css("height",d[0].offsetHeight+"px").css("filter","alpha(opacity=0)").css("display",d.css("display")).css("overflow","hidden").css("z-index","900000").css("visibility","visible"))};d.bind("mouseenter",g);var h=function(a){for(var b=FileAPI.getFiles(a),c=0;c=d.size&&(a._finished=!0,a._end=d.size);var h=d.slice(a._start,a._end||d.size);h.name=d.name,d=h,a._chunkSize&&(b.append("chunkSize",a._end-a._start),b.append("chunkNumber",Math.floor(a._start/a._chunkSize)),b.append("totalSize",a._file.size))}b.append(e,d,d.fileName||d.name)}else{if(!angular.isObject(d))throw"Expected file object in Upload.upload file option: "+d.toString();for(var i in d)if(d.hasOwnProperty(i)){var j=i.split(",");j[1]&&(d[i].fileName=j[1].replace(/^\s+|\s+$/g,"")),g(b,d[i],j[0])}}}return a._chunkSize=e.translateScalars(a.resumeChunkSize),a._chunkSize=a._chunkSize?parseInt(a._chunkSize.toString()):null,a.headers=a.headers||{},a.headers["Content-Type"]=void 0,a.transformRequest=a.transformRequest?angular.isArray(a.transformRequest)?a.transformRequest:[a.transformRequest]:[],a.transformRequest.push(function(c){var d,e=new FormData,f={};for(d in a.fields)a.fields.hasOwnProperty(d)&&(f[d]=a.fields[d]);c&&(f.data=c);for(d in f)if(f.hasOwnProperty(d)){var h=f[d];a.formDataAppender?a.formDataAppender(e,d,h):b(e,h,d)}if(null!=a.file)if(angular.isArray(a.file))for(var i=0;it;t++)if(h[s].name===o[t].name){n.push(h[s]);break}t===r&&(o.push(h[s]),q=!0)}if(!q)return;h=o}else h=o.concat(h)}d.$$ngfPrevFiles=h,j?k():e.validate(h,c,d,f,e.attrGetter("ngfValidateLater",d),function(){l(h,function(){b(function(){k()})})});for(var u=o.length;u--;){var v=o[u];window.URL&&v.blobUrl&&(URL.revokeObjectURL(v.blobUrl),delete v.blobUrl)}},e}]),ngFileUpload.directive("ngfSelect",["$parse","$timeout","$compile","Upload",function(a,b,c,d){function e(a){var b=a.match(/Android[^\d]*(\d+)\.(\d+)/);if(b&&b.length>2){var c=d.defaults.androidFixMinorVersion||4;return parseInt(b[1])<4||parseInt(b[1])===c&&parseInt(b[2])');return n(a),a.css("visibility","hidden").css("position","absolute").css("overflow","hidden").css("width","0px").css("height","0px").css("border","none").css("margin","0px").css("padding","0px").attr("tabindex","-1"),g.push({el:b,ref:a}),document.body.appendChild(a[0]),a}function p(c){if(b.attr("disabled")||t("ngfSelectDisabled",a))return!1;var d=q(c);return null!=d?d:(r(c),e(navigator.userAgent)?setTimeout(function(){w[0].click()},0):w[0].click(),!1)}function q(a){var b=a.changedTouches||a.originalEvent&&a.originalEvent.changedTouches;if("touchstart"===a.type)return v=b?b[0].clientY:0,!0;if(a.stopPropagation(),a.preventDefault(),"touchend"===a.type){var c=b?b[0].clientY:0;if(Math.abs(c-v)>20)return!1}}function r(b){w.val()&&(w.val(null),j.updateModel(d,c,a,l(),null,b,!0))}function s(a){if(w&&!w.attr("__ngf_ie10_Fix_")){if(!w[0].parentNode)return void(w=null);a.preventDefault(),a.stopPropagation(),w.unbind("click");var b=w.clone();return w.replaceWith(b),w=b,w.attr("__ngf_ie10_Fix_","true"),w.bind("change",m),w.bind("click",s),w[0].click(),!1}w.removeAttr("__ngf_ie10_Fix_")}var t=function(a,b){return j.attrGetter(a,c,b)},u=[];u.push(a.$watch(t("ngfMultiple"),function(){w.attr("multiple",t("ngfMultiple",a))})),u.push(a.$watch(t("ngfCapture"),function(){w.attr("capture",t("ngfCapture",a))})),c.$observe("accept",function(){w.attr("accept",t("accept"))}),u.push(function(){c.$$observers&&delete c.$$observers.accept});var v=0,w=b;k()||(w=o()),w.bind("change",m),k()?b.bind("click",r):b.bind("click touchstart touchend",p),j.registerValidators(d,w,c,a),-1!==navigator.appVersion.indexOf("MSIE 10")&&w.bind("click",s),a.$on("$destroy",function(){k()||w.remove(),angular.forEach(u,function(a){a()})}),h(function(){for(var a=0;a.ngf-hide{display:none !important}");document.getElementsByTagName("head")[0].appendChild(c[0]),ngFileUpload.directive("ngfSrc",["Upload","$timeout",function(a,c){return{restrict:"AE",link:function(d,e,f){b(a,c,d,e,f,"ngfSrc",a.attrGetter("ngfResize",f,d),!1)}}}]),ngFileUpload.directive("ngfBackground",["Upload","$timeout",function(a,c){return{restrict:"AE",link:function(d,e,f){b(a,c,d,e,f,"ngfBackground",a.attrGetter("ngfResize",f,d),!0)}}}]),ngFileUpload.directive("ngfThumbnail",["Upload","$timeout",function(a,c){return{restrict:"AE",link:function(d,e,f){var g=a.attrGetter("ngfSize",f,d);b(a,c,d,e,f,"ngfThumbnail",g,a.attrGetter("ngfAsBackground",f,d))}}}])}(),ngFileUpload.service("UploadValidate",["UploadDataUrl","$q","$timeout",function(a,b,c){function d(a){var b="",c=[];if(a.length>2&&"/"===a[0]&&"/"===a[a.length-1])b=a.substring(1,a.length-1);else{var e=a.split(",");if(e.length>1)for(var f=0;f|:\\-]","g"),"\\$&")+"$",b=b.replace(/\\\*/g,".*").replace(/\\\?/g,"."))}return{regexp:b,excludes:c}}var e=a;return e.registerValidators=function(a,b,c,d){function f(a){angular.forEach(a.$ngfValidations,function(b){a.$setValidity(b.name,b.valid)})}a&&(a.$ngfValidations=[],a.$formatters.push(function(g){return e.attrGetter("ngfValidateLater",c,d)||!a.$$ngfValidated?(e.validate(g,a,c,d,!1,function(){f(a),a.$$ngfValidated=!1}),g&&0===g.length&&(g=null),!b||null!=g&&0!==g.length||b.val()&&b.val(null)):(f(a),a.$$ngfValidated=!1),g}))},e.validatePattern=function(a,b){if(!b)return!0;var c=d(b),e=!0;if(c.regexp&&c.regexp.length){var f=new RegExp(c.regexp,"i");e=null!=a.type&&f.test(a.type)||null!=a.name&&f.test(a.name)}for(var g=c.excludes.length;g--;){var h=new RegExp(c.excludes[g],"i");e=e&&(null==a.type||h.test(a.type))&&(null==a.name||h.test(a.name))}return e},e.validate=function(a,b,c,d,f,g){function h(c,d,e){if(a){for(var f="ngf"+c[0].toUpperCase()+c.substr(1),g=a.length,h=null;g--;){var i=a[g],k=j(f,{$file:i});null==k&&(k=d(j("ngfValidate")||{}),h=null==h?!0:h),null!=k&&(e(i,k)||(i.$error=c,i.$errorParam=k,a.splice(g,1),h=!1))}null!==h&&b.$ngfValidations.push({name:c,valid:h})}}function i(c,d,e,f,h){if(a){var i=0,l=!1,m="ngf"+c[0].toUpperCase()+c.substr(1);a=void 0===a.length?[a]:a,angular.forEach(a,function(a){if(0!==a.type.search(e))return!0;var n=j(m,{$file:a})||d(j("ngfValidate",{$file:a})||{});n&&(k++,i++,f(a,n).then(function(b){h(b,n)||(a.$error=c,a.$errorParam=n,l=!0)},function(){j("ngfValidateForce",{$file:a})&&(a.$error=c,a.$errorParam=n,l=!0)})["finally"](function(){k--,i--,i||b.$ngfValidations.push({name:c,valid:!l}),k||g.call(b,b.$ngfValidations)}))})}}b=b||{},b.$ngfValidations=b.$ngfValidations||[],angular.forEach(b.$ngfValidations,function(a){a.valid=!0});var j=function(a,b){return e.attrGetter(a,c,d,b)};if(f)return void g.call(b);if(b.$$ngfValidated=!0,null==a||0===a.length)return void g.call(b);if(a=void 0===a.length?[a]:a.slice(0),h("pattern",function(a){return a.pattern},e.validatePattern),h("minSize",function(a){return a.size&&a.size.min},function(a,b){return a.size>=e.translateScalars(b)}),h("maxSize",function(a){return a.size&&a.size.max},function(a,b){return a.size<=e.translateScalars(b)}),h("validateFn",function(){return null},function(a,b){return b===!0||null===b||""===b}),!a.length)return void g.call(b,b.$ngfValidations);var k=0;i("maxHeight",function(a){return a.height&&a.height.max},/image/,this.imageDimensions,function(a,b){return a.height<=b}),i("minHeight",function(a){return a.height&&a.height.min},/image/,this.imageDimensions,function(a,b){return a.height>=b}),i("maxWidth",function(a){return a.width&&a.width.max},/image/,this.imageDimensions,function(a,b){return a.width<=b}),i("minWidth",function(a){return a.width&&a.width.min},/image/,this.imageDimensions,function(a,b){return a.width>=b}),i("ratio",function(a){return a.ratio},/image/,this.imageDimensions,function(a,b){for(var c=b.toString().split(","),d=!1,e=0;e-1?parseFloat(f.substring(0,g))/parseFloat(f.substring(g+1)):parseFloat(f),Math.abs(a.width/a.height-f)<1e-4&&(d=!0)}return d}),i("maxDuration",function(a){return a.duration&&a.duration.max},/audio|video/,this.mediaDuration,function(a,b){return a<=e.translateScalars(b)}),i("minDuration",function(a){return a.duration&&a.duration.min},/audio|video/,this.mediaDuration,function(a,b){return a>=e.translateScalars(b)}),i("validateAsyncFn",function(){return null},/./,function(a,b){return b},function(a){return a===!0||null===a||""===a}),k||g.call(b,b.$ngfValidations)},e.imageDimensions=function(a){if(a.width&&a.height){var d=b.defer();return c(function(){d.resolve({width:a.width,height:a.height})}),d.promise}if(a.$ngfDimensionPromise)return a.$ngfDimensionPromise;var f=b.defer();return c(function(){return 0!==a.type.indexOf("image")?void f.reject("not image"):void e.dataUrl(a).then(function(b){function d(){var b=h[0].clientWidth,c=h[0].clientHeight;h.remove(),a.width=b,a.height=c,f.resolve({width:b,height:c})}function e(){h.remove(),f.reject("load error")}function g(){c(function(){h[0].parentNode&&(h[0].clientWidth?d():i>10?e():g())},1e3)}var h=angular.element("").attr("src",b).css("visibility","hidden").css("position","fixed");h.on("load",d),h.on("error",e);var i=0;g(),angular.element(document.getElementsByTagName("body")[0]).append(h)},function(){f.reject("load error")})}),a.$ngfDimensionPromise=f.promise,a.$ngfDimensionPromise["finally"](function(){delete a.$ngfDimensionPromise}),a.$ngfDimensionPromise},e.mediaDuration=function(a){if(a.duration){var d=b.defer();return c(function(){d.resolve(a.duration)}),d.promise}if(a.$ngfDurationPromise)return a.$ngfDurationPromise;var f=b.defer();return c(function(){return 0!==a.type.indexOf("audio")&&0!==a.type.indexOf("video")?void f.reject("not media"):void e.dataUrl(a).then(function(b){function d(){var b=h[0].duration;a.duration=b,h.remove(),f.resolve(b)}function e(){h.remove(),f.reject("load error")}function g(){c(function(){h[0].parentNode&&(h[0].duration?d():i>10?e():g())},1e3)}var h=angular.element(0===a.type.indexOf("audio")?"