/**! * AngularJS file upload directives and services. Supoorts: file upload/drop/paste, resume, cancel/abort, * progress, resize, thumbnail, preview, validation and CORS * @author Danial * @version 12.0.4 */ if (window.XMLHttpRequest && !(window.FileAPI && FileAPI.shouldLoad)) { window.XMLHttpRequest.prototype.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); } }; })(window.XMLHttpRequest.prototype.setRequestHeader); } var ngFileUpload = angular.module('ngFileUpload', []); ngFileUpload.version = '12.0.4'; ngFileUpload.service('UploadBase', ['$http', '$q', '$timeout', function ($http, $q, $timeout) { var upload = this; upload.promisesCount = 0; this.isResumeSupported = function () { return window.Blob && window.Blob.prototype.slice; }; var resumeSupported = this.isResumeSupported(); function sendHttp(config) { config.method = config.method || 'POST'; config.headers = config.headers || {}; var deferred = config._deferred = config._deferred || $q.defer(); var promise = deferred.promise; function notifyProgress(e) { if (deferred.notify) { deferred.notify(e); } if (promise.progressFunc) { $timeout(function () { promise.progressFunc(e); }); } } function getNotifyEvent(n) { if (config._start != null && resumeSupported) { return { loaded: n.loaded + config._start, total: (config._file && config._file.size) || n.total, type: n.type, config: config, lengthComputable: true, target: n.target }; } else { return n; } } if (!config.disableProgress) { config.headers.__setXHR_ = function () { return function (xhr) { if (!xhr || !xhr.upload || !xhr.upload.addEventListener) return; config.__XHR = xhr; if (config.xhrFn) config.xhrFn(xhr); xhr.upload.addEventListener('progress', function (e) { e.config = config; notifyProgress(getNotifyEvent(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; notifyProgress(getNotifyEvent(e)); } }, false); }; }; } function uploadWithAngular() { $http(config).then(function (r) { if (resumeSupported && config._chunkSize && !config._finished && config._file) { notifyProgress({ loaded: config._end, total: config._file && config._file.size, config: config, type: 'progress' } ); upload.upload(config, true); } else { if (config._finished) delete config._finished; deferred.resolve(r); } }, function (e) { deferred.reject(e); }, function (n) { deferred.notify(n); } ); } if (!resumeSupported) { uploadWithAngular(); } else if (config._chunkSize && config._end && !config._finished) { config._start = config._end; config._end += config._chunkSize; uploadWithAngular(); } else if (config.resumeSizeUrl) { $http.get(config.resumeSizeUrl).then(function (resp) { if (config.resumeSizeResponseReader) { config._start = config.resumeSizeResponseReader(resp.data); } else { config._start = parseInt((resp.data.size == null ? resp.data : resp.data.size).toString()); } if (config._chunkSize) { config._end = config._start + config._chunkSize; } uploadWithAngular(); }, function (e) { throw e; }); } else if (config.resumeSize) { config.resumeSize().then(function (size) { config._start = size; uploadWithAngular(); }, function (e) { throw e; }); } else { if (config._chunkSize) { config._start = 0; config._end = config._start + config._chunkSize; } uploadWithAngular(); } 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.progressFunc = fn; promise.then(null, null, function (n) { fn(n); }); return promise; }; promise.abort = promise.pause = function () { if (config.__XHR) { $timeout(function () { config.__XHR.abort(); }); } return promise; }; promise.xhr = function (fn) { config.xhrFn = (function (origXhrFn) { return function () { if (origXhrFn) origXhrFn.apply(promise, arguments); fn.apply(promise, arguments); }; })(config.xhrFn); return promise; }; upload.promisesCount++; promise['finally'](function () { upload.promisesCount--; }); return promise; } this.isUploadInProgress = function () { return upload.promisesCount > 0; }; this.rename = function (file, name) { file.ngfName = name; return file; }; this.jsonBlob = function (val) { if (val != null && !angular.isString(val)) { val = JSON.stringify(val); } var blob = new window.Blob([val], {type: 'application/json'}); blob._ngfBlob = true; return blob; }; this.json = function (val) { return angular.toJson(val); }; function copy(obj) { var clone = {}; for (var key in obj) { if (obj.hasOwnProperty(key)) { clone[key] = obj[key]; } } return clone; } this.isFile = function (file) { return file != null && (file instanceof window.Blob || (file.flashId && file.name && file.size)); }; this.upload = function (config, internal) { 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._chunkSize); formData.append('_currentChunkSize', 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)) { val = val.toISOString(); } if (angular.isString(val)) { formData.append(key, val); } else if (upload.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)) { if (val.$$ngfCircularDetection) throw 'ngFileUpload: Circular reference in config.data. Make sure specified data for Upload.upload() has no circular reference: ' + key; val.$$ngfCircularDetection = true; try { for (var k in val) { if (val.hasOwnProperty(k) && k !== '$$ngfCircularDetection') { 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)); } } } finally { delete val.$$ngfCircularDetection; } } else { formData.append(key, val); } } } } 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 window.FormData(), key; data = data || config.fields || {}; if (config.file) { data.file = config.file; } 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); } } } return formData; }); } if (!internal) config = copy(config); if (!config._isDigested) { config._isDigested = true; digestConfig(); } return sendHttp(config); }; this.http = function (config) { config = copy(config); config.transformRequest = config.transformRequest || function (data) { if ((window.ArrayBuffer && data instanceof window.ArrayBuffer) || data instanceof window.Blob) { return data; } return $http.defaults.transformRequest[0].apply(this, arguments); }; config._chunkSize = upload.translateScalars(config.resumeChunkSize); config._chunkSize = config._chunkSize ? parseInt(config._chunkSize.toString()) : null; return sendHttp(config); }; this.translateScalars = function (str) { if (angular.isString(str)) { if (str.search(/kb/i) === str.length - 2) { return parseFloat(str.substring(0, str.length - 2) * 1024); } else if (str.search(/mb/i) === str.length - 2) { return parseFloat(str.substring(0, str.length - 2) * 1048576); } else if (str.search(/gb/i) === str.length - 2) { return parseFloat(str.substring(0, str.length - 2) * 1073741824); } else if (str.search(/b/i) === str.length - 1) { return parseFloat(str.substring(0, str.length - 1)); } else if (str.search(/s/i) === str.length - 1) { return parseFloat(str.substring(0, str.length - 1)); } else if (str.search(/m/i) === str.length - 1) { return parseFloat(str.substring(0, str.length - 1) * 60); } else if (str.search(/h/i) === str.length - 1) { return parseFloat(str.substring(0, str.length - 1) * 3600); } } return str; }; this.urlToBlob = function(url) { var defer = $q.defer(); $http({url: url, method: 'get', responseType: 'arraybuffer'}).then(function (resp) { var arrayBufferView = new Uint8Array(resp.data); var type = resp.headers('content-type') || 'image/WebP'; var blob = new window.Blob([arrayBufferView], {type: type}); defer.resolve(blob); //var split = type.split('[/;]'); //blob.name = url.substring(0, 150).replace(/\W+/g, '') + '.' + (split.length > 1 ? split[1] : 'jpg'); }, function (e) { defer.reject(e); }); return defer.promise; }; this.setDefaults = function (defaults) { this.defaults = defaults || {}; }; this.defaults = {}; this.version = ngFileUpload.version; } ]); ngFileUpload.service('Upload', ['$parse', '$timeout', '$compile', '$q', 'UploadExif', function ($parse, $timeout, $compile, $q, UploadExif) { var upload = UploadExif; upload.getAttrWithDefaults = function (attr, name) { if (attr[name] != null) return attr[name]; var def = upload.defaults[name]; return (def == null ? def : (angular.isString(def) ? def : JSON.stringify(def))); }; upload.attrGetter = function (name, attr, scope, params) { var attrVal = this.getAttrWithDefaults(attr, name); if (scope) { try { if (params) { return $parse(attrVal)(scope, params); } else { return $parse(attrVal)(scope); } } catch (e) { // hangle string value without single qoute if (name.search(/min|max|pattern/i)) { return attrVal; } else { throw e; } } } else { return attrVal; } }; upload.shouldUpdateOn = function (type, attr, scope) { var modelOptions = upload.attrGetter('ngModelOptions', attr, scope); if (modelOptions && modelOptions.updateOn) { return modelOptions.updateOn.split(' ').indexOf(type) > -1; } return true; }; upload.emptyPromise = function () { var d = $q.defer(); var args = arguments; $timeout(function () { d.resolve.apply(d, args); }); return d.promise; }; upload.rejectPromise = function () { var d = $q.defer(); var args = arguments; $timeout(function () { d.reject.apply(d, args); }); return d.promise; }; upload.happyPromise = function (promise, data) { var d = $q.defer(); promise.then(function (result) { d.resolve(result); }, function (error) { $timeout(function () { throw error; }); d.resolve(data); }); return d.promise; }; function applyExifRotations(files, attr, scope) { var promises = [upload.emptyPromise()]; angular.forEach(files, function (f, i) { if (f.type.indexOf('image/jpeg') === 0 && upload.attrGetter('ngfFixOrientation', attr, scope, {$file: f})) { promises.push(upload.happyPromise(upload.applyExifRotation(f), f).then(function (fixedFile) { files.splice(i, 1, fixedFile); })); } }); return $q.all(promises); } function resize(files, attr, scope) { var resizeVal = upload.attrGetter('ngfResize', attr, scope); if (!resizeVal || !upload.isResizeSupported() || !files.length) return upload.emptyPromise(); if (resizeVal instanceof Function) { var defer = $q.defer(); resizeVal(files).then(function (p) { resizeWithParams(p, files, attr, scope).then(function (r) { defer.resolve(r); }, function (e) { defer.reject(e); }); }, function (e) { defer.reject(e); }); } else { return resizeWithParams(resizeVal, files, attr, scope); } } function resizeWithParams(param, files, attr, scope) { var promises = [upload.emptyPromise()]; function handleFile(f, i) { if (f.type.indexOf('image') === 0) { if (param.pattern && !upload.validatePattern(f, param.pattern)) return; var promise = upload.resize(f, param.width, param.height, param.quality, param.type, param.ratio, param.centerCrop, function (width, height) { return upload.attrGetter('ngfResizeIf', attr, scope, {$width: width, $height: height, $file: f}); }, param.restoreExif !== false); promises.push(promise); promise.then(function (resizedFile) { files.splice(i, 1, resizedFile); }, function (e) { f.$error = 'resize'; f.$errorParam = (e ? (e.message ? e.message : e) + ': ' : '') + (f && f.name); }); } } for (var i = 0; i < files.length; i++) { handleFile(files[i], i); } return $q.all(promises); } upload.updateModel = function (ngModel, attr, scope, fileChange, files, evt, noDelay) { function update(files, invalidFiles, newFiles, dupFiles, isSingleModel) { attr.$$ngfPrevValidFiles = files; attr.$$ngfPrevInvalidFiles = invalidFiles; var file = files && files.length ? files[0] : null; var invalidFile = invalidFiles && invalidFiles.length ? invalidFiles[0] : null; if (ngModel) { upload.applyModelValidation(ngModel, files); ngModel.$setViewValue(isSingleModel ? file : files); } if (fileChange) { $parse(fileChange)(scope, { $files: files, $file: file, $newFiles: newFiles, $duplicateFiles: dupFiles, $invalidFiles: invalidFiles, $invalidFile: invalidFile, $event: evt }); } var invalidModel = upload.attrGetter('ngfModelInvalid', attr); if (invalidModel) { $timeout(function () { $parse(invalidModel).assign(scope, isSingleModel ? invalidFile : invalidFiles); }); } $timeout(function () { // scope apply changes }); } var allNewFiles, dupFiles = [], prevValidFiles, prevInvalidFiles, invalids = [], valids = []; function removeDuplicates() { function equals(f1, f2) { return f1.name === f2.name && (f1.$ngfOrigSize || f1.size) === (f2.$ngfOrigSize || f2.size) && f1.type === f2.type; } function isInPrevFiles(f) { var j; for (j = 0; j < prevValidFiles.length; j++) { if (equals(f, prevValidFiles[j])) { return true; } } for (j = 0; j < prevInvalidFiles.length; j++) { if (equals(f, prevInvalidFiles[j])) { return true; } } return false; } if (files) { allNewFiles = []; dupFiles = []; for (var i = 0; i < files.length; i++) { if (isInPrevFiles(files[i])) { dupFiles.push(files[i]); } else { allNewFiles.push(files[i]); } } } } function toArray(v) { return angular.isArray(v) ? v : [v]; } function separateInvalids() { valids = []; invalids = []; angular.forEach(allNewFiles, function (file) { if (file.$error) { invalids.push(file); } else { valids.push(file); } }); } function resizeAndUpdate() { function updateModel() { $timeout(function () { update(keep ? prevValidFiles.concat(valids) : valids, keep ? prevInvalidFiles.concat(invalids) : invalids, files, dupFiles, isSingleModel); }, options && options.debounce ? options.debounce.change || options.debounce : 0); } resize(validateAfterResize ? allNewFiles : valids, attr, scope).then(function () { if (validateAfterResize) { upload.validate(allNewFiles, prevValidFiles.length, ngModel, attr, scope).then(function () { separateInvalids(); updateModel(); }); } else { updateModel(); } }, function (e) { throw 'Could not resize files ' + e; }); } prevValidFiles = attr.$$ngfPrevValidFiles || []; prevInvalidFiles = attr.$$ngfPrevInvalidFiles || []; if (ngModel && ngModel.$modelValue) { prevValidFiles = toArray(ngModel.$modelValue); } var keep = upload.attrGetter('ngfKeep', attr, scope); allNewFiles = (files || []).slice(0); if (keep === 'distinct' || upload.attrGetter('ngfKeepDistinct', attr, scope) === true) { removeDuplicates(attr, scope); } var isSingleModel = !keep && !upload.attrGetter('ngfMultiple', attr, scope) && !upload.attrGetter('multiple', attr); if (keep && !allNewFiles.length) return; upload.attrGetter('ngfBeforeModelChange', attr, scope, { $files: files, $file: files && files.length ? files[0] : null, $newFiles: allNewFiles, $duplicateFiles: dupFiles, $event: evt }); var validateAfterResize = upload.attrGetter('ngfValidateAfterResize', attr, scope); var options = upload.attrGetter('ngModelOptions', attr, scope); upload.validate(allNewFiles, prevValidFiles.length, ngModel, attr, scope).then(function () { if (noDelay) { update(allNewFiles, [], files, dupFiles, isSingleModel); } else { if ((!options || !options.allowInvalid) && !validateAfterResize) { separateInvalids(); } else { valids = allNewFiles; } if (upload.attrGetter('ngfFixOrientation', attr, scope) && upload.isExifSupported()) { applyExifRotations(valids, attr, scope).then(function () { resizeAndUpdate(); }); } else { resizeAndUpdate(); } } }); }; return upload; }]); ngFileUpload.directive('ngfSelect', ['$parse', '$timeout', '$compile', 'Upload', function ($parse, $timeout, $compile, Upload) { var generatedElems = []; function isDelayedClickSupported(ua) { // fix for android native browser < 4.4 and safari windows var m = ua.match(/Android[^\d]*(\d+)\.(\d+)/); if (m && m.length > 2) { var v = Upload.defaults.androidFixMinorVersion || 4; return parseInt(m[1]) < 4 || (parseInt(m[1]) === v && parseInt(m[2]) < v); } // safari on windows return ua.indexOf('Chrome') === -1 && /.*Windows.*Safari.*/.test(ua); } function linkFileSelect(scope, elem, attr, ngModel, $parse, $timeout, $compile, upload) { /** @namespace attr.ngfSelect */ /** @namespace attr.ngfChange */ /** @namespace attr.ngModel */ /** @namespace attr.ngModelOptions */ /** @namespace attr.ngfMultiple */ /** @namespace attr.ngfCapture */ /** @namespace attr.ngfValidate */ /** @namespace attr.ngfKeep */ var attrGetter = function (name, scope) { return upload.attrGetter(name, attr, scope); }; function isInputTypeFile() { return elem[0].tagName.toLowerCase() === 'input' && attr.type && attr.type.toLowerCase() === 'file'; } function fileChangeAttr() { return attrGetter('ngfChange') || attrGetter('ngfSelect'); } function changeFn(evt) { if (upload.shouldUpdateOn('change', attr, scope)) { var fileList = evt.__files_ || (evt.target && evt.target.files), files = []; for (var i = 0; i < fileList.length; i++) { files.push(fileList[i]); } upload.updateModel(ngModel, attr, scope, fileChangeAttr(), files.length ? files : null, evt); } } upload.registerModelChangeValidator(ngModel, attr, scope); var unwatches = []; unwatches.push(scope.$watch(attrGetter('ngfMultiple'), function () { fileElem.attr('multiple', attrGetter('ngfMultiple', scope)); })); unwatches.push(scope.$watch(attrGetter('ngfCapture'), function () { fileElem.attr('capture', attrGetter('ngfCapture', scope)); })); unwatches.push(scope.$watch(attrGetter('ngfAccept'), function () { fileElem.attr('accept', attrGetter('ngfAccept', scope)); })); attr.$observe('accept', function () { fileElem.attr('accept', attrGetter('accept')); }); unwatches.push(function () { if (attr.$$observers) delete attr.$$observers.accept; }); function bindAttrToFileInput(fileElem) { if (elem !== fileElem) { for (var i = 0; i < elem[0].attributes.length; i++) { var attribute = elem[0].attributes[i]; if (attribute.name !== 'type' && attribute.name !== 'class' && attribute.name !== 'style') { if (attribute.value == null || attribute.value === '') { if (attribute.name === 'required') attribute.value = 'required'; if (attribute.name === 'multiple') attribute.value = 'multiple'; } fileElem.attr(attribute.name, attribute.name === 'id' ? 'ngf-' + attribute.value : attribute.value); } } } } function createFileInput() { if (isInputTypeFile()) { return elem; } var fileElem = angular.element(''); bindAttrToFileInput(fileElem); var label = angular.element(''); label.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'); generatedElems.push({el: elem, ref: label}); document.body.appendChild(label.append(fileElem)[0]); return fileElem; } var initialTouchStartY = 0; function clickHandler(evt) { if (elem.attr('disabled')) return false; if (attrGetter('ngfSelectDisabled', scope)) return; var r = handleTouch(evt); if (r != null) return r; resetModel(evt); // fix for md when the element is removed from the DOM and added back #460 try { if (!isInputTypeFile() && !document.body.contains(fileElem[0])) { generatedElems.push({el: elem, ref: fileElem.parent()}); document.body.appendChild(fileElem.parent()[0]); fileElem.bind('change', changeFn); } } catch(e){/*ignore*/} if (isDelayedClickSupported(navigator.userAgent)) { setTimeout(function () { fileElem[0].click(); }, 0); } else { fileElem[0].click(); } return false; } function handleTouch(evt) { var touches = evt.changedTouches || (evt.originalEvent && evt.originalEvent.changedTouches); if (evt.type === 'touchstart') { initialTouchStartY = touches ? touches[0].clientY : 0; return true; // don't block event default } else { evt.stopPropagation(); evt.preventDefault(); // prevent scroll from triggering event if (evt.type === 'touchend') { var currentLocation = touches ? touches[0].clientY : 0; if (Math.abs(currentLocation - initialTouchStartY) > 20) return false; } } } var fileElem = elem; function resetModel(evt) { if (upload.shouldUpdateOn('click', attr, scope) && fileElem.val()) { fileElem.val(null); upload.updateModel(ngModel, attr, scope, fileChangeAttr(), null, evt, true); } } if (!isInputTypeFile()) { fileElem = createFileInput(); } fileElem.bind('change', changeFn); if (!isInputTypeFile()) { elem.bind('click touchstart touchend', clickHandler); } else { elem.bind('click', resetModel); } function ie10SameFileSelectFix(evt) { if (fileElem && !fileElem.attr('__ngf_ie10_Fix_')) { if (!fileElem[0].parentNode) { fileElem = null; return; } evt.preventDefault(); evt.stopPropagation(); fileElem.unbind('click'); var clone = fileElem.clone(); fileElem.replaceWith(clone); fileElem = clone; fileElem.attr('__ngf_ie10_Fix_', 'true'); fileElem.bind('change', changeFn); fileElem.bind('click', ie10SameFileSelectFix); fileElem[0].click(); return false; } else { fileElem.removeAttr('__ngf_ie10_Fix_'); } } if (navigator.appVersion.indexOf('MSIE 10') !== -1) { fileElem.bind('click', ie10SameFileSelectFix); } if (ngModel) ngModel.$formatters.push(function (val) { if (val == null || val.length === 0) { if (fileElem.val()) { fileElem.val(null); } } return val; }); scope.$on('$destroy', function () { if (!isInputTypeFile()) fileElem.parent().remove(); angular.forEach(unwatches, function (unwatch) { unwatch(); }); }); $timeout(function () { for (var i = 0; i < generatedElems.length; i++) { var g = generatedElems[i]; if (!document.body.contains(g.el[0])) { generatedElems.splice(i, 1); g.ref.remove(); } } }); if (window.FileAPI && window.FileAPI.ngfFixIE) { window.FileAPI.ngfFixIE(elem, fileElem, changeFn); } } return { restrict: 'AEC', require: '?ngModel', link: function (scope, elem, attr, ngModel) { linkFileSelect(scope, elem, attr, ngModel, $parse, $timeout, $compile, Upload); } }; }]); (function () { ngFileUpload.service('UploadDataUrl', ['UploadBase', '$timeout', '$q', function (UploadBase, $timeout, $q) { var upload = UploadBase; upload.base64DataUrl = function (file) { if (angular.isArray(file)) { var d = $q.defer(), count = 0; angular.forEach(file, function (f) { upload.dataUrl(f, true)['finally'](function () { count++; if (count === file.length) { var urls = []; angular.forEach(file, function (ff) { urls.push(ff.$ngfDataUrl); }); d.resolve(urls, file); } }); }); return d.promise; } else { return upload.dataUrl(file, true); } }; upload.dataUrl = function (file, disallowObjectUrl) { if (!file) return upload.emptyPromise(file, file); if ((disallowObjectUrl && file.$ngfDataUrl != null) || (!disallowObjectUrl && file.$ngfBlobUrl != null)) { return upload.emptyPromise(disallowObjectUrl ? file.$ngfDataUrl : file.$ngfBlobUrl, file); } var p = disallowObjectUrl ? file.$$ngfDataUrlPromise : file.$$ngfBlobUrlPromise; if (p) return p; var deferred = $q.defer(); $timeout(function () { if (window.FileReader && file && (!window.FileAPI || navigator.userAgent.indexOf('MSIE 8') === -1 || file.size < 20000) && (!window.FileAPI || navigator.userAgent.indexOf('MSIE 9') === -1 || file.size < 4000000)) { //prefer URL.createObjectURL for handling refrences to files of all sizes //since it doesn´t build a large string in memory var URL = window.URL || window.webkitURL; if (URL && URL.createObjectURL && !disallowObjectUrl) { var url; try { url = URL.createObjectURL(file); } catch (e) { $timeout(function () { file.$ngfBlobUrl = ''; deferred.reject(); }); return; } $timeout(function () { file.$ngfBlobUrl = url; if (url) { deferred.resolve(url, file); upload.blobUrls = upload.blobUrls || []; upload.blobUrlsTotalSize = upload.blobUrlsTotalSize || 0; upload.blobUrls.push({url: url, size: file.size}); upload.blobUrlsTotalSize += file.size || 0; var maxMemory = upload.defaults.blobUrlsMaxMemory || 268435456; var maxLength = upload.defaults.blobUrlsMaxQueueSize || 200; while ((upload.blobUrlsTotalSize > maxMemory || upload.blobUrls.length > maxLength) && upload.blobUrls.length > 1) { var obj = upload.blobUrls.splice(0, 1)[0]; URL.revokeObjectURL(obj.url); upload.blobUrlsTotalSize -= obj.size; } } }); } else { var fileReader = new FileReader(); fileReader.onload = function (e) { $timeout(function () { file.$ngfDataUrl = e.target.result; deferred.resolve(e.target.result, file); $timeout(function () { delete file.$ngfDataUrl; }, 1000); }); }; fileReader.onerror = function () { $timeout(function () { file.$ngfDataUrl = ''; deferred.reject(); }); }; fileReader.readAsDataURL(file); } } else { $timeout(function () { file[disallowObjectUrl ? '$ngfDataUrl' : '$ngfBlobUrl'] = ''; deferred.reject(); }); } }); if (disallowObjectUrl) { p = file.$$ngfDataUrlPromise = deferred.promise; } else { p = file.$$ngfBlobUrlPromise = deferred.promise; } p['finally'](function () { delete file[disallowObjectUrl ? '$$ngfDataUrlPromise' : '$$ngfBlobUrlPromise']; }); return p; }; return upload; }]); function getTagType(el) { if (el.tagName.toLowerCase() === 'img') return 'image'; if (el.tagName.toLowerCase() === 'audio') return 'audio'; if (el.tagName.toLowerCase() === 'video') return 'video'; return /./; } function linkFileDirective(Upload, $timeout, scope, elem, attr, directiveName, resizeParams, isBackground) { function constructDataUrl(file) { var disallowObjectUrl = Upload.attrGetter('ngfNoObjectUrl', attr, scope); Upload.dataUrl(file, disallowObjectUrl)['finally'](function () { $timeout(function () { var src = (disallowObjectUrl ? file.$ngfDataUrl : file.$ngfBlobUrl) || file.$ngfDataUrl; if (isBackground) { elem.css('background-image', 'url(\'' + (src || '') + '\')'); } else { elem.attr('src', src); } if (src) { elem.removeClass('ng-hide'); } else { elem.addClass('ng-hide'); } }); }); } $timeout(function () { var unwatch = scope.$watch(attr[directiveName], function (file) { var size = resizeParams; if (directiveName === 'ngfThumbnail') { if (!size) { size = {width: elem[0].clientWidth, height: elem[0].clientHeight}; } if (size.width === 0 && window.getComputedStyle) { var style = getComputedStyle(elem[0]); size = { width: parseInt(style.width.slice(0, -2)), height: parseInt(style.height.slice(0, -2)) }; } } if (angular.isString(file)) { elem.removeClass('ng-hide'); if (isBackground) { return elem.css('background-image', 'url(\'' + file + '\')'); } else { return elem.attr('src', file); } } if (file && file.type && file.type.search(getTagType(elem[0])) === 0 && (!isBackground || file.type.indexOf('image') === 0)) { if (size && Upload.isResizeSupported()) { Upload.resize(file, size.width, size.height, size.quality).then( function (f) { constructDataUrl(f); }, function (e) { throw e; } ); } else { constructDataUrl(file); } } else { elem.addClass('ng-hide'); } }); scope.$on('$destroy', function () { unwatch(); }); }); } /** @namespace attr.ngfSrc */ /** @namespace attr.ngfNoObjectUrl */ ngFileUpload.directive('ngfSrc', ['Upload', '$timeout', function (Upload, $timeout) { return { restrict: 'AE', link: function (scope, elem, attr) { linkFileDirective(Upload, $timeout, scope, elem, attr, 'ngfSrc', Upload.attrGetter('ngfResize', attr, scope), false); } }; }]); /** @namespace attr.ngfBackground */ /** @namespace attr.ngfNoObjectUrl */ ngFileUpload.directive('ngfBackground', ['Upload', '$timeout', function (Upload, $timeout) { return { restrict: 'AE', link: function (scope, elem, attr) { linkFileDirective(Upload, $timeout, scope, elem, attr, 'ngfBackground', Upload.attrGetter('ngfResize', attr, scope), true); } }; }]); /** @namespace attr.ngfThumbnail */ /** @namespace attr.ngfAsBackground */ /** @namespace attr.ngfSize */ /** @namespace attr.ngfNoObjectUrl */ ngFileUpload.directive('ngfThumbnail', ['Upload', '$timeout', function (Upload, $timeout) { return { restrict: 'AE', link: function (scope, elem, attr) { var size = Upload.attrGetter('ngfSize', attr, scope); linkFileDirective(Upload, $timeout, scope, elem, attr, 'ngfThumbnail', size, Upload.attrGetter('ngfAsBackground', attr, scope)); } }; }]); ngFileUpload.config(['$compileProvider', function ($compileProvider) { if ($compileProvider.imgSrcSanitizationWhitelist) $compileProvider.imgSrcSanitizationWhitelist(/^\s*(https?|ftp|mailto|tel|local|file|data|blob):/); if ($compileProvider.aHrefSanitizationWhitelist) $compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|ftp|mailto|tel|local|file|data|blob):/); }]); ngFileUpload.filter('ngfDataUrl', ['UploadDataUrl', '$sce', function (UploadDataUrl, $sce) { return function (file, disallowObjectUrl, trustedUrl) { if (angular.isString(file)) { return $sce.trustAsResourceUrl(file); } var src = file && ((disallowObjectUrl ? file.$ngfDataUrl : file.$ngfBlobUrl) || file.$ngfDataUrl); if (file && !src) { if (!file.$ngfDataUrlFilterInProgress && angular.isObject(file)) { file.$ngfDataUrlFilterInProgress = true; UploadDataUrl.dataUrl(file, disallowObjectUrl); } return ''; } if (file) delete file.$ngfDataUrlFilterInProgress; return (file && src ? (trustedUrl ? $sce.trustAsResourceUrl(src) : src) : file) || ''; }; }]); })(); ngFileUpload.service('UploadValidate', ['UploadDataUrl', '$q', '$timeout', function (UploadDataUrl, $q, $timeout) { var upload = UploadDataUrl; function globStringToRegex(str) { var regexp = '', excludes = []; if (str.length > 2 && str[0] === '/' && str[str.length - 1] === '/') { regexp = str.substring(1, str.length - 1); } else { var split = str.split(','); if (split.length > 1) { for (var i = 0; i < split.length; i++) { var r = globStringToRegex(split[i]); if (r.regexp) { regexp += '(' + r.regexp + ')'; if (i < split.length - 1) { regexp += '|'; } } else { excludes = excludes.concat(r.excludes); } } } else { if (str.indexOf('!') === 0) { excludes.push('^((?!' + globStringToRegex(str.substring(1)).regexp + ').)*$'); } else { if (str.indexOf('.') === 0) { str = '*' + str; } regexp = '^' + str.replace(new RegExp('[.\\\\+*?\\[\\^\\]$(){}=!<>|:\\-]', 'g'), '\\$&') + '$'; regexp = regexp.replace(/\\\*/g, '.*').replace(/\\\?/g, '.'); } } } return {regexp: regexp, excludes: excludes}; } upload.validatePattern = function (file, val) { if (!val) { return true; } var pattern = globStringToRegex(val), valid = true; if (pattern.regexp && pattern.regexp.length) { var regexp = new RegExp(pattern.regexp, 'i'); valid = (file.type != null && regexp.test(file.type)) || (file.name != null && regexp.test(file.name)); } var len = pattern.excludes.length; while (len--) { var exclude = new RegExp(pattern.excludes[len], 'i'); valid = valid && (file.type == null || exclude.test(file.type)) && (file.name == null || exclude.test(file.name)); } return valid; }; upload.ratioToFloat = function (val) { var r = val.toString(), xIndex = r.search(/[x:]/i); if (xIndex > -1) { r = parseFloat(r.substring(0, xIndex)) / parseFloat(r.substring(xIndex + 1)); } else { r = parseFloat(r); } return r; }; upload.registerModelChangeValidator = function (ngModel, attr, scope) { if (ngModel) { ngModel.$formatters.push(function (files) { if (ngModel.$dirty) { if (files && !angular.isArray(files)) { files = [files]; } upload.validate(files, 0, ngModel, attr, scope).then(function () { upload.applyModelValidation(ngModel, files); }); } }); } }; function markModelAsDirty(ngModel, files) { if (files != null && !ngModel.$dirty) { if (ngModel.$setDirty) { ngModel.$setDirty(); } else { ngModel.$dirty = true; } } } upload.applyModelValidation = function (ngModel, files) { markModelAsDirty(ngModel, files); angular.forEach(ngModel.$ngfValidations, function (validation) { ngModel.$setValidity(validation.name, validation.valid); }); }; upload.getValidationAttr = function (attr, scope, name, validationName, file) { var dName = 'ngf' + name[0].toUpperCase() + name.substr(1); var val = upload.attrGetter(dName, attr, scope, {$file: file}); if (val == null) { val = upload.attrGetter('ngfValidate', attr, scope, {$file: file}); if (val) { var split = (validationName || name).split('.'); val = val[split[0]]; if (split.length > 1) { val = val && val[split[1]]; } } } return val; }; upload.validate = function (files, prevLength, ngModel, attr, scope) { ngModel = ngModel || {}; ngModel.$ngfValidations = ngModel.$ngfValidations || []; angular.forEach(ngModel.$ngfValidations, function (v) { v.valid = true; }); var attrGetter = function (name, params) { return upload.attrGetter(name, attr, scope, params); }; if (files == null || files.length === 0) { return upload.emptyPromise(ngModel); } files = files.length === undefined ? [files] : files.slice(0); function validateSync(name, validationName, fn) { if (files) { var i = files.length, valid = null; while (i--) { var file = files[i]; if (file) { var val = upload.getValidationAttr(attr, scope, name, validationName, file); if (val != null) { if (!fn(file, val, i)) { file.$error = name; (file.$errorMessages = (file.$errorMessages || {}))[name] = true; file.$errorParam = val; files.splice(i, 1); valid = false; } } } } if (valid !== null) { ngModel.$ngfValidations.push({name: name, valid: valid}); } } } validateSync('maxFiles', null, function (file, val, i) { return prevLength + i < val; }); validateSync('pattern', null, upload.validatePattern); validateSync('minSize', 'size.min', function (file, val) { return file.size + 0.1 >= upload.translateScalars(val); }); validateSync('maxSize', 'size.max', function (file, val) { return file.size - 0.1 <= upload.translateScalars(val); }); var totalSize = 0; validateSync('maxTotalSize', null, function (file, val) { totalSize += file.size; if (totalSize > upload.translateScalars(val)) { files.splice(0, files.length); return false; } return true; }); validateSync('validateFn', null, function (file, r) { return r === true || r === null || r === ''; }); if (!files.length) { return upload.emptyPromise(ngModel, ngModel.$ngfValidations); } function validateAsync(name, validationName, type, asyncFn, fn) { function resolveResult(defer, file, val) { if (val != null) { asyncFn(file, val).then(function (d) { if (!fn(d, val)) { file.$error = name; (file.$errorMessages = (file.$errorMessages || {}))[name] = true; file.$errorParam = val; defer.reject(); } else { defer.resolve(); } }, function () { if (attrGetter('ngfValidateForce', {$file: file})) { file.$error = name; (file.$errorMessages = (file.$errorMessages || {}))[name] = true; file.$errorParam = val; defer.reject(); } else { defer.resolve(); } }); } else { defer.resolve(); } } var promises = [upload.emptyPromise()]; if (files) { files = files.length === undefined ? [files] : files; angular.forEach(files, function (file) { var defer = $q.defer(); promises.push(defer.promise); if (type && (file.type == null || file.type.search(type) !== 0)) { defer.resolve(); return; } if (name === 'dimensions' && upload.attrGetter('ngfDimensions', attr) != null) { upload.imageDimensions(file).then(function (d) { resolveResult(defer, file, attrGetter('ngfDimensions', {$file: file, $width: d.width, $height: d.height})); }, function () { defer.reject(); }); } else if (name === 'duration' && upload.attrGetter('ngfDuration', attr) != null) { upload.mediaDuration(file).then(function (d) { resolveResult(defer, file, attrGetter('ngfDuration', {$file: file, $duration: d})); }, function () { defer.reject(); }); } else { resolveResult(defer, file, upload.getValidationAttr(attr, scope, name, validationName, file)); } }); return $q.all(promises).then(function () { ngModel.$ngfValidations.push({name: name, valid: true}); }, function () { ngModel.$ngfValidations.push({name: name, valid: false}); }); } } var deffer = $q.defer(); var promises = []; promises.push(upload.happyPromise(validateAsync('maxHeight', 'height.max', /image/, this.imageDimensions, function (d, val) { return d.height <= val; }))); promises.push(upload.happyPromise(validateAsync('minHeight', 'height.min', /image/, this.imageDimensions, function (d, val) { return d.height >= val; }))); promises.push(upload.happyPromise(validateAsync('maxWidth', 'width.max', /image/, this.imageDimensions, function (d, val) { return d.width <= val; }))); promises.push(upload.happyPromise(validateAsync('minWidth', 'width.min', /image/, this.imageDimensions, function (d, val) { return d.width >= val; }))); promises.push(upload.happyPromise(validateAsync('dimensions', null, /image/, function (file, val) { return upload.emptyPromise(val); }, function (r) { return r; }))); promises.push(upload.happyPromise(validateAsync('ratio', null, /image/, this.imageDimensions, function (d, val) { var split = val.toString().split(','), valid = false; for (var i = 0; i < split.length; i++) { if (Math.abs((d.width / d.height) - upload.ratioToFloat(split[i])) < 0.0001) { valid = true; } } return valid; }))); promises.push(upload.happyPromise(validateAsync('maxRatio', 'ratio.max', /image/, this.imageDimensions, function (d, val) { return (d.width / d.height) - upload.ratioToFloat(val) < 0.0001; }))); promises.push(upload.happyPromise(validateAsync('minRatio', 'ratio.min', /image/, this.imageDimensions, function (d, val) { return (d.width / d.height) - upload.ratioToFloat(val) > -0.0001; }))); promises.push(upload.happyPromise(validateAsync('maxDuration', 'duration.max', /audio|video/, this.mediaDuration, function (d, val) { return d <= upload.translateScalars(val); }))); promises.push(upload.happyPromise(validateAsync('minDuration', 'duration.min', /audio|video/, this.mediaDuration, function (d, val) { return d >= upload.translateScalars(val); }))); promises.push(upload.happyPromise(validateAsync('duration', null, /audio|video/, function (file, val) { return upload.emptyPromise(val); }, function (r) { return r; }))); promises.push(upload.happyPromise(validateAsync('validateAsyncFn', null, null, function (file, val) { return val; }, function (r) { return r === true || r === null || r === ''; }))); return $q.all(promises).then(function () { deffer.resolve(ngModel, ngModel.$ngfValidations); }); }; upload.imageDimensions = function (file) { if (file.$ngfWidth && file.$ngfHeight) { var d = $q.defer(); $timeout(function () { d.resolve({width: file.$ngfWidth, height: file.$ngfHeight}); }); return d.promise; } if (file.$ngfDimensionPromise) return file.$ngfDimensionPromise; var deferred = $q.defer(); $timeout(function () { if (file.type.indexOf('image') !== 0) { deferred.reject('not image'); return; } upload.dataUrl(file).then(function (dataUrl) { var img = angular.element('').attr('src', dataUrl) .css('visibility', 'hidden').css('position', 'fixed') .css('max-width', 'none !important').css('max-height', 'none !important'); function success() { var width = img[0].clientWidth; var height = img[0].clientHeight; img.remove(); file.$ngfWidth = width; file.$ngfHeight = height; deferred.resolve({width: width, height: height}); } function error() { img.remove(); deferred.reject('load error'); } img.on('load', success); img.on('error', error); var count = 0; function checkLoadError() { $timeout(function () { if (img[0].parentNode) { if (img[0].clientWidth) { success(); } else if (count > 10) { error(); } else { checkLoadError(); } } }, 1000); } checkLoadError(); angular.element(document.getElementsByTagName('body')[0]).append(img); }, function () { deferred.reject('load error'); }); }); file.$ngfDimensionPromise = deferred.promise; file.$ngfDimensionPromise['finally'](function () { delete file.$ngfDimensionPromise; }); return file.$ngfDimensionPromise; }; upload.mediaDuration = function (file) { if (file.$ngfDuration) { var d = $q.defer(); $timeout(function () { d.resolve(file.$ngfDuration); }); return d.promise; } if (file.$ngfDurationPromise) return file.$ngfDurationPromise; var deferred = $q.defer(); $timeout(function () { if (file.type.indexOf('audio') !== 0 && file.type.indexOf('video') !== 0) { deferred.reject('not media'); return; } upload.dataUrl(file).then(function (dataUrl) { var el = angular.element(file.type.indexOf('audio') === 0 ? '