Rewrite JS API for uploads
authorJoshua Rüsweg <josh@bastelstu.be>
Fri, 11 Jan 2019 23:02:06 +0000 (00:02 +0100)
committerJoshua Rüsweg <josh@bastelstu.be>
Fri, 11 Jan 2019 23:02:06 +0000 (00:02 +0100)
See #2825

wcfsetup/install/files/js/WoltLabSuite/Core/Ui/File/Upload.js
wcfsetup/install/files/js/WoltLabSuite/Core/Upload.js

index 53e334f3fe742e3703bd4811e481d16888f20100..52f4b2957d3c29ca01a1df34798d1f7123afc80a 100644 (file)
@@ -5,9 +5,9 @@
  * @copyright  2001-2018 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @module     WoltLabSuite/Core/Ui/File/Upload
- * @since      3.2
+ * @since      5.2
  */
-define(['AjaxRequest', 'Core', 'Dom/ChangeListener', 'Language', 'Dom/Util', 'Dom/Traverse', 'WoltLabSuite/Core/Ui/File/Delete'], function(AjaxRequest, Core, DomChangeListener, Language, DomUtil, DomTraverse, DeleteHandler) {
+define(['AjaxRequest', 'Core', 'Dom/ChangeListener', 'Language', 'Dom/Util', 'Dom/Traverse', 'WoltLabSuite/Core/Ui/File/Delete', 'Upload'], function(AjaxRequest, Core, DomChangeListener, Language, DomUtil, DomTraverse, DeleteHandler, CoreUpload) {
        "use strict";
        
        /**
@@ -65,118 +65,51 @@ define(['AjaxRequest', 'Core', 'Dom/ChangeListener', 'Language', 'Dom/Util', 'Do
                this._deleteHandler = new DeleteHandler(buttonContainerId, targetId, this._options.imagePreview, this);
        }
        
-       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));
+       Core.inherit(Upload, CoreUpload, {
+               _createFileElement: function(file) {
+                       var element = Upload._super.prototype._createFileElement.call(this, file);
+                       element.classList.add('box64', 'uploadedFile');
                        
-                       this._button = elCreate('p');
-                       this._button.className = 'button uploadButton';
+                       var progress = elBySel('progress', element);
                        
-                       var span = elCreate('span');
-                       span.textContent = Language.get('wcf.global.button.upload');
-                       this._button.appendChild(span);
+                       var icon = elCreate('span');
+                       icon.classList = 'icon icon64 fa-spinner';
                        
-                       DomUtil.prepend(this._fileUpload, this._button);
+                       var fileName = element.textContent;
+                       element.textContent = "";
+                       element.append(icon);
                        
-                       this._insertButton();
+                       var innerDiv = elCreate('div');
+                       var fileNameP = elCreate('p');
+                       fileNameP.textContent = fileName; // file.name
                        
-                       DomChangeListener.trigger();
-               },
-               
-               /**
-                * Creates the document element for an uploaded file.
-                *
-                * @param       {File}          file            uploaded file
-                * @return      {HTMLElement}
-                */
-               _createFileElement: function(file) {
-                       var li = elCreate('li');
-                       li.classList = 'box64 uploadedFile';
+                       var smallProgress = elCreate('small');
+                       smallProgress.appendChild(progress);
                        
-                       var span = elCreate('span');
-                       span.classList = 'icon icon64 fa-spinner';
-                       li.appendChild(span);
+                       innerDiv.appendChild(fileNameP);
+                       innerDiv.appendChild(smallProgress);
                        
                        var div = elCreate('div');
-                       var innerDiv = elCreate('div');
-                       var p = elCreate('p');
-                       p.textContent = file.name;
-                       
-                       var small = elCreate('small');
-                       var progress = elCreate('progress');
-                       elAttr(progress, 'max', 100);
-                       small.appendChild(progress);
-                       
-                       innerDiv.appendChild(p);
-                       innerDiv.appendChild(small);
+                       div.appendChild(innerDiv);
                        
                        var ul = elCreate('ul');
                        ul.classList = 'buttonGroup';
-                       
-                       div.appendChild(innerDiv);
                        div.appendChild(ul);
-                       li.appendChild(div);
                        
-                       this._target.appendChild(li);
+                       // reset element textContent and replace with own element style
+                       element.append(div);
                        
-                       return li;
+                       return element;
                },
                
-               /**
-                * Creates the document elements for uploaded files.
-                *
-                * @param       {(FileList|Array.<File>)}       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')) {
-                                               elData(fileElement, 'filename', file.name);
-                                               elData(fileElement, 'internal-file-id', this._internalFileId++);
-                                               this._fileElements[uploadId][i] = fileElement;
-                                       }
-                               }
-                               
-                               DomChangeListener.trigger();
-                               
-                               return uploadId;
-                       }
-                       
-                       return null;
-               },
-               
-               /**
-                * Handles a failed file upload.
-                *
-                * @param       {int}                   uploadId        identifier of a file upload
-                * @param       {object<string, *>}     data            response data
-                * @param       {string}                responseText    response
-                * @param       {XMLHttpRequest}        xhr             request object
-                * @param       {object<string, *>}     requestOptions  options used to send AJAX request
-                * @return      {boolean}       true if the error message should be shown
-                */
                _failure: function(uploadId, data, responseText, xhr, requestOptions) {
                        for (var i in this._fileElements[uploadId]) {
                                this._fileElements[uploadId][i].classList.add('uploadFailed');
                                
                                elBySel('small', this._fileElements[uploadId][i]).innerHTML = '';
-                               elBySel('.icon', this._fileElements[uploadId][i]).classList.remove('fa-spinner');
-                               elBySel('.icon', this._fileElements[uploadId][i]).classList.add('fa-ban');
+                               var icon = elBySel('.icon', this._fileElements[uploadId][i]);
+                               icon.classList.remove('fa-spinner');
+                               icon.classList.add('fa-ban');
                                
                                var innerError = elCreate('span');
                                innerError.classList = 'innerError';
@@ -185,61 +118,8 @@ define(['AjaxRequest', 'Core', 'Dom/ChangeListener', 'Language', 'Dom/Util', 'Do
                        }
                        
                        throw new Error("Upload failed: " + data.message);
-                       
-                       return false;
-               },
-               
-               /**
-                * Return additional parameters for upload requests.
-                *
-                * @return      {object<string, *>}     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       {int}                           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() {
-                       elRemove(this._button);
-                       
-                       DomChangeListener.trigger();
                },
                
-               /**
-                * Handles a successful file upload.
-                *
-                * @param       {int}                   uploadId        identifier of a file upload
-                * @param       {object<string, *>}     data            response data
-                * @param       {string}                responseText    response
-                * @param       {XMLHttpRequest}        xhr             request object
-                * @param       {object<string, *>}     requestOptions  options used to send AJAX request
-                */
                _success: function(uploadId, data, responseText, xhr, requestOptions) {
                        for (var i in this._fileElements[uploadId]) {
                                if (data['files'][i] !== undefined) {
@@ -264,18 +144,19 @@ define(['AjaxRequest', 'Core', 'Dom/ChangeListener', 'Language', 'Dom/Util', 'Do
                                        }
                                        else {
                                                elData(this._fileElements[uploadId][i], 'unique-file-id', data['files'][i].uniqueFileId);
-                                               elBySel('small', this._fileElements[uploadId][i]).innerHTML = '';
                                                elBySel('small', this._fileElements[uploadId][i]).textContent = data['files'][i].filesize;
-                                               elBySel('.icon', this._fileElements[uploadId][i]).classList.remove('fa-spinner');
-                                               elBySel('.icon', this._fileElements[uploadId][i]).classList.add('fa-' + data['files'][i].icon);
+                                               var icon = elBySel('.icon', this._fileElements[uploadId][i]);
+                                               icon.classList.remove('fa-spinner');
+                                               icon.classList.add('fa-' + data['files'][i].icon);
                                        }
                                }
                                else if (data['error'][i] !== undefined) {
                                        this._fileElements[uploadId][i].classList.add('uploadFailed');
                                        
                                        elBySel('small', this._fileElements[uploadId][i]).innerHTML = '';
-                                       elBySel('.icon', this._fileElements[uploadId][i]).classList.remove('fa-spinner');
-                                       elBySel('.icon', this._fileElements[uploadId][i]).classList.add('fa-ban');
+                                       var icon = elBySel('.icon', this._fileElements[uploadId][i]);
+                                       icon.classList.remove('fa-spinner');
+                                       icon.classList.add('fa-ban');
                                        
                                        if (elBySel('.innerError', this._fileElements[uploadId][i]) === null) {
                                                var innerError = elCreate('span');
@@ -297,66 +178,15 @@ define(['AjaxRequest', 'Core', 'Dom/ChangeListener', 'Language', 'Dom/Util', 'Do
                        this.checkMaxFiles();
                },
                
-               /**
-                * 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      {(int|Array.<int>|null)}        identifier(s) for the uploaded files
-                */
-               _upload: function(event, file, blob) {
-                       // remove failed upload elements first
-                       var failedUploads = DomTraverse.childrenByClass(this._target, 'uploadFailed');
-                       for (var i = 0, length = failedUploads.length; i < length; i++) {
-                               elRemove(failedUploads[i]);
-                       }
-                       
-                       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 && files.length + this.countFiles() <= this._options.maxFiles) {
-                               if (this._options.singleFileRequests) {
-                                       uploadId = [];
-                                       for (var i = 0, length = files.length; i < length; i++) {
-                                               var localUploadId = this._uploadFiles([ files[i] ], blob);
-                                               
-                                               if (files.length !== 1) {
-                                                       this._multiFileUploadIds.push(localUploadId)
-                                               }
-                                               uploadId.push(localUploadId);
-                                       }
-                               }
-                               else {
-                                       uploadId = this._uploadFiles(files, blob);
-                               }
+               _getFormData: function() {
+                       return {
+                               internalId: this._options.internalId
+                       };
+               },
+               
+               validateUpload: function(files) {
+                       if (files.length + this.countFiles() <= this._options.maxFiles) {
+                               return true;
                        }
                        else {
                                var innerError = elBySel('small.innerError:not(.innerFileError)', this._buttonContainer.parentNode);
@@ -368,117 +198,9 @@ define(['AjaxRequest', 'Core', 'Dom/ChangeListener', 'Language', 'Dom/Util', 'Do
                                }
                                
                                innerError.textContent = WCF.Language.get('wcf.upload.error.reachedRemainingLimit').replace(/#remaining#/, this._options.maxFiles);
-                       }
-                       
-                       // 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.<File>)}       files           uploaded files
-                * @param       {Blob}                          blob            file blob
-                * @return      {(int|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 = elData(this._fileElements[uploadId][i], 'internal-file-id');
-                                       
-                                       if (blob) {
-                                               formData.append('__files[' + internalFileId + ']', blob, files[i].name);
-                                       }
-                                       else {
-                                               formData.append('__files[' + internalFileId + ']', files[i]);
-                                       }
-                               }
-                       }
-                       
-                       formData.append('internalId', this._options.internalId);
-                       
-                       // recursively append additional parameters to form data
-                       var appendFormData = function(parameters, prefix) {
-                               prefix = prefix || '';
                                
-                               for (var name in parameters) {
-                                       if (typeof parameters[name] === 'object') {
-                                               appendFormData(parameters[name], prefix + '[' + name + ']');
-                                       }
-                                       else {
-                                               formData.append('parameters' + prefix + '[' + name + ']', parameters[name]);
-                                       }
-                               }
-                       };
-                       
-                       appendFormData(this._getParameters());
-                       
-                       var request = new AjaxRequest({
-                               data: formData,
-                               contentType: false,
-                               failure: this._failure.bind(this, uploadId),
-                               silent: true,
-                               success: this._success.bind(this, uploadId),
-                               uploadProgress: this._progress.bind(this, uploadId),
-                               url: this._options.url,
-                               withCredentials: true
-                       });
-                       request.sendRequest();
-                       
-                       return uploadId;
-               },
-               
-               /**
-                * Returns true if there are any pending uploads handled by this
-                * upload manager.
-                *
-                * @return      {boolean}
-                * @since       3.2
-                */
-               hasPendingUploads: function() {
-                       for (var uploadId in this._fileElements) {
-                               for (var i in this._fileElements[uploadId]) {
-                                       var progress = elByTag('PROGRESS', this._fileElements[uploadId][i]);
-                                       if (progress.length === 1) {
-                                               return true;
-                                       }
-                               }
+                               return false;
                        }
-                       
-                       return false;
-               },
-               
-               /**
-                * Uploads the given file blob.
-                *
-                * @param       {Blob}          blob            file blob
-                * @return      {int}           identifier for the uploaded file
-                */
-               uploadBlob: function(blob) {
-                       return this._upload(null, null, blob);
-               },
-               
-               /**
-                * Uploads the given file.
-                *
-                * @param       {File}          file            uploaded file
-                * @return      {int}           identifier(s) for the uploaded file
-                */
-               uploadFile: function(file) {
-                       return this._upload(null, file);
                },
                
                /**
@@ -506,7 +228,7 @@ define(['AjaxRequest', 'Core', 'Dom/ChangeListener', 'Language', 'Dom/Util', 'Do
                                elShow(this._button);
                        }
                }
-       };
+       });
        
        return Upload;
 });
index b9cff77eff21fc2556ff99949350db938b26a42b..da9d359f738b5e3ba4f7c759bd9749df3a354ebc 100644 (file)
@@ -193,6 +193,16 @@ define(['AjaxRequest', 'Core', 'Dom/ChangeListener', 'Language', 'Dom/Util', 'Do
                        return {};
                },
                
+               /**
+                * Return additional form data for upload requests.
+                * 
+                * @return      {object<string, *>}     additional form data
+                * @since       5.2
+                */
+               _getFormData: function() {
+                       return {};
+               },
+               
                /**
                 * Inserts the created button to upload files into the button container.
                 */
@@ -284,7 +294,7 @@ define(['AjaxRequest', 'Core', 'Dom/ChangeListener', 'Language', 'Dom/Util', 'Do
                                files = this._fileUpload.files;
                        }
                        
-                       if (files.length) {
+                       if (files.length && this.validateUpload(files)) {
                                if (this._options.singleFileRequests) {
                                        uploadId = [];
                                        for (var i = 0, length = files.length; i < length; i++) {
@@ -309,6 +319,17 @@ define(['AjaxRequest', 'Core', 'Dom/ChangeListener', 'Language', 'Dom/Util', 'Do
                        return uploadId;
                },
                
+               /**
+                * Validates the upload before uploading them.
+                * 
+                * @param       {(FileList|Array.<File>)}       files           uploaded files
+                * @return      {boolean}
+                * @since       5.2
+                */
+               validateUpload: function(files) {
+                       return true;
+               },
+               
                /**
                 * Sends the request to upload files.
                 * 
@@ -350,15 +371,18 @@ define(['AjaxRequest', 'Core', 'Dom/ChangeListener', 'Language', 'Dom/Util', 'Do
                                
                                for (var name in parameters) {
                                        if (typeof parameters[name] === 'object') {
-                                               appendFormData(parameters[name], prefix + '[' + name + ']');
+                                               var newPrefix = prefix.length === 0 ? name : prefix + '[' + name + ']';
+                                               appendFormData(parameters[name], newPrefix);
                                        }
                                        else {
-                                               formData.append('parameters' + prefix + '[' + name + ']', parameters[name]);
+                                               var dataName = prefix.length === 0 ? name : prefix + '[' + name + ']';
+                                               formData.append(dataName, parameters[name]);
                                        }
                                }
                        };
                        
-                       appendFormData(this._getParameters());
+                       appendFormData(this._getParameters(), '[parameters]');
+                       appendFormData(this._getFormData());
                        
                        var request = new AjaxRequest({
                                data: formData,