ng-file-upload-haikulearning
Version:
An AngularJS directive for file upload using HTML5 with FileAPI polyfill for unsupported browsers
451 lines (410 loc) • 14.7 kB
JavaScript
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 += regexp ? '|' : '';
regexp += '(' + r.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('<img>').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 ? '<audio>' : '<video>')
.attr('src', dataUrl).css('visibility', 'none').css('position', 'fixed');
function success() {
var duration = el[0].duration;
file.$ngfDuration = duration;
el.remove();
deferred.resolve(duration);
}
function error() {
el.remove();
deferred.reject('load error');
}
el.on('loadedmetadata', success);
el.on('error', error);
var count = 0;
function checkLoadError() {
$timeout(function () {
if (el[0].parentNode) {
if (el[0].duration) {
success();
} else if (count > 10) {
error();
} else {
checkLoadError();
}
}
}, 1000);
}
checkLoadError();
angular.element(document.body).append(el);
}, function () {
deferred.reject('load error');
});
});
file.$ngfDurationPromise = deferred.promise;
file.$ngfDurationPromise['finally'](function () {
delete file.$ngfDurationPromise;
});
return file.$ngfDurationPromise;
};
return upload;
}
]);