/**
* Class that creates upload widget with drag-and-drop and file list
* @inherits qq.FineUploaderBasic
*/
qq.FineUploader = function(o){
// call parent constructor
qq.FineUploaderBasic.apply(this, arguments);
// additional options
qq.extend(this._options, {
element: null,
listElement: null,
dragAndDrop: {
extraDropzones: [],
hideDropzones: true,
disableDefaultDropzone: false
},
text: {
uploadButton: 'Upload a file',
cancelButton: 'Cancel',
retryButton: 'Retry',
deleteButton: 'Delete',
failUpload: 'Upload failed',
dragZone: 'Drop files here to upload',
dropProcessing: 'Processing dropped files...',
formatProgress: "{percent}% of {total_size}",
waitingForResponse: "Processing..."
},
template: '
' +
((!this._options.dragAndDrop || !this._options.dragAndDrop.disableDefaultDropzone) ? '
{dragZoneText}
' : '') +
(!this._options.button ? '
' : '') +
'
{dropProcessingText}' +
(!this._options.listElement ? '
' : '') +
'
',
// template for one item in file list
fileTemplate: '' +
'' +
'' +
'' +
'' +
'' +
'{cancelButtonText}' +
'{retryButtonText}' +
'{deleteButtonText}' +
'{statusText}' +
'',
classes: {
button: 'qq-upload-button',
drop: 'qq-upload-drop-area',
dropActive: 'qq-upload-drop-area-active',
list: 'qq-upload-list',
progressBar: 'qq-progress-bar',
file: 'qq-upload-file',
spinner: 'qq-upload-spinner',
finished: 'qq-upload-finished',
retrying: 'qq-upload-retrying',
retryable: 'qq-upload-retryable',
size: 'qq-upload-size',
cancel: 'qq-upload-cancel',
deleteButton: 'qq-upload-delete',
retry: 'qq-upload-retry',
statusText: 'qq-upload-status-text',
success: 'qq-upload-success',
fail: 'qq-upload-fail',
successIcon: null,
failIcon: null,
dropProcessing: 'qq-drop-processing',
dropProcessingSpinner: 'qq-drop-processing-spinner'
},
failedUploadTextDisplay: {
mode: 'default', //default, custom, or none
maxChars: 50,
responseProperty: 'error',
enableTooltip: true
},
messages: {
tooManyFilesError: "You may only drop one file",
unsupportedBrowser: "Unrecoverable error - this browser does not permit file uploading of any kind."
},
retry: {
showAutoRetryNote: true,
autoRetryNote: "Retrying {retryNum}/{maxAuto}...",
showButton: false
},
deleteFile: {
forceConfirm: false,
confirmMessage: "Are you sure you want to delete {filename}?",
deletingStatusText: "Deleting...",
deletingFailedText: "Delete failed"
},
display: {
fileSizeOnSubmit: false
},
paste: {
promptForName: false,
namePromptMessage: "Please name this image"
},
showMessage: function(message){
setTimeout(function() {
window.alert(message);
}, 0);
},
showConfirm: function(message, okCallback, cancelCallback) {
setTimeout(function() {
var result = window.confirm(message);
if (result) {
okCallback();
}
else if (cancelCallback) {
cancelCallback();
}
}, 0);
},
showPrompt: function(message, defaultValue) {
var promise = new qq.Promise(),
retVal = window.prompt(message, defaultValue);
/*jshint eqeqeq: true, eqnull: true*/
if (retVal != null && qq.trimStr(retVal).length > 0) {
promise.success(retVal);
}
else {
promise.failure("Undefined or invalid user-supplied value.");
}
return promise;
}
}, true);
// overwrite options with user supplied
qq.extend(this._options, o, true);
if (!qq.supportedFeatures.uploading || (this._options.cors.expected && !qq.supportedFeatures.uploadCors)) {
this._options.element.innerHTML = "" + this._options.messages.unsupportedBrowser + "
"
}
else {
this._wrapCallbacks();
// overwrite the upload button text if any
// same for the Cancel button and Fail message text
this._options.template = this._options.template.replace(/\{dragZoneText\}/g, this._options.text.dragZone);
this._options.template = this._options.template.replace(/\{uploadButtonText\}/g, this._options.text.uploadButton);
this._options.template = this._options.template.replace(/\{dropProcessingText\}/g, this._options.text.dropProcessing);
this._options.fileTemplate = this._options.fileTemplate.replace(/\{cancelButtonText\}/g, this._options.text.cancelButton);
this._options.fileTemplate = this._options.fileTemplate.replace(/\{retryButtonText\}/g, this._options.text.retryButton);
this._options.fileTemplate = this._options.fileTemplate.replace(/\{deleteButtonText\}/g, this._options.text.deleteButton);
this._options.fileTemplate = this._options.fileTemplate.replace(/\{statusText\}/g, "");
this._element = this._options.element;
this._element.innerHTML = this._options.template;
this._listElement = this._options.listElement || this._find(this._element, 'list');
this._classes = this._options.classes;
if (!this._button) {
this._button = this._createUploadButton(this._find(this._element, 'button'));
}
this._bindCancelAndRetryEvents();
this._dnd = this._setupDragAndDrop();
if (this._options.paste.targetElement && this._options.paste.promptForName) {
this._setupPastePrompt();
}
}
};
// inherit from Basic Uploader
qq.extend(qq.FineUploader.prototype, qq.FineUploaderBasic.prototype);
qq.extend(qq.FineUploader.prototype, {
clearStoredFiles: function() {
qq.FineUploaderBasic.prototype.clearStoredFiles.apply(this, arguments);
this._listElement.innerHTML = "";
},
addExtraDropzone: function(element){
this._dnd.setupExtraDropzone(element);
},
removeExtraDropzone: function(element){
return this._dnd.removeDropzone(element);
},
getItemByFileId: function(id){
var item = this._listElement.firstChild;
// there can't be txt nodes in dynamically created list
// and we can use nextSibling
while (item){
if (item.qqFileId == id) return item;
item = item.nextSibling;
}
},
reset: function() {
qq.FineUploaderBasic.prototype.reset.apply(this, arguments);
this._element.innerHTML = this._options.template;
this._listElement = this._options.listElement || this._find(this._element, 'list');
if (!this._options.button) {
this._button = this._createUploadButton(this._find(this._element, 'button'));
}
this._bindCancelAndRetryEvents();
this._dnd.dispose();
this._dnd = this._setupDragAndDrop();
},
_removeFileItem: function(fileId) {
var item = this.getItemByFileId(fileId);
qq(item).remove();
},
_setupDragAndDrop: function() {
var self = this,
dropProcessingEl = this._find(this._element, 'dropProcessing'),
dropZoneElements = this._options.dragAndDrop.extraDropzones,
preventSelectFiles;
preventSelectFiles = function(event) {
event.preventDefault();
};
if (!this._options.dragAndDrop.disableDefaultDropzone) {
dropZoneElements.push(this._find(this._options.element, 'drop'));
}
return new qq.DragAndDrop({
dropZoneElements: dropZoneElements,
hideDropZonesBeforeEnter: this._options.dragAndDrop.hideDropzones,
allowMultipleItems: this._options.multiple,
classes: {
dropActive: this._options.classes.dropActive
},
callbacks: {
processingDroppedFiles: function() {
var input = self._button.getInput();
qq(dropProcessingEl).css({display: 'block'});
qq(input).attach('click', preventSelectFiles);
},
processingDroppedFilesComplete: function(files) {
var input = self._button.getInput();
qq(dropProcessingEl).hide();
qq(input).detach('click', preventSelectFiles);
if (files) {
self.addFiles(files);
}
},
dropError: function(code, errorData) {
self._itemError(code, errorData);
},
dropLog: function(message, level) {
self.log(message, level);
}
}
});
},
_leaving_document_out: function(e){
return ((qq.chrome() || (qq.safari() && qq.windows())) && e.clientX == 0 && e.clientY == 0) // null coords for Chrome and Safari Windows
|| (qq.firefox() && !e.relatedTarget); // null e.relatedTarget for Firefox
},
_storeForLater: function(id) {
qq.FineUploaderBasic.prototype._storeForLater.apply(this, arguments);
var item = this.getItemByFileId(id);
qq(this._find(item, 'spinner')).hide();
},
/**
* Gets one of the elements listed in this._options.classes
**/
_find: function(parent, type){
var element = qq(parent).getByClass(this._options.classes[type])[0];
if (!element){
throw new Error('element not found ' + type);
}
return element;
},
_onSubmit: function(id, name){
qq.FineUploaderBasic.prototype._onSubmit.apply(this, arguments);
this._addToList(id, name);
},
// Update the progress bar & percentage as the file is uploaded
_onProgress: function(id, name, loaded, total){
qq.FineUploaderBasic.prototype._onProgress.apply(this, arguments);
var item, progressBar, percent, cancelLink;
item = this.getItemByFileId(id);
progressBar = this._find(item, 'progressBar');
percent = Math.round(loaded / total * 100);
if (loaded === total) {
cancelLink = this._find(item, 'cancel');
qq(cancelLink).hide();
qq(progressBar).hide();
qq(this._find(item, 'statusText')).setText(this._options.text.waitingForResponse);
// If last byte was sent, display total file size
this._displayFileSize(id);
}
else {
// If still uploading, display percentage - total size is actually the total request(s) size
this._displayFileSize(id, loaded, total);
qq(progressBar).css({display: 'block'});
}
// Update progress bar element
qq(progressBar).css({width: percent + '%'});
},
_onComplete: function(id, name, result, xhr){
qq.FineUploaderBasic.prototype._onComplete.apply(this, arguments);
var item = this.getItemByFileId(id);
qq(this._find(item, 'statusText')).clearText();
qq(item).removeClass(this._classes.retrying);
qq(this._find(item, 'progressBar')).hide();
if (!this._options.disableCancelForFormUploads || qq.supportedFeatures.ajaxUploading) {
qq(this._find(item, 'cancel')).hide();
}
qq(this._find(item, 'spinner')).hide();
if (result.success) {
if (this._isDeletePossible()) {
this._showDeleteLink(id);
}
qq(item).addClass(this._classes.success);
if (this._classes.successIcon) {
this._find(item, 'finished').style.display = "inline-block";
qq(item).addClass(this._classes.successIcon);
}
} else {
qq(item).addClass(this._classes.fail);
if (this._classes.failIcon) {
this._find(item, 'finished').style.display = "inline-block";
qq(item).addClass(this._classes.failIcon);
}
if (this._options.retry.showButton && !this._preventRetries[id]) {
qq(item).addClass(this._classes.retryable);
}
this._controlFailureTextDisplay(item, result);
}
},
_onUpload: function(id, name){
qq.FineUploaderBasic.prototype._onUpload.apply(this, arguments);
this._showSpinner(id);
},
_onCancel: function(id, name) {
qq.FineUploaderBasic.prototype._onCancel.apply(this, arguments);
this._removeFileItem(id);
},
_onBeforeAutoRetry: function(id) {
var item, progressBar, failTextEl, retryNumForDisplay, maxAuto, retryNote;
qq.FineUploaderBasic.prototype._onBeforeAutoRetry.apply(this, arguments);
item = this.getItemByFileId(id);
progressBar = this._find(item, 'progressBar');
this._showCancelLink(item);
progressBar.style.width = 0;
qq(progressBar).hide();
if (this._options.retry.showAutoRetryNote) {
failTextEl = this._find(item, 'statusText');
retryNumForDisplay = this._autoRetries[id] + 1;
maxAuto = this._options.retry.maxAutoAttempts;
retryNote = this._options.retry.autoRetryNote.replace(/\{retryNum\}/g, retryNumForDisplay);
retryNote = retryNote.replace(/\{maxAuto\}/g, maxAuto);
qq(failTextEl).setText(retryNote);
if (retryNumForDisplay === 1) {
qq(item).addClass(this._classes.retrying);
}
}
},
//return false if we should not attempt the requested retry
_onBeforeManualRetry: function(id) {
var item = this.getItemByFileId(id);
if (qq.FineUploaderBasic.prototype._onBeforeManualRetry.apply(this, arguments)) {
this._find(item, 'progressBar').style.width = 0;
qq(item).removeClass(this._classes.fail);
qq(this._find(item, 'statusText')).clearText();
this._showSpinner(id);
this._showCancelLink(item);
return true;
}
else {
qq(item).addClass(this._classes.retryable);
return false;
}
},
_onSubmitDelete: function(id) {
if (this._isDeletePossible()) {
if (this._options.callbacks.onSubmitDelete(id) !== false) {
if (this._options.deleteFile.forceConfirm) {
this._showDeleteConfirm(id);
}
else {
this._sendDeleteRequest(id);
}
}
}
else {
this.log("Delete request ignored for file ID " + id + ", delete feature is disabled.", "warn");
return false;
}
},
_onDeleteComplete: function(id, xhr, isError) {
qq.FineUploaderBasic.prototype._onDeleteComplete.apply(this, arguments);
var item = this.getItemByFileId(id),
spinnerEl = this._find(item, 'spinner'),
statusTextEl = this._find(item, 'statusText');
qq(spinnerEl).hide();
if (isError) {
qq(statusTextEl).setText(this._options.deleteFile.deletingFailedText);
this._showDeleteLink(id);
}
else {
this._removeFileItem(id);
}
},
_sendDeleteRequest: function(id) {
var item = this.getItemByFileId(id),
deleteLink = this._find(item, 'deleteButton'),
statusTextEl = this._find(item, 'statusText');
qq(deleteLink).hide();
this._showSpinner(id);
qq(statusTextEl).setText(this._options.deleteFile.deletingStatusText);
this._deleteHandler.sendDelete(id, this.getUuid(id));
},
_showDeleteConfirm: function(id) {
var fileName = this._handler.getName(id),
confirmMessage = this._options.deleteFile.confirmMessage.replace(/\{filename\}/g, fileName),
uuid = this.getUuid(id),
self = this;
this._options.showConfirm(confirmMessage, function() {
self._sendDeleteRequest(id);
});
},
_addToList: function(id, name){
var item = qq.toElement(this._options.fileTemplate);
if (this._options.disableCancelForFormUploads && !qq.supportedFeatures.ajaxUploading) {
var cancelLink = this._find(item, 'cancel');
qq(cancelLink).remove();
}
item.qqFileId = id;
var fileElement = this._find(item, 'file');
qq(fileElement).setText(this._options.formatFileName(name));
qq(this._find(item, 'size')).hide();
if (!this._options.multiple) {
this._handler.cancelAll();
this._clearList();
}
this._listElement.appendChild(item);
if (this._options.display.fileSizeOnSubmit && qq.supportedFeatures.ajaxUploading) {
this._displayFileSize(id);
}
},
_clearList: function(){
this._listElement.innerHTML = '';
this.clearStoredFiles();
},
_displayFileSize: function(id, loadedSize, totalSize) {
var item = this.getItemByFileId(id),
size = this.getSize(id),
sizeForDisplay = this._formatSize(size),
sizeEl = this._find(item, 'size');
if (loadedSize !== undefined && totalSize !== undefined) {
sizeForDisplay = this._formatProgress(loadedSize, totalSize);
}
qq(sizeEl).css({display: 'inline'});
qq(sizeEl).setText(sizeForDisplay);
},
/**
* delegate click event for cancel & retry links
**/
_bindCancelAndRetryEvents: function(){
var self = this,
list = this._listElement;
this._disposeSupport.attach(list, 'click', function(e){
e = e || window.event;
var target = e.target || e.srcElement;
if (qq(target).hasClass(self._classes.cancel) || qq(target).hasClass(self._classes.retry) || qq(target).hasClass(self._classes.deleteButton)){
qq.preventDefault(e);
var item = target.parentNode;
while(item.qqFileId === undefined) {
item = item.parentNode;
}
if (qq(target).hasClass(self._classes.deleteButton)) {
self.deleteFile(item.qqFileId);
}
else if (qq(target).hasClass(self._classes.cancel)) {
self.cancel(item.qqFileId);
}
else {
qq(item).removeClass(self._classes.retryable);
self.retry(item.qqFileId);
}
}
});
},
_formatProgress: function (uploadedSize, totalSize) {
var message = this._options.text.formatProgress;
function r(name, replacement) { message = message.replace(name, replacement); }
r('{percent}', Math.round(uploadedSize / totalSize * 100));
r('{total_size}', this._formatSize(totalSize));
return message;
},
_controlFailureTextDisplay: function(item, response) {
var mode, maxChars, responseProperty, failureReason, shortFailureReason;
mode = this._options.failedUploadTextDisplay.mode;
maxChars = this._options.failedUploadTextDisplay.maxChars;
responseProperty = this._options.failedUploadTextDisplay.responseProperty;
if (mode === 'custom') {
failureReason = response[responseProperty];
if (failureReason) {
if (failureReason.length > maxChars) {
shortFailureReason = failureReason.substring(0, maxChars) + '...';
}
}
else {
failureReason = this._options.text.failUpload;
this.log("'" + responseProperty + "' is not a valid property on the server response.", 'warn');
}
qq(this._find(item, 'statusText')).setText(shortFailureReason || failureReason);
if (this._options.failedUploadTextDisplay.enableTooltip) {
this._showTooltip(item, failureReason);
}
}
else if (mode === 'default') {
qq(this._find(item, 'statusText')).setText(this._options.text.failUpload);
}
else if (mode !== 'none') {
this.log("failedUploadTextDisplay.mode value of '" + mode + "' is not valid", 'warn');
}
},
_showTooltip: function(item, text) {
item.title = text;
},
_showSpinner: function(id) {
var item = this.getItemByFileId(id),
spinnerEl = this._find(item, 'spinner');
spinnerEl.style.display = "inline-block";
},
_showCancelLink: function(item) {
if (!this._options.disableCancelForFormUploads || qq.supportedFeatures.ajaxUploading) {
var cancelLink = this._find(item, 'cancel');
qq(cancelLink).css({display: 'inline'});
}
},
_showDeleteLink: function(id) {
var item = this.getItemByFileId(id),
deleteLink = this._find(item, 'deleteButton');
qq(deleteLink).css({display: 'inline'});
},
_itemError: function(code, name){
var message = qq.FineUploaderBasic.prototype._itemError.apply(this, arguments);
this._options.showMessage(message);
},
_batchError: function(message) {
qq.FineUploaderBasic.prototype._batchError.apply(this, arguments);
this._options.showMessage(message);
},
_setupPastePrompt: function() {
var self = this;
this._options.callbacks.onPasteReceived = function() {
var message = self._options.paste.namePromptMessage,
defaultVal = self._options.paste.defaultName;
return self._options.showPrompt(message, defaultVal);
};
}
});