501 lines
16 KiB
JavaScript
501 lines
16 KiB
JavaScript
/*
|
|
* Class: fileUploader
|
|
* Use: Upload multiple files using jquery
|
|
* Author: Matt Brailsford
|
|
* Version: 1.0
|
|
*/
|
|
|
|
(function ($, window, document, undefined) {
|
|
|
|
$.event.props.push('dataTransfer');
|
|
|
|
var defaultOptions = {
|
|
autoUpload: false,
|
|
limit: false,
|
|
allowedExtension: 'jpg|jpeg|gif|png|pdf',
|
|
dropTarget: null,
|
|
|
|
//Callbacks
|
|
onValidationError: null,
|
|
onAdd: function () { },
|
|
onRemove: function () { },
|
|
onUpload: function () { },
|
|
onUploadAll: function () { },
|
|
onProgress: function () { },
|
|
onDone: function () { },
|
|
onDoneAll: function () { }
|
|
};
|
|
|
|
var isHtml5 = (window.FormData) ? true : false;
|
|
|
|
function FileUploader(el, options) {
|
|
var self = this;
|
|
var $el = $(el);
|
|
|
|
// Setup instance
|
|
self.input = el;
|
|
self.form = $el.parents("form");
|
|
self.uploaderId = $.fileUploader.count + 1;
|
|
|
|
self.opts = $.extend({}, defaultOptions, options);
|
|
|
|
// Initialize
|
|
self._init();
|
|
|
|
// Return configures component
|
|
return self;
|
|
}
|
|
|
|
FileUploader.prototype = {
|
|
|
|
// Private methods
|
|
_init: function () {
|
|
var self = this;
|
|
|
|
// Init vars
|
|
self.wrapperId = 'fu-fileUploader-' + self.uploaderId;
|
|
self.wrapper = ' \
|
|
<div id="' + self.wrapperId + '" class="fu-fileUploader"> \
|
|
<div class="fu-formContainer"></div> \
|
|
<div class="fu-itemContainer"></div> \
|
|
</div>';
|
|
|
|
self.wrapperSelector = '#' + self.wrapperId;
|
|
self.formContainerSelector = self.wrapperSelector + " .fu-formContainer";
|
|
self.itemContainerSelector = self.wrapperSelector + " .fu-itemContainer";
|
|
|
|
self.itemCount = 0;
|
|
|
|
self.progressTimeout = null;
|
|
self.inProgressJqXhr = null;
|
|
self.inProgressItemId = 0;
|
|
|
|
//prepend wrapper markup
|
|
self.form.before(self.wrapper);
|
|
|
|
//clear all form data
|
|
$(":file", self.form).each(function () {
|
|
$(this).val('');
|
|
});
|
|
|
|
// Hide the form (we'll just use it as a template)
|
|
self.form.hide();
|
|
|
|
// Create the actual form users will interact with
|
|
self._createNewForm();
|
|
|
|
// Hookup drag and drop support
|
|
$(self.opts.dropTarget !== null ? self.opts.dropTarget : self.wrapperSelector).bind('dragenter dragover', false).bind('drop', function (e) {
|
|
e.stopPropagation();
|
|
e.preventDefault();
|
|
self._addFileHtml5(e.dataTransfer);
|
|
});
|
|
},
|
|
|
|
_getFileName: function (filePath) {
|
|
var fileName = filePath;
|
|
if (fileName.indexOf('/') > -1) {
|
|
fileName = fileName.substring(fileName.lastIndexOf('/') + 1);
|
|
} else if (fileName.indexOf('\\') > -1) {
|
|
fileName = fileName.substring(fileName.lastIndexOf('\\') + 1);
|
|
}
|
|
return fileName;
|
|
},
|
|
|
|
_validateFileName: function (fileName) {
|
|
var self = this;
|
|
var extensions = new RegExp(self.opts.allowedExtension + '$', 'i');
|
|
if (extensions.test(fileName)) {
|
|
return fileName;
|
|
} else {
|
|
return -1;
|
|
}
|
|
},
|
|
|
|
_getFileSize: function (file) {
|
|
var fileSize = 0;
|
|
if (file.size > 1024 * 1024) {
|
|
fileSize = (Math.round(file.size * 100 / (1024 * 1024)) / 100).toString() + 'MB';
|
|
} else {
|
|
fileSize = (Math.round(file.size * 100 / 1024) / 100).toString() + 'KB';
|
|
}
|
|
return fileSize;
|
|
},
|
|
|
|
_createNewForm: function () {
|
|
var self = this;
|
|
var id = self.itemCount + 1;
|
|
|
|
var itemId = 'fu-item-' + self.uploaderId + '-' + id;
|
|
var iframeId = 'fu-frame-' + self.uploaderId + '-' + id;
|
|
var formId = 'fu-form-' + self.uploaderId + '-' + id;
|
|
|
|
// Create item container
|
|
var item = $('<div id="' + itemId + '" class="fu-item"></div>');
|
|
|
|
// Create iFrame for iFrame based uploads
|
|
$('<iframe name="' + iframeId + '"></iframe>').attr({
|
|
id: iframeId,
|
|
src: 'about:blank',
|
|
style: 'display:none'
|
|
}).prependTo(item);
|
|
|
|
// Clone the form
|
|
var newForm = self.form.clone().attr({
|
|
id: formId,
|
|
target: iframeId
|
|
}).prependTo(item).show();
|
|
|
|
// Remove any id's / css classes from file inputs
|
|
$(":file", newForm).each(function (idx, itm) {
|
|
$(itm).removeAttr("id").removeAttr("class");
|
|
});
|
|
|
|
// Hide everything in the form, other than file inputs
|
|
newForm.children().each(function () {
|
|
var isFile = $(this).is(':file');
|
|
if (!isFile && $(this).find(':file').length === 0) {
|
|
$(this).hide();
|
|
}
|
|
});
|
|
|
|
// Append everything to the form container
|
|
item.prependTo(self.formContainerSelector);
|
|
|
|
// Attatch event listener to file input
|
|
$(":file", newForm).change(function () {
|
|
if (isHtml5) {
|
|
self._addFileHtml5(this);
|
|
} else {
|
|
self._addFile($(this));
|
|
}
|
|
});
|
|
},
|
|
|
|
_addFileHtml5: function (input) {
|
|
var self = this;
|
|
$.each(input.files, function (index, file) {
|
|
self._addFile(file);
|
|
});
|
|
self._afterAddFile();
|
|
},
|
|
|
|
_addFile: function (input) {
|
|
var self = this;
|
|
var id = self.itemCount;
|
|
var $item = $('#fu-item-' + self.uploaderId + '-' + id);
|
|
|
|
// Validate file
|
|
var filename = (isHtml5) ? input.name : self._getFileName($(input).val());
|
|
if (self._validateFileName(filename) == -1) {
|
|
if ($.isFunction(self.opts.onValidationError)) {
|
|
self.opts.onValidationError({ input: input, type: 'format' });
|
|
} else {
|
|
alert('Invalid file!');
|
|
}
|
|
$item.find("form :file").val('');
|
|
return false;
|
|
}
|
|
|
|
// Check to see if we have reached the limit
|
|
if (self.opts.limit !== false &&
|
|
self.opts.limit >= $(self.itemContainerSelector + " .fu-item").length) {
|
|
if ($.isFunction(self.opts.onValidationError)) {
|
|
self.opts.onValidationError({ input: input, type: 'limit' });
|
|
} else {
|
|
alert('Maximum limit reached!');
|
|
}
|
|
$item.find("form :file").val('');
|
|
return false;
|
|
}
|
|
|
|
// Create context object
|
|
var data = {
|
|
uploaderId: self.uploaderId,
|
|
itemId: id,
|
|
name: filename,
|
|
size: (isHtml5) ? self._getFileSize(input) : "Unkown",
|
|
status: 'queued',
|
|
progress: 0,
|
|
input: input,
|
|
context: null // Somewhere for users to store cutom data
|
|
};
|
|
|
|
//Store data in item
|
|
$item.data('data', data);
|
|
|
|
// Move item to queue, and hide it
|
|
$item.appendTo(self.itemContainerSelector).hide();
|
|
|
|
//Create a new form
|
|
self._createNewForm();
|
|
|
|
//Callback on file queued
|
|
self.opts.onAdd(data);
|
|
|
|
if (!isHtml5) {
|
|
self._afterAddFile();
|
|
}
|
|
},
|
|
|
|
_afterAddFile: function () {
|
|
var self = this;
|
|
if (self.opts.autoUpload) {
|
|
self._processNextItemInQueue(true);
|
|
}
|
|
},
|
|
|
|
_processNextItemInQueue: function (autoProceed) {
|
|
var self = this;
|
|
var totalItems = $(self.itemContainerSelector + " .fu-item");
|
|
if (totalItems.length > 0) {
|
|
var $pendingUpload = $(totalItems.get(0));
|
|
var data = $pendingUpload.data('data');
|
|
|
|
//before upload
|
|
var response = self.opts.onUpload(data);
|
|
if (response === false) {
|
|
return false; //TODO: Raise onDone event?
|
|
}
|
|
|
|
self.inProgressItemId = data.itemId;
|
|
|
|
data.status = "inprogress";
|
|
data.progress = 0;
|
|
|
|
if (isHtml5) {
|
|
//Upload Using Html5 api
|
|
self._uploadHtml5($pendingUpload, autoProceed);
|
|
} else {
|
|
//upload using iframe
|
|
self._uploadIFrame($pendingUpload, autoProceed);
|
|
}
|
|
}
|
|
},
|
|
|
|
_uploadHtml5: function ($item, autoProceed) {
|
|
var self = this;
|
|
var $form = $item.find("form");
|
|
var data = $item.data('data');
|
|
var file = data.input;
|
|
if (file) {
|
|
var fd = new FormData();
|
|
fd.append($(":file", $form).first().attr('name'), file);
|
|
|
|
//get other form input and append to formData
|
|
$form.find(':input').each(function () {
|
|
if (!$(this).is(':file')) {
|
|
fd.append($(this).attr('name'), $(this).val());
|
|
}
|
|
});
|
|
|
|
//Upload using jQuery AJAX
|
|
self.inProgressJqXhr = $.ajax({
|
|
url: $form.attr('action'),
|
|
data: fd,
|
|
cache: false,
|
|
contentType: false,
|
|
processData: false,
|
|
type: 'POST',
|
|
xhr: function () {
|
|
var req = $.ajaxSettings.xhr();
|
|
if (req) {
|
|
req.upload.addEventListener('progress', function (ev) {
|
|
data.progress = Math.round(ev.loaded * 100 / ev.total);
|
|
self.opts.onProgress(data);
|
|
}, false);
|
|
}
|
|
return req;
|
|
}
|
|
}).success(function (d) {
|
|
data.status = 'success';
|
|
self.opts.onDone(data);
|
|
}).error(function (jqXhr, textStatus, errorThrown) {
|
|
data.status = 'error';
|
|
data.message = errorThrown;
|
|
self.opts.onDone(data);
|
|
}).complete(function (jqXhr, textStatus) {
|
|
self._afterUpload($item, autoProceed);
|
|
});
|
|
|
|
}
|
|
},
|
|
|
|
_uploadIFrame: function ($item, autoProceed) {
|
|
var self = this;
|
|
var $form = $item.find("form");
|
|
var $iframe = $item.find("iframe");
|
|
var data = $item.data('data');
|
|
|
|
self._startDummyProgress(data);
|
|
|
|
$form.submit();
|
|
|
|
$iframe.load(function () {
|
|
self._stopDummyProgress(data, true);
|
|
|
|
// Read content returned
|
|
var rawResponse;
|
|
|
|
if (this.contentDocument) {
|
|
rawResponse = this.contentDocument.body.innerHTML;
|
|
} else {
|
|
rawResponse = this.contentWindow.document.body.innerHTML;
|
|
}
|
|
|
|
var response = $.parseJSON(rawResponse);
|
|
|
|
if (response.success) {
|
|
data.status = 'success';
|
|
} else {
|
|
data.status = 'error';
|
|
data.message = 'An error occured whilst uploading.';
|
|
}
|
|
|
|
self.opts.onDone(data);
|
|
|
|
self._afterUpload($item, autoProceed);
|
|
});
|
|
},
|
|
|
|
_startDummyProgress: function (data, count) {
|
|
var self = this;
|
|
|
|
if (count === undefined) {
|
|
count = 0;
|
|
}
|
|
|
|
if ($.fileUploader.percentageInterval[count]) {
|
|
data.progress = $.fileUploader.percentageInterval[count] + Math.floor(Math.random() * 5 + 1);
|
|
self.opts.onProgress(data);
|
|
}
|
|
|
|
if ($.fileUploader.timeInterval[count]) {
|
|
self.progressTimeout = setTimeout(function () {
|
|
self._startDummyProgress(data, count + 1);
|
|
}, $.fileUploader.timeInterval[count] * 1000);
|
|
}
|
|
},
|
|
|
|
_stopDummyProgress: function (data, success) {
|
|
var self = this;
|
|
|
|
clearTimeout(self.progressTimeout);
|
|
|
|
if (success) {
|
|
// Force a 100% onProgress event
|
|
data.progress = 100;
|
|
self.opts.onProgress(data);
|
|
}
|
|
},
|
|
|
|
_afterUpload: function ($item, autoProceed) {
|
|
var self = this;
|
|
var data = $item.data('data');
|
|
|
|
if (data.itemId == self.inProgressItemId) {
|
|
self.inProgressItemId = 0;
|
|
self.inProgressJqXhr = null;
|
|
}
|
|
|
|
$item.remove();
|
|
|
|
if ($(self.itemContainerSelector + " .fu-item").length > 0) {
|
|
if (autoProceed) {
|
|
self._processNextItemInQueue(autoProceed);
|
|
}
|
|
}
|
|
else {
|
|
self.opts.onDoneAll();
|
|
}
|
|
},
|
|
|
|
// Public methods
|
|
uploadAll: function () {
|
|
var self = this;
|
|
var result = self.opts.onUploadAll(self);
|
|
if (result === false) {
|
|
return false;
|
|
}
|
|
|
|
self._processNextItemInQueue(true);
|
|
},
|
|
|
|
cancelAll: function () {
|
|
var self = this;
|
|
|
|
// Remove current form
|
|
$(self.formContainerSelector).empty();
|
|
|
|
var queuedItems = $(self.itemContainerSelector + " .fu-item");
|
|
for (var i = 0; i < queuedItems.length; i + 1) {
|
|
var data = $(queuedItems.get(i)).data('data');
|
|
self.cancelItem(data.itemId);
|
|
}
|
|
|
|
self.itemCount = 0;
|
|
|
|
// Recreate the form
|
|
self._createNewForm();
|
|
},
|
|
|
|
cancelItem: function (itemId) {
|
|
var self = this;
|
|
var $item = $("#fu-item-" + self.uploaderId + "-" + itemId);
|
|
var data = $item.data('data');
|
|
|
|
// If the item to cancel is in progress, abort the upload
|
|
if (self.inProgressItemId == itemId && self.inProgressJqXhr) {
|
|
//NB: The abort event handler will cleanup the aborted item from the queue
|
|
self.inProgressJqXhr.abort();
|
|
}
|
|
else {
|
|
// Update the status
|
|
data.status = 'canceled';
|
|
|
|
self.opts.onDone(data);
|
|
|
|
self._afterUpload($item, false);
|
|
}
|
|
}
|
|
};
|
|
|
|
$.fileUploader = {
|
|
version: '1.0',
|
|
count: 0,
|
|
timeInterval: [1, 2, 4, 2, 1, 5, 5, 5, 5],
|
|
percentageInterval: [10, 20, 30, 40, 60, 80, 85, 90, 95]
|
|
};
|
|
|
|
$.fn.fileUploader = function (o) {
|
|
var args = arguments;
|
|
if (typeof o === 'string') {
|
|
var api = this.fileUploaderApi();
|
|
if (api[o]) {
|
|
return api[o].apply(api, Array.prototype.slice.call(args, 1));
|
|
} else {
|
|
$.error('Method ' + o + ' does not exist on jQuery.fileUploader');
|
|
}
|
|
} else if (typeof o === 'object' || !o) {
|
|
return this.each(function () {
|
|
var fileUploader = new FileUploader(this, o);
|
|
$(this).data("fileuploader_api", fileUploader);
|
|
});
|
|
}
|
|
};
|
|
|
|
$.fn.fileUploaderApi = function () {
|
|
//ensure there's only 1
|
|
if (this.length != 1) {
|
|
throw "Requesting the API can only match one element";
|
|
}
|
|
|
|
//ensure thsi is a collapse panel
|
|
if (this.data("fileuploader_api") === null) {
|
|
throw "The matching element had not been bound to a fileUploader";
|
|
}
|
|
|
|
return this.data("fileuploader_api");
|
|
};
|
|
|
|
})(jQuery, window, document); |