From 8f07e42e22fd6112d82c51587da9028516ec6b69 Mon Sep 17 00:00:00 2001 From: Matthias Schmidt Date: Sun, 27 Sep 2015 10:34:11 +0200 Subject: [PATCH] Add Upload.js --- wcfsetup/install/files/js/WCF.js | 6 +- .../install/files/js/WoltLab/WCF/Upload.js | 327 ++++++++++++++++++ 2 files changed, 332 insertions(+), 1 deletion(-) create mode 100644 wcfsetup/install/files/js/WoltLab/WCF/Upload.js diff --git a/wcfsetup/install/files/js/WCF.js b/wcfsetup/install/files/js/WCF.js index 8cdb33fa34..e11c1725f0 100755 --- a/wcfsetup/install/files/js/WCF.js +++ b/wcfsetup/install/files/js/WCF.js @@ -6514,7 +6514,9 @@ WCF.InlineEditor = Class.extend({ }); /** - * Default implementation for ajax file uploads + * Default implementation for ajax file uploads. + * + * @deprecated Use WoltLab/WCF/Upload * * @param jquery buttonSelector * @param jquery fileListSelector @@ -6891,6 +6893,8 @@ WCF.Upload = Class.extend({ /** * Default implementation for parallel AJAX file uploads. + * + * @deprecated Use WoltLab/WCF/Upload */ WCF.Upload.Parallel = WCF.Upload.extend({ /** diff --git a/wcfsetup/install/files/js/WoltLab/WCF/Upload.js b/wcfsetup/install/files/js/WoltLab/WCF/Upload.js new file mode 100644 index 0000000000..d6123dd0c3 --- /dev/null +++ b/wcfsetup/install/files/js/WoltLab/WCF/Upload.js @@ -0,0 +1,327 @@ +/** + * Uploads file via AJAX. + * + * @author Matthias Schmidt + * @copyright 2001-2015 WoltLab GmbH + * @license GNU Lesser General Public License + * @module WoltLab/WCF/Upload + */ +define(['AjaxRequest', 'Core', 'Dom/ChangeListener', 'Language', 'Dom/Util'], function(AjaxRequest, Core, DomChangeListener, Language, DomUtil) { + "use strict"; + + /** + * @constructor + */ + function Upload(buttonContainerId, targetId, options) { + options = options || {}; + + if (options.className === undefined) { + throw new Error("Missing class name."); + } + + // set default options + this._options = Core.extend({ + // name of the PHP action + action: 'upload', + // is true if multiple files can be uploaded at once + multiple: false, + // name if the upload field + name: '__files[]', + // is true if every file from a multi-file selection is uploaded in its own request + singleFileRequests: false, + // url for uploading file + url: 'index.php/AJAXUpload/?t=' + SECURITY_TOKEN + SID_ARG_2ND + }, options); + + this._options.url = WCF.convertLegacyURL(this._options.url); + + this._buttonContainer = elById(buttonContainerId); + if (this._buttonContainer === null) { + throw new Error("Element id '" + buttonContainerId + "' is unknown."); + } + + this._target = elById(targetId); + if (targetId === null) { + throw new Error("Element id '" + targetId + "' is unknown."); + } + if (options.multiple && this._target.nodeName !== 'UL' && this._target.nodeName !== 'OL') { + throw new Error("Target element has to be list when allowing upload of multiple files."); + } + + this._fileElements = []; + this._internalFileId = 0; + + this._createButton(); + }; + Upload.prototype = { + /** + * Creates the upload button. + */ + _createButton: function() { + this._fileUpload = elCreate('input'); + elAttr(this._fileUpload, 'type', 'file'); + elAttr(this._fileUpload, 'name', this._options.name); + if (this._options.multiple) { + elAttr(this._fileUpload, 'multiple', 'true'); + } + this._fileUpload.addEventListener('change', this._upload.bind(this)); + + this._button = elCreate('p'); + this._button.classList.add('button'); + this._button.classList.add('uploadButton'); + + var span = elCreate('span'); + span.textContent = Language.get('wcf.global.button.upload'); + this._button.appendChild(span); + + DomUtil.prepend(this._fileUpload, this._button); + + this._insertButton(); + + DomChangeListener.trigger(); + }, + + /** + * Creates the document element for an uploaded file. + * + * @param {File} file uploaded file + */ + _createFileElement: function(file) { + var progress = elCreate('progress'); + elAttr(progress, 'max', 100); + + if (this._target.nodeName === 'OL' || this._target.nodeName === 'UL') { + var li = elCreate('li'); + li.innerText = file.name; + li.appendChild(progress); + + this._target.appendChild(li); + + return li; + } + else { + var p = elCreate('p'); + p.appendChild(progress); + + this._target.appendChild(p); + + return p; + } + }, + + /** + * Creates the document elements for uploaded files. + * + * @param {(FileList|Array.)} files uploaded files + */ + _createFileElements: function(files) { + if (files.length) { + var uploadId = this._fileElements.length; + this._fileElements[uploadId] = []; + + for (var i = 0, length = files.length; i < length; i++) { + var file = files[i]; + var fileElement = this._createFileElement(file); + + if (!fileElement.classList.contains('uploadFailed')) { + elAttr(fileElement, 'data-filename', file.name); + elAttr(fileElement, 'data-internal-file-id', this._internalFileId++); + this._fileElements[uploadId][i] = fileElement; + } + } + + DomChangeListener.trigger(); + + return uploadId; + } + + return null; + }, + + /** + * Handles a failed file upload. + * + * @param {integer} uploadId identifier of a file upload + * @param {object} data response data + * @param {string} responseText response + * @param {XMLHttpRequest} xhr request object + * @param {object} requestOptions options used to send AJAX request + * @return {boolean} true if the error message should be shown + */ + _failure: function(uploadId, data, responseText, xhr, requestOptions) { + // does nothing + return true; + }, + + /** + * Return additional parameters for upload requests. + * + * @return {object} additional parameters + */ + _getParameters: function() { + return {}; + }, + + /** + * Inserts the created button to upload files into the button container. + */ + _insertButton: function() { + DomUtil.prepend(this._button, this._buttonContainer); + }, + + /** + * Updates the progress of an upload. + * + * @param {integer} uploadId internal upload identifier + * @param {XMLHttpRequestProgressEvent} event progress event object + */ + _progress: function(uploadId, event) { + var percentComplete = Math.round(event.loaded / event.total * 100); + + for (var i in this._fileElements[uploadId]) { + var progress = elByTag('PROGRESS', this._fileElements[uploadId][i]); + if (progress.length === 1) { + elAttr(progress[0], 'value', percentComplete); + } + } + }, + + /** + * Removes the button to upload files. + */ + _removeButton: function() { + this._button.parentNode.removeChild(this._button); + + DomChangeListener.trigger(); + }, + + /** + * Handles a successful file upload. + * + * @param {integer} uploadId identifier of a file upload + * @param {object} data response data + * @param {string} responseText response + * @param {XMLHttpRequest} xhr request object + * @param {object} requestOptions options used to send AJAX request + */ + _success: function(uploadId, data, responseText, xhr, requestOptions) { + // does nothing + }, + + /** + * File input change callback to upload files. + * + * @param {Event} event input change event object + * @param {File} file uploaded file + * @param {Blob} blob file blob + * @return {(integer|Array.|null)} identifier(s) for the uploaded files + */ + _upload: function(event, file, blob) { + var uploadId = null; + + var files = []; + if (file) { + files.push(file); + } + else if (blob) { + var fileExtension = ''; + switch (blob.type) { + case 'image/jpeg': + fileExtension = '.jpg'; + break; + + case 'image/gif': + fileExtension = '.gif'; + break; + + case 'image/png': + fileExtension = '.png'; + break; + } + + files.push({ + name: 'pasted-from-clipboard' + fileExtension + }); + } + else { + files = this._fileUpload.files; + } + + if (files.length) { + if (this._options.singleFileRequests) { + uploadId = []; + for (var i = 0, length = files.length; i < length; i++) { + uploadId.push(this._uploadFiles([ files[i] ], blob)); + } + } + else { + uploadId = this._uploadFiles(files, blob); + } + } + + // re-create upload button to effectively reset the 'files' + // property of the input element + this._removeButton(); + this._createButton(); + + return uploadId; + }, + + /** + * Sends the request to upload files. + * + * @param {(FileList|Array.)} files uploaded files + * @param {Blob} blob file blob + * @return {(integer|null)} identifier for the uploaded files + */ + _uploadFiles: function(files, blob) { + var uploadId = this._createFileElements(files); + + // no more files left, abort + if (!this._fileElements[uploadId].length) { + return null; + } + + var formData = new FormData(); + for (var i = 0, length = files.length; i < length; i++) { + if (this._fileElements[uploadId][i]) { + var internalFileId = elAttr(this._fileElements[uploadId][i], 'data-internal-file-id'); + + if (blob) { + formData.append('__files[' + internalFileId + ']', blob, files[i].name); + } + else { + formData.append('__files[' + internalFileId + ']', files[i]); + } + } + } + + formData.append('actionName', this._options.action); + formData.append('className', this._options.className); + var additionalParameters = this._getParameters(); + for (var name in additionalParameters) { + formData.append('parameters[' + name + ']', additionalParameters[name]); + } + + var request = new AjaxRequest({ + data: formData, + contentType: false, + failure: function(data, responseText, xhr, requestOptions) { + this._failure(uploadId, data, responseText, xhr, options.data); + }.bind(this), + success: function(data, responseText, xhr, requestOptions) { + this._success(uploadId, data, responseText, xhr, requestOptions); + }.bind(this), + uploadProgress: (function(event) { + this._progress(uploadId, event); + }).bind(this), + url: this._options.url + }); + request.sendRequest(); + + return uploadId; + } + }; + + return Upload; +}); -- 2.20.1