ng-file-upload-haikulearning
Version:
An AngularJS directive for file upload using HTML5 with FileAPI polyfill for unsupported browsers
234 lines (199 loc) • 7.55 kB
JavaScript
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('<input type="file">');
bindAttrToFileInput(fileElem);
var label = angular.element('<label>upload</label>');
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);
}
};
}]);