ng-file-upload-haikulearning
Version:
An AngularJS directive for file upload using HTML5 with FileAPI polyfill for unsupported browsers
340 lines (317 loc) • 12 kB
JavaScript
(function () {
ngFileUpload.directive('ngfDrop', ['$parse', '$timeout', '$location', 'Upload', '$http', '$q',
function ($parse, $timeout, $location, Upload, $http, $q) {
return {
restrict: 'AEC',
require: '?ngModel',
link: function (scope, elem, attr, ngModel) {
linkDrop(scope, elem, attr, ngModel, $parse, $timeout, $location, Upload, $http, $q);
}
};
}]);
ngFileUpload.directive('ngfNoFileDrop', function () {
return function (scope, elem) {
if (dropAvailable()) elem.css('display', 'none');
};
});
ngFileUpload.directive('ngfDropAvailable', ['$parse', '$timeout', 'Upload', function ($parse, $timeout, Upload) {
return function (scope, elem, attr) {
if (dropAvailable()) {
var model = $parse(Upload.attrGetter('ngfDropAvailable', attr));
$timeout(function () {
model(scope);
if (model.assign) {
model.assign(scope, true);
}
});
}
};
}]);
function linkDrop(scope, elem, attr, ngModel, $parse, $timeout, $location, upload, $http, $q) {
var available = dropAvailable();
var attrGetter = function (name, scope, params) {
return upload.attrGetter(name, attr, scope, params);
};
if (attrGetter('dropAvailable')) {
$timeout(function () {
if (scope[attrGetter('dropAvailable')]) {
scope[attrGetter('dropAvailable')].value = available;
} else {
scope[attrGetter('dropAvailable')] = available;
}
});
}
if (!available) {
if (attrGetter('ngfHideOnDropNotAvailable', scope) === true) {
elem.css('display', 'none');
}
return;
}
function isDisabled() {
return elem.attr('disabled') || attrGetter('ngfDropDisabled', scope);
}
if (attrGetter('ngfSelect') == null) {
upload.registerModelChangeValidator(ngModel, attr, scope);
}
var leaveTimeout = null;
var stopPropagation = $parse(attrGetter('ngfStopPropagation'));
var dragOverDelay = 1;
var actualDragOverClass;
elem[0].addEventListener('dragover', function (evt) {
if (isDisabled() || !upload.shouldUpdateOn('drop', attr, 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 (!actualDragOverClass) {
actualDragOverClass = 'C';
calculateDragOverClass(scope, attr, evt, function (clazz) {
actualDragOverClass = clazz;
elem.addClass(actualDragOverClass);
attrGetter('ngfDrag', scope, {$isDragging: true, $class: actualDragOverClass, $event: evt});
});
}
}, false);
elem[0].addEventListener('dragenter', function (evt) {
if (isDisabled() || !upload.shouldUpdateOn('drop', attr, scope)) return;
evt.preventDefault();
if (stopPropagation(scope)) evt.stopPropagation();
}, false);
elem[0].addEventListener('dragleave', function (evt) {
if (isDisabled() || !upload.shouldUpdateOn('drop', attr, scope)) return;
evt.preventDefault();
if (stopPropagation(scope)) evt.stopPropagation();
leaveTimeout = $timeout(function () {
if (actualDragOverClass) elem.removeClass(actualDragOverClass);
actualDragOverClass = null;
attrGetter('ngfDrag', scope, {$isDragging: false, $event: evt});
}, dragOverDelay || 100);
}, false);
elem[0].addEventListener('drop', function (evt) {
if (isDisabled() || !upload.shouldUpdateOn('drop', attr, scope)) return;
evt.preventDefault();
if (stopPropagation(scope)) evt.stopPropagation();
if (actualDragOverClass) elem.removeClass(actualDragOverClass);
actualDragOverClass = null;
var items = evt.dataTransfer.items;
var html;
try {
html = evt.dataTransfer && evt.dataTransfer.getData && evt.dataTransfer.getData('text/html');
} catch (e) {/* Fix IE11 that throw error calling getData */
}
extractFiles(items, evt.dataTransfer.files, attrGetter('ngfAllowDir', scope) !== false,
attrGetter('multiple') || attrGetter('ngfMultiple', scope)).then(function (files) {
if (files.length) {
updateModel(files, evt);
} else {
extractFilesFromHtml('dropUrl', html).then(function (files) {
updateModel(files, evt);
});
}
});
}, false);
elem[0].addEventListener('paste', function (evt) {
if (navigator.userAgent.toLowerCase().indexOf('firefox') > -1 &&
attrGetter('ngfEnableFirefoxPaste', scope)) {
evt.preventDefault();
}
if (isDisabled() || !upload.shouldUpdateOn('paste', attr, scope)) return;
var files = [];
var clipboard = evt.clipboardData || evt.originalEvent.clipboardData;
if (clipboard && clipboard.items) {
for (var k = 0; k < clipboard.items.length; k++) {
if (clipboard.items[k].type.indexOf('image') !== -1) {
files.push(clipboard.items[k].getAsFile());
}
}
}
if (files.length) {
updateModel(files, evt);
} else {
extractFilesFromHtml('pasteUrl', clipboard).then(function (files) {
updateModel(files, evt);
});
}
}, false);
if (navigator.userAgent.toLowerCase().indexOf('firefox') > -1 &&
attrGetter('ngfEnableFirefoxPaste', scope)) {
elem.attr('contenteditable', true);
elem.on('keypress', function (e) {
if (!e.metaKey && !e.ctrlKey) {
e.preventDefault();
}
});
}
function updateModel(files, evt) {
upload.updateModel(ngModel, attr, scope, attrGetter('ngfChange') || attrGetter('ngfDrop'), files, evt);
}
function extractFilesFromHtml(updateOn, html) {
if (!upload.shouldUpdateOn(updateOn, attr, scope) || !html) return upload.rejectPromise([]);
var urls = [];
html.replace(/<(img src|img [^>]* src) *=\"([^\"]*)\"/gi, function (m, n, src) {
urls.push(src);
});
var promises = [], files = [];
if (urls.length) {
angular.forEach(urls, function (url) {
promises.push(upload.urlToBlob(url).then(function (blob) {
files.push(blob);
}));
});
var defer = $q.defer();
$q.all(promises).then(function () {
defer.resolve(files);
}, function (e) {
defer.reject(e);
});
return defer.promise;
}
return upload.emptyPromise();
}
function calculateDragOverClass(scope, attr, evt, callback) {
var obj = attrGetter('ngfDragOverClass', scope, {$event: evt}), dClass = 'dragover';
if (angular.isString(obj)) {
dClass = obj;
} else if (obj) {
if (obj.delay) dragOverDelay = obj.delay;
if (obj.accept || obj.reject) {
var items = evt.dataTransfer.items;
if (items == null || !items.length) {
dClass = obj.accept;
} else {
var pattern = obj.pattern || attrGetter('ngfPattern', scope, {$event: evt});
var len = items.length;
while (len--) {
if (!upload.validatePattern(items[len], pattern)) {
dClass = obj.reject;
break;
} else {
dClass = obj.accept;
}
}
}
}
}
callback(dClass);
}
function extractFiles(items, fileList, allowDir, multiple) {
var maxFiles = upload.getValidationAttr(attr, scope, 'maxFiles') || Number.MAX_VALUE;
var maxTotalSize = upload.getValidationAttr(attr, scope, 'maxTotalSize') || Number.MAX_VALUE;
var includeDir = attrGetter('ngfIncludeDir', scope);
var files = [], totalSize = 0;
function traverseFileTree(entry, path) {
var defer = $q.defer();
if (entry != null) {
if (entry.isDirectory) {
var promises = [upload.emptyPromise()];
if (includeDir) {
var file = {type: 'directory'};
file.name = file.path = (path || '') + entry.name;
files.push(file);
}
var dirReader = entry.createReader();
var entries = [];
var readEntries = function () {
dirReader.readEntries(function (results) {
try {
if (!results.length) {
angular.forEach(entries.slice(0), function (e) {
if (files.length <= maxFiles && totalSize <= maxTotalSize) {
promises.push(traverseFileTree(e, (path ? path : '') + entry.name + '/'));
}
});
$q.all(promises).then(function () {
defer.resolve();
}, function (e) {
defer.reject(e);
});
} else {
entries = entries.concat(Array.prototype.slice.call(results || [], 0));
readEntries();
}
} catch (e) {
defer.reject(e);
}
}, function (e) {
defer.reject(e);
});
};
readEntries();
} else {
entry.file(function (file) {
try {
file.path = (path ? path : '') + file.name;
if (includeDir) {
file = upload.rename(file, file.path);
}
files.push(file);
totalSize += file.size;
defer.resolve();
} catch (e) {
defer.reject(e);
}
}, function (e) {
defer.reject(e);
});
}
}
return defer.promise;
}
var promises = [upload.emptyPromise()];
if (items && items.length > 0 && $location.protocol() !== 'file') {
for (var 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) {
promises.push(traverseFileTree(entry));
}
} else {
var f = items[i].getAsFile();
if (f != null) {
files.push(f);
totalSize += f.size;
}
}
if (files.length > maxFiles || totalSize > maxTotalSize ||
(!multiple && files.length > 0)) break;
}
} else {
if (fileList != null) {
for (var j = 0; j < fileList.length; j++) {
var file = fileList.item(j);
if (file.type || file.size > 0) {
files.push(file);
totalSize += file.size;
}
if (files.length > maxFiles || totalSize > maxTotalSize ||
(!multiple && files.length > 0)) break;
}
}
}
var defer = $q.defer();
$q.all(promises).then(function () {
if (!multiple && !includeDir && files.length) {
var i = 0;
while (files[i] && files[i].type === 'directory') i++;
defer.resolve([files[i]]);
} else {
defer.resolve(files);
}
}, function (e) {
defer.reject(e);
});
return defer.promise;
}
}
function dropAvailable() {
var div = document.createElement('div');
return ('draggable' in div) && ('ondrop' in div) && !/Edge\/12./i.test(navigator.userAgent);
}
})();