JAVASCRIPT   11

Upload.js

Guest on 6th June 2021 05:33:37 AM

  1. /**
  2.  * Uploads file via AJAX.
  3.  *
  4.  * @author  Matthias Schmidt
  5.  * @copyright     2001-2021 WoltLab GmbH
  6.  * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  7.  * @module  Upload (alias)
  8.  * @module  WoltLabSuite/Core/Upload
  9.  */
  10. define(['AjaxRequest', 'Core', 'Dom/ChangeListener', 'Language', 'Dom/Util', 'Dom/Traverse'], function(AjaxRequest, Core, DomChangeListener, Language, DomUtil, DomTraverse) {
  11.       "use strict";
  12.      
  13.       if (!COMPILER_TARGET_DEFAULT) {
  14.             var Fake = function() {};
  15.             Fake.prototype = {
  16.                   _createButton: function() {},
  17.                   _createFileElement: function() {},
  18.                   _createFileElements: function() {},
  19.                   _failure: function() {},
  20.                   _getParameters: function() {},
  21.                   _insertButton: function() {},
  22.                   _progress: function() {},
  23.                   _removeButton: function() {},
  24.                   _success: function() {},
  25.                   _upload: function() {},
  26.                   _uploadFiles: function() {}
  27.             };
  28.             return Fake;
  29.       }
  30.      
  31.       /**
  32.        * @constructor
  33.        */
  34.       function Upload(buttonContainerId, targetId, options) {
  35.             options = options || {};
  36.            
  37.             if (options.className === undefined) {
  38.                   throw new Error("Missing class name.");
  39.             }
  40.            
  41.             // set default options
  42.             this._options = Core.extend({
  43.                   // name of the PHP action
  44.                   action: 'upload',
  45.                   // is true if multiple files can be uploaded at once
  46.                   multiple: false,
  47.                   // array of acceptable file types, null if any file type is acceptable
  48.                   acceptableFiles: null,
  49.                   // name if the upload field
  50.                   name: '__files[]',
  51.                   // is true if every file from a multi-file selection is uploaded in its own request
  52.                   singleFileRequests: false,
  53.                   // url for uploading file
  54.                   url: 'index.php?ajax-upload/&t=' + SECURITY_TOKEN
  55.             }, options);
  56.            
  57.             this._options.url = Core.convertLegacyUrl(this._options.url);
  58.             if (this._options.url.indexOf('index.php') === 0) {
  59.                   this._options.url = WSC_API_URL + this._options.url;
  60.             }
  61.            
  62.             this._buttonContainer = elById(buttonContainerId);
  63.             if (this._buttonContainer === null) {
  64.                   throw new Error("Element id '" + buttonContainerId + "' is unknown.");
  65.             }
  66.            
  67.             this._target = elById(targetId);
  68.             if (targetId === null) {
  69.                   throw new Error("Element id '" + targetId + "' is unknown.");
  70.             }
  71.             if (options.multiple && this._target.nodeName !== 'UL' && this._target.nodeName !== 'OL' && this._target.nodeName !== 'TBODY') {
  72.                   throw new Error("Target element has to be list or table body if uploading multiple files is supported.");
  73.             }
  74.            
  75.             this._fileElements = [];
  76.             this._internalFileId = 0;
  77.            
  78.             // upload ids that belong to an upload of multiple files at once
  79.             this._multiFileUploadIds = [];
  80.            
  81.             this._createButton();
  82.       }
  83.       Upload.prototype = {
  84.             /**
  85.              * Creates the upload button.
  86.              */
  87.             _createButton: function() {
  88.                   this._fileUpload = elCreate('input');
  89.                   elAttr(this._fileUpload, 'type', 'file');
  90.                   elAttr(this._fileUpload, 'name', this._options.name);
  91.                   if (this._options.multiple) {
  92.                         elAttr(this._fileUpload, 'multiple', 'true');
  93.                   }
  94.                   if (this._options.acceptableFiles !== null) {
  95.                         elAttr(this._fileUpload, 'accept', this._options.acceptableFiles.join(','));
  96.                   }
  97.                   this._fileUpload.addEventListener('change', this._upload.bind(this));
  98.                  
  99.                   this._button = elCreate('p');
  100.                   this._button.className = 'button uploadButton';
  101.                   elAttr(this._button, 'role', 'button');
  102.  
  103.                   this._fileUpload.addEventListener('focus', (function() {
  104.                         if (this._fileUpload.classList.contains('focus-visible')) {
  105.                               this._button.classList.add('active');
  106.                         }
  107.                   }).bind(this));
  108.                   this._fileUpload.addEventListener('blur', (function() { this._button.classList.remove('active'); }).bind(this));
  109.                  
  110.                   var span = elCreate('span');
  111.                   span.textContent = Language.get('wcf.global.button.upload');
  112.                   this._button.appendChild(span);
  113.                  
  114.                   DomUtil.prepend(this._fileUpload, this._button);
  115.                  
  116.                   this._insertButton();
  117.                  
  118.                   DomChangeListener.trigger();
  119.             },
  120.            
  121.             /**
  122.              * Creates the document element for an uploaded file.
  123.              *
  124.              * @param   {File}            file        uploaded file
  125.              * @return  {HTMLElement}
  126.              */
  127.             _createFileElement: function(file) {
  128.                   var progress = elCreate('progress');
  129.                   elAttr(progress, 'max', 100);
  130.                  
  131.                   if (this._target.nodeName === 'OL' || this._target.nodeName === 'UL') {
  132.                         var li = elCreate('li');
  133.                         li.innerText = file.name;
  134.                         li.appendChild(progress);
  135.                        
  136.                         this._target.appendChild(li);
  137.                        
  138.                         return li;
  139.                   }
  140.                   else if (this._target.nodeName === 'TBODY') {
  141.                         return this._createFileTableRow(file);
  142.                   }
  143.                   else {
  144.                         var p = elCreate('p');
  145.                         p.appendChild(progress);
  146.                        
  147.                         this._target.appendChild(p);
  148.                        
  149.                         return p;
  150.                   }
  151.             },
  152.            
  153.             /**
  154.              * Creates the document elements for uploaded files.
  155.              *
  156.              * @param   {(FileList|Array.<File>)}     files       uploaded files
  157.              */
  158.             _createFileElements: function(files) {
  159.                   if (files.length) {
  160.                         var uploadId = this._fileElements.length;
  161.                         this._fileElements[uploadId] = [];
  162.                        
  163.                         for (var i = 0, length = files.length; i < length; i++) {
  164.                               var file = files[i];
  165.                               var fileElement = this._createFileElement(file);
  166.                              
  167.                               if (!fileElement.classList.contains('uploadFailed')) {
  168.                                     elData(fileElement, 'filename', file.name);
  169.                                     elData(fileElement, 'internal-file-id', this._internalFileId++);
  170.                                     this._fileElements[uploadId][i] = fileElement;
  171.                               }
  172.                         }
  173.                        
  174.                         DomChangeListener.trigger();
  175.                        
  176.                         return uploadId;
  177.                   }
  178.                  
  179.                   return null;
  180.             },
  181.            
  182.             _createFileTableRow: function(file) {
  183.                   throw new Error("Has to be implemented in subclass.");
  184.             },
  185.            
  186.             /**
  187.              * Handles a failed file upload.
  188.              *
  189.              * @param   {int}             uploadId    identifier of a file upload
  190.              * @param   {object<string, *>}     data        response data
  191.              * @param   {string}          responseText      response
  192.              * @param   {XMLHttpRequest}  xhr         request object
  193.              * @param   {object<string, *>}     requestOptions    options used to send AJAX request
  194.              * @return  {boolean}   true if the error message should be shown
  195.              */
  196.             _failure: function(uploadId, data, responseText, xhr, requestOptions) {
  197.                   // does nothing
  198.                   return true;
  199.             },
  200.            
  201.             /**
  202.              * Return additional parameters for upload requests.
  203.              *
  204.              * @return  {object<string, *>}     additional parameters
  205.              */
  206.             _getParameters: function() {
  207.                   return {};
  208.             },
  209.            
  210.             /**
  211.              * Return additional form data for upload requests.
  212.              *
  213.              * @return  {object<string, *>}     additional form data
  214.              * @since       5.2
  215.              */
  216.             _getFormData: function() {
  217.                   return {};
  218.             },
  219.            
  220.             /**
  221.              * Inserts the created button to upload files into the button container.
  222.              */
  223.             _insertButton: function() {
  224.                   DomUtil.prepend(this._button, this._buttonContainer);
  225.             },
  226.            
  227.             /**
  228.              * Updates the progress of an upload.
  229.              *
  230.              * @param   {int}                   uploadId    internal upload identifier
  231.              * @param   {XMLHttpRequestProgressEvent} event       progress event object
  232.              */
  233.             _progress: function(uploadId, event) {
  234.                   var percentComplete = Math.round(event.loaded / event.total * 100);
  235.                  
  236.                   for (var i in this._fileElements[uploadId]) {
  237.                         var progress = elByTag('PROGRESS', this._fileElements[uploadId][i]);
  238.                         if (progress.length === 1) {
  239.                               elAttr(progress[0], 'value', percentComplete);
  240.                         }
  241.                   }
  242.             },
  243.            
  244.             /**
  245.              * Removes the button to upload files.
  246.              */
  247.             _removeButton: function() {
  248.                   elRemove(this._button);
  249.                  
  250.                   DomChangeListener.trigger();
  251.             },
  252.            
  253.             /**
  254.              * Handles a successful file upload.
  255.              *
  256.              * @param   {int}             uploadId    identifier of a file upload
  257.              * @param   {object<string, *>}     data        response data
  258.              * @param   {string}          responseText      response
  259.              * @param   {XMLHttpRequest}  xhr         request object
  260.              * @param   {object<string, *>}     requestOptions    options used to send AJAX request
  261.              */
  262.             _success: function(uploadId, data, responseText, xhr, requestOptions) {
  263.                   // does nothing
  264.             },
  265.            
  266.             /**
  267.              * File input change callback to upload files.
  268.              *
  269.              * @param   {Event}           event       input change event object
  270.              * @param   {File}            file        uploaded file
  271.              * @param   {Blob}            blob        file blob
  272.              * @return  {(int|Array.<int>|null)}      identifier(s) for the uploaded files
  273.              */
  274.             _upload: function(event, file, blob) {
  275.                   // remove failed upload elements first
  276.                   var failedUploads = DomTraverse.childrenByClass(this._target, 'uploadFailed');
  277.                   for (var i = 0, length = failedUploads.length; i < length; i++) {
  278.                         elRemove(failedUploads[i]);
  279.                   }
  280.                  
  281.                   var uploadId = null;
  282.                  
  283.                   var files = [];
  284.                   if (file) {
  285.                         files.push(file);
  286.                   }
  287.                   else if (blob) {
  288.                         var fileExtension = '';
  289.                         switch (blob.type) {
  290.                               case 'image/jpeg':
  291.                                     fileExtension = '.jpg';
  292.                               break;
  293.                              
  294.                               case 'image/gif':
  295.                                     fileExtension = '.gif';
  296.                               break;
  297.                              
  298.                               case 'image/png':
  299.                                     fileExtension = '.png';
  300.                               break;
  301.                         }
  302.                        
  303.                         files.push({
  304.                               name: 'pasted-from-clipboard' + fileExtension
  305.                         });
  306.                   }
  307.                   else {
  308.                         files = this._fileUpload.files;
  309.                   }
  310.                  
  311.                   if (files.length && this.validateUpload(files)) {
  312.                         if (this._options.singleFileRequests) {
  313.                               uploadId = [];
  314.                               for (var i = 0, length = files.length; i < length; i++) {
  315.                                     var localUploadId = this._uploadFiles([ files[i] ], blob);
  316.                                    
  317.                                     if (files.length !== 1) {
  318.                                           this._multiFileUploadIds.push(localUploadId)
  319.                                     }
  320.                                     uploadId.push(localUploadId);
  321.                               }
  322.                         }
  323.                         else {
  324.                               uploadId = this._uploadFiles(files, blob);
  325.                         }
  326.                   }
  327.                  
  328.                   // re-create upload button to effectively reset the 'files'
  329.                   // property of the input element
  330.                   this._removeButton();
  331.                   this._createButton();
  332.                  
  333.                   return uploadId;
  334.             },
  335.            
  336.             /**
  337.              * Validates the upload before uploading them.
  338.              *
  339.              * @param       {(FileList|Array.<File>)} files       uploaded files
  340.              * @return  {boolean}
  341.              * @since       5.2
  342.              */
  343.             validateUpload: function(files) {
  344.                   return true;
  345.             },
  346.            
  347.             /**
  348.              * Sends the request to upload files.
  349.              *
  350.              * @param   {(FileList|Array.<File>)}     files       uploaded files
  351.              * @param   {Blob}                        blob        file blob
  352.              * @return  {(int|null)}      identifier for the uploaded files
  353.              */
  354.             _uploadFiles: function(files, blob) {
  355.                   var uploadId = this._createFileElements(files);
  356.                  
  357.                   // no more files left, abort
  358.                   if (!this._fileElements[uploadId].length) {
  359.                         return null;
  360.                   }
  361.                  
  362.                   var formData = new FormData();
  363.                   for (var i = 0, length = files.length; i < length; i++) {
  364.                         if (this._fileElements[uploadId][i]) {
  365.                               var internalFileId = elData(this._fileElements[uploadId][i], 'internal-file-id');
  366.                              
  367.                               if (blob) {
  368.                                     formData.append('__files[' + internalFileId + ']', blob, files[i].name);
  369.                               }
  370.                               else {
  371.                                     formData.append('__files[' + internalFileId + ']', files[i]);
  372.                               }
  373.                         }
  374.                   }
  375.                  
  376.                   formData.append('actionName', this._options.action);
  377.                   formData.append('className', this._options.className);
  378.                   if (this._options.action === 'upload') {
  379.                         formData.append('interfaceName', 'wcf\\data\\IUploadAction');
  380.                   }
  381.                  
  382.                   // recursively append additional parameters to form data
  383.                   var appendFormData = function(parameters, prefix) {
  384.                         prefix = prefix || '';
  385.                        
  386.                         for (var name in parameters) {
  387.                               if (typeof parameters[name] === 'object') {
  388.                                     var newPrefix = prefix.length === 0 ? name : prefix + '[' + name + ']';
  389.                                     appendFormData(parameters[name], newPrefix);
  390.                               }
  391.                               else {
  392.                                     var dataName = prefix.length === 0 ? name : prefix + '[' + name + ']';
  393.                                     formData.append(dataName, parameters[name]);
  394.                               }
  395.                         }
  396.                   };
  397.                  
  398.                   appendFormData(this._getParameters(), 'parameters');
  399.                   appendFormData(this._getFormData());
  400.                  
  401.                   var request = new AjaxRequest({
  402.                         data: formData,
  403.                         contentType: false,
  404.                         failure: this._failure.bind(this, uploadId),
  405.                         silent: true,
  406.                         success: this._success.bind(this, uploadId),
  407.                         uploadProgress: this._progress.bind(this, uploadId),
  408.                         url: this._options.url,
  409.                         withCredentials: true
  410.                   });
  411.                   request.sendRequest();
  412.                  
  413.                   return uploadId;
  414.             },
  415.            
  416.             /**
  417.              * Returns true if there are any pending uploads handled by this
  418.              * upload manager.
  419.              *
  420.              * @return  {boolean}
  421.              * @since   5.2
  422.              */
  423.             hasPendingUploads: function() {
  424.                   for (var uploadId in this._fileElements) {
  425.                         for (var i in this._fileElements[uploadId]) {
  426.                               var progress = elByTag('PROGRESS', this._fileElements[uploadId][i]);
  427.                               if (progress.length === 1) {
  428.                                     return true;
  429.                               }
  430.                         }
  431.                   }
  432.                  
  433.                   return false;
  434.             },
  435.            
  436.             /**
  437.              * Uploads the given file blob.
  438.              *
  439.              * @param   {Blob}            blob        file blob
  440.              * @return  {int}       identifier for the uploaded file
  441.              */
  442.             uploadBlob: function(blob) {
  443.                   return this._upload(null, null, blob);
  444.             },
  445.            
  446.             /**
  447.              * Uploads the given file.
  448.              *
  449.              * @param   {File}            file        uploaded file
  450.              * @return  {int}       identifier(s) for the uploaded file
  451.              */
  452.             uploadFile: function(file) {
  453.                   return this._upload(null, file);
  454.             }
  455.       };
  456.      
  457.       return Upload;
  458. });

Raw Paste


Login or Register to edit or fork this paste. It's free.