/**! * AngularJS file upload/drop directive with http post and progress * @author Danial * @version 1.5.0 */ (function() { var angularFileUpload = angular.module('angularFileUpload', []); 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; } console.log($http.defaults.transformRequest[0]) console.log($http.defaults.transformRequest[0](data, headersGetter)) return $http.defaults.transformRequest[0](data, headersGetter); }; var deferred = $q.defer(); if (window.XMLHttpRequest.__isShim) { config.headers['__setXHR_'] = function() { return function(xhr) { if (!xhr) return; config.__XHR = xhr; config.xhrFn && config.xhrFn(xhr); xhr.upload.addEventListener('progress', function(e) { deferred.notify(e); }, false); //fix for firefox not firing upload progress end, also IE8-9 xhr.upload.addEventListener('load', function(e) { if (e.lengthComputable) { deferred.notify(e); } }, false); }; }; } $http(config).then(function(r){deferred.resolve(r)}, function(e){deferred.reject(e)}, function(n){deferred.notify(n)}); var promise = deferred.promise; 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.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 || $http.defaults.transformRequest; var formData = new FormData(); var origTransformRequest = config.transformRequest; var origData = config.data; config.transformRequest = function(formData, headerGetter) { if (origData) { if (config.formDataAppender) { for (var key in origData) { var val = origData[key]; config.formDataAppender(formData, key, val); } } else { for (var key in origData) { var val = origData[key]; if (typeof origTransformRequest == 'function') { val = origTransformRequest(val, headerGetter); } else { for (var i = 0; i < origTransformRequest.length; i++) { var transformFn = origTransformRequest[i]; if (typeof transformFn == 'function') { val = transformFn(val, headerGetter); } } } formData.append(key, 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 (var 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; }; config.data = formData; return sendHttp(config); }; this.http = function(config) { return sendHttp(config); } }]); angularFileUpload.directive('ngFileSelect', [ '$parse', '$timeout', function($parse, $timeout) { return function(scope, elem, attr) { var fn = $parse(attr['ngFileSelect']); elem.bind('change', function(evt) { var files = [], fileList, i; fileList = evt.target.files; if (fileList != null) { for (i = 0; i < fileList.length; i++) { files.push(fileList.item(i)); } } $timeout(function() { fn(scope, { $files : files, $event : evt }); }); }); // removed this since it was confusing if the user click on browse and then cancel #181 // elem.bind('click', function(){ // this.value = null; // }); // touch screens if (('ontouchstart' in window) || (navigator.maxTouchPoints > 0) || (navigator.msMaxTouchPoints > 0)) { elem.bind('touchend', function(e) { e.preventDefault(); e.target.click(); }); } }; } ]); angularFileUpload.directive('ngFileDropAvailable', [ '$parse', '$timeout', function($parse, $timeout) { return function(scope, elem, attr) { if ('draggable' in document.createElement('span')) { var fn = $parse(attr['ngFileDropAvailable']); $timeout(function() { fn(scope); }); } }; } ]); angularFileUpload.directive('ngFileDrop', [ '$parse', '$timeout', '$location', function($parse, $timeout, $location) { return function(scope, elem, attr) { if ('draggable' in document.createElement('span')) { var leaveTimeout = null; elem[0].addEventListener("dragover", function(evt) { evt.stopPropagation(); evt.preventDefault(); elem.addClass(elem[0].__drag_over_class_); $timeout.cancel(leaveTimeout); if (!elem[0].__drag_entered_) { elem[0].__drag_entered_ = true; var dragOverClassFn = $parse(attr['ngFileDragOverClass']); if (dragOverClassFn instanceof Function) { var dragOverClass = dragOverClassFn(scope, { $event : evt }); elem[0].__drag_over_class_ = dragOverClass; elem.addClass(elem[0].__drag_over_class_); } else { elem[0].__drag_over_class_ = attr['ngFileDragOverClass'] || "dragover"; elem.addClass(elem[0].__drag_over_class_); } } }, false); elem[0].addEventListener("dragenter", function(evt) { evt.stopPropagation(); evt.preventDefault(); }, false); elem[0].addEventListener("dragleave", function(evt) { leaveTimeout = $timeout(function() { elem[0].__drag_entered_ = false; elem.removeClass(elem[0].__drag_over_class_); }); }, false); var fn = $parse(attr['ngFileDrop']); elem[0].addEventListener("drop", function(evt) { evt.stopPropagation(); evt.preventDefault(); elem[0].__drag_entered_ = false; elem.removeClass(elem[0].__drag_over_class_); extractFiles(evt, function(files) { fn(scope, { $files : files, $event : evt }); }); }, false); function isASCII(str) { return /^[\000-\177]*$/.test(str); } function extractFiles(evt, callback) { var files = [], items = evt.dataTransfer.items; if (items && items.length > 0 && items[0].webkitGetAsEntry && $location.protocol() != 'file') { for (var i = 0; i < items.length; i++) { var entry = items[i].webkitGetAsEntry(); if (entry != null) { //fix for chrome bug https://code.google.com/p/chromium/issues/detail?id=149735 if (isASCII(entry.name)) { traverseFileTree(files, entry); } else { files.push(items[i].getAsFile()); } } } } else { var fileList = evt.dataTransfer.files; if (fileList != null) { for (var i = 0; i < fileList.length; i++) { files.push(fileList.item(i)); } } } (function waitForProcess(delay) { $timeout(function() { if (!processing) { callback(files); } else { waitForProcess(10); } }, delay || 0) })(); } var processing = 0; function traverseFileTree(files, entry) { if (entry != null) { if (entry.isDirectory) { var dirReader = entry.createReader(); processing++; dirReader.readEntries(function(entries) { for (var i = 0; i < entries.length; i++) { traverseFileTree(files, entries[i]); } processing--; }); } else { processing++; entry.file(function(file) { processing--; files.push(file); }); } } } } }; } ]); })();