github.com/apremalal/vamps-core@v1.0.1-0.20161221121535-d430b56ec174/server/webapps/app/base/plugins/jquery-file-upload/js/jquery.fileupload.js (about)

     1  /*
     2   * jQuery File Upload Plugin 5.42.0
     3   * https://github.com/blueimp/jQuery-File-Upload
     4   *
     5   * Copyright 2010, Sebastian Tschan
     6   * https://blueimp.net
     7   *
     8   * Licensed under the MIT license:
     9   * http://www.opensource.org/licenses/MIT
    10   */
    11  
    12  /* jshint nomen:false */
    13  /* global define, window, document, location, Blob, FormData */
    14  
    15  (function (factory) {
    16      'use strict';
    17      if (typeof define === 'function' && define.amd) {
    18          // Register as an anonymous AMD module:
    19          define([
    20              'jquery',
    21              'jquery.ui.widget'
    22          ], factory);
    23      } else {
    24          // Browser globals:
    25          factory(window.jQuery);
    26      }
    27  }(function ($) {
    28      'use strict';
    29  
    30      // Detect file input support, based on
    31      // http://viljamis.com/blog/2012/file-upload-support-on-mobile/
    32      $.support.fileInput = !(new RegExp(
    33          // Handle devices which give false positives for the feature detection:
    34          '(Android (1\\.[0156]|2\\.[01]))' +
    35              '|(Windows Phone (OS 7|8\\.0))|(XBLWP)|(ZuneWP)|(WPDesktop)' +
    36              '|(w(eb)?OSBrowser)|(webOS)' +
    37              '|(Kindle/(1\\.0|2\\.[05]|3\\.0))'
    38      ).test(window.navigator.userAgent) ||
    39          // Feature detection for all other devices:
    40          $('<input type="file">').prop('disabled'));
    41  
    42      // The FileReader API is not actually used, but works as feature detection,
    43      // as some Safari versions (5?) support XHR file uploads via the FormData API,
    44      // but not non-multipart XHR file uploads.
    45      // window.XMLHttpRequestUpload is not available on IE10, so we check for
    46      // window.ProgressEvent instead to detect XHR2 file upload capability:
    47      $.support.xhrFileUpload = !!(window.ProgressEvent && window.FileReader);
    48      $.support.xhrFormDataFileUpload = !!window.FormData;
    49  
    50      // Detect support for Blob slicing (required for chunked uploads):
    51      $.support.blobSlice = window.Blob && (Blob.prototype.slice ||
    52          Blob.prototype.webkitSlice || Blob.prototype.mozSlice);
    53  
    54      // Helper function to create drag handlers for dragover/dragenter/dragleave:
    55      function getDragHandler(type) {
    56          var isDragOver = type === 'dragover';
    57          return function (e) {
    58              e.dataTransfer = e.originalEvent && e.originalEvent.dataTransfer;
    59              var dataTransfer = e.dataTransfer;
    60              if (dataTransfer && $.inArray('Files', dataTransfer.types) !== -1 &&
    61                      this._trigger(
    62                          type,
    63                          $.Event(type, {delegatedEvent: e})
    64                      ) !== false) {
    65                  e.preventDefault();
    66                  if (isDragOver) {
    67                      dataTransfer.dropEffect = 'copy';
    68                  }
    69              }
    70          };
    71      }
    72  
    73      // The fileupload widget listens for change events on file input fields defined
    74      // via fileInput setting and paste or drop events of the given dropZone.
    75      // In addition to the default jQuery Widget methods, the fileupload widget
    76      // exposes the "add" and "send" methods, to add or directly send files using
    77      // the fileupload API.
    78      // By default, files added via file input selection, paste, drag & drop or
    79      // "add" method are uploaded immediately, but it is possible to override
    80      // the "add" callback option to queue file uploads.
    81      $.widget('blueimp.fileupload', {
    82  
    83          options: {
    84              // The drop target element(s), by the default the complete document.
    85              // Set to null to disable drag & drop support:
    86              dropZone: $(document),
    87              // The paste target element(s), by the default undefined.
    88              // Set to a DOM node or jQuery object to enable file pasting:
    89              pasteZone: undefined,
    90              // The file input field(s), that are listened to for change events.
    91              // If undefined, it is set to the file input fields inside
    92              // of the widget element on plugin initialization.
    93              // Set to null to disable the change listener.
    94              fileInput: undefined,
    95              // By default, the file input field is replaced with a clone after
    96              // each input field change event. This is required for iframe transport
    97              // queues and allows change events to be fired for the same file
    98              // selection, but can be disabled by setting the following option to false:
    99              replaceFileInput: true,
   100              // The parameter name for the file form data (the request argument name).
   101              // If undefined or empty, the name property of the file input field is
   102              // used, or "files[]" if the file input name property is also empty,
   103              // can be a string or an array of strings:
   104              paramName: undefined,
   105              // By default, each file of a selection is uploaded using an individual
   106              // request for XHR type uploads. Set to false to upload file
   107              // selections in one request each:
   108              singleFileUploads: true,
   109              // To limit the number of files uploaded with one XHR request,
   110              // set the following option to an integer greater than 0:
   111              limitMultiFileUploads: undefined,
   112              // The following option limits the number of files uploaded with one
   113              // XHR request to keep the request size under or equal to the defined
   114              // limit in bytes:
   115              limitMultiFileUploadSize: undefined,
   116              // Multipart file uploads add a number of bytes to each uploaded file,
   117              // therefore the following option adds an overhead for each file used
   118              // in the limitMultiFileUploadSize configuration:
   119              limitMultiFileUploadSizeOverhead: 512,
   120              // Set the following option to true to issue all file upload requests
   121              // in a sequential order:
   122              sequentialUploads: false,
   123              // To limit the number of concurrent uploads,
   124              // set the following option to an integer greater than 0:
   125              limitConcurrentUploads: undefined,
   126              // Set the following option to true to force iframe transport uploads:
   127              forceIframeTransport: false,
   128              // Set the following option to the location of a redirect url on the
   129              // origin server, for cross-domain iframe transport uploads:
   130              redirect: undefined,
   131              // The parameter name for the redirect url, sent as part of the form
   132              // data and set to 'redirect' if this option is empty:
   133              redirectParamName: undefined,
   134              // Set the following option to the location of a postMessage window,
   135              // to enable postMessage transport uploads:
   136              postMessage: undefined,
   137              // By default, XHR file uploads are sent as multipart/form-data.
   138              // The iframe transport is always using multipart/form-data.
   139              // Set to false to enable non-multipart XHR uploads:
   140              multipart: true,
   141              // To upload large files in smaller chunks, set the following option
   142              // to a preferred maximum chunk size. If set to 0, null or undefined,
   143              // or the browser does not support the required Blob API, files will
   144              // be uploaded as a whole.
   145              maxChunkSize: undefined,
   146              // When a non-multipart upload or a chunked multipart upload has been
   147              // aborted, this option can be used to resume the upload by setting
   148              // it to the size of the already uploaded bytes. This option is most
   149              // useful when modifying the options object inside of the "add" or
   150              // "send" callbacks, as the options are cloned for each file upload.
   151              uploadedBytes: undefined,
   152              // By default, failed (abort or error) file uploads are removed from the
   153              // global progress calculation. Set the following option to false to
   154              // prevent recalculating the global progress data:
   155              recalculateProgress: true,
   156              // Interval in milliseconds to calculate and trigger progress events:
   157              progressInterval: 100,
   158              // Interval in milliseconds to calculate progress bitrate:
   159              bitrateInterval: 500,
   160              // By default, uploads are started automatically when adding files:
   161              autoUpload: true,
   162  
   163              // Error and info messages:
   164              messages: {
   165                  uploadedBytes: 'Uploaded bytes exceed file size'
   166              },
   167  
   168              // Translation function, gets the message key to be translated
   169              // and an object with context specific data as arguments:
   170              i18n: function (message, context) {
   171                  message = this.messages[message] || message.toString();
   172                  if (context) {
   173                      $.each(context, function (key, value) {
   174                          message = message.replace('{' + key + '}', value);
   175                      });
   176                  }
   177                  return message;
   178              },
   179  
   180              // Additional form data to be sent along with the file uploads can be set
   181              // using this option, which accepts an array of objects with name and
   182              // value properties, a function returning such an array, a FormData
   183              // object (for XHR file uploads), or a simple object.
   184              // The form of the first fileInput is given as parameter to the function:
   185              formData: function (form) {
   186                  return form.serializeArray();
   187              },
   188  
   189              // The add callback is invoked as soon as files are added to the fileupload
   190              // widget (via file input selection, drag & drop, paste or add API call).
   191              // If the singleFileUploads option is enabled, this callback will be
   192              // called once for each file in the selection for XHR file uploads, else
   193              // once for each file selection.
   194              //
   195              // The upload starts when the submit method is invoked on the data parameter.
   196              // The data object contains a files property holding the added files
   197              // and allows you to override plugin options as well as define ajax settings.
   198              //
   199              // Listeners for this callback can also be bound the following way:
   200              // .bind('fileuploadadd', func);
   201              //
   202              // data.submit() returns a Promise object and allows to attach additional
   203              // handlers using jQuery's Deferred callbacks:
   204              // data.submit().done(func).fail(func).always(func);
   205              add: function (e, data) {
   206                  if (e.isDefaultPrevented()) {
   207                      return false;
   208                  }
   209                  if (data.autoUpload || (data.autoUpload !== false &&
   210                          $(this).fileupload('option', 'autoUpload'))) {
   211                      data.process().done(function () {
   212                          data.submit();
   213                      });
   214                  }
   215              },
   216  
   217              // Other callbacks:
   218  
   219              // Callback for the submit event of each file upload:
   220              // submit: function (e, data) {}, // .bind('fileuploadsubmit', func);
   221  
   222              // Callback for the start of each file upload request:
   223              // send: function (e, data) {}, // .bind('fileuploadsend', func);
   224  
   225              // Callback for successful uploads:
   226              // done: function (e, data) {}, // .bind('fileuploaddone', func);
   227  
   228              // Callback for failed (abort or error) uploads:
   229              // fail: function (e, data) {}, // .bind('fileuploadfail', func);
   230  
   231              // Callback for completed (success, abort or error) requests:
   232              // always: function (e, data) {}, // .bind('fileuploadalways', func);
   233  
   234              // Callback for upload progress events:
   235              // progress: function (e, data) {}, // .bind('fileuploadprogress', func);
   236  
   237              // Callback for global upload progress events:
   238              // progressall: function (e, data) {}, // .bind('fileuploadprogressall', func);
   239  
   240              // Callback for uploads start, equivalent to the global ajaxStart event:
   241              // start: function (e) {}, // .bind('fileuploadstart', func);
   242  
   243              // Callback for uploads stop, equivalent to the global ajaxStop event:
   244              // stop: function (e) {}, // .bind('fileuploadstop', func);
   245  
   246              // Callback for change events of the fileInput(s):
   247              // change: function (e, data) {}, // .bind('fileuploadchange', func);
   248  
   249              // Callback for paste events to the pasteZone(s):
   250              // paste: function (e, data) {}, // .bind('fileuploadpaste', func);
   251  
   252              // Callback for drop events of the dropZone(s):
   253              // drop: function (e, data) {}, // .bind('fileuploaddrop', func);
   254  
   255              // Callback for dragover events of the dropZone(s):
   256              // dragover: function (e) {}, // .bind('fileuploaddragover', func);
   257  
   258              // Callback for the start of each chunk upload request:
   259              // chunksend: function (e, data) {}, // .bind('fileuploadchunksend', func);
   260  
   261              // Callback for successful chunk uploads:
   262              // chunkdone: function (e, data) {}, // .bind('fileuploadchunkdone', func);
   263  
   264              // Callback for failed (abort or error) chunk uploads:
   265              // chunkfail: function (e, data) {}, // .bind('fileuploadchunkfail', func);
   266  
   267              // Callback for completed (success, abort or error) chunk upload requests:
   268              // chunkalways: function (e, data) {}, // .bind('fileuploadchunkalways', func);
   269  
   270              // The plugin options are used as settings object for the ajax calls.
   271              // The following are jQuery ajax settings required for the file uploads:
   272              processData: false,
   273              contentType: false,
   274              cache: false
   275          },
   276  
   277          // A list of options that require reinitializing event listeners and/or
   278          // special initialization code:
   279          _specialOptions: [
   280              'fileInput',
   281              'dropZone',
   282              'pasteZone',
   283              'multipart',
   284              'forceIframeTransport'
   285          ],
   286  
   287          _blobSlice: $.support.blobSlice && function () {
   288              var slice = this.slice || this.webkitSlice || this.mozSlice;
   289              return slice.apply(this, arguments);
   290          },
   291  
   292          _BitrateTimer: function () {
   293              this.timestamp = ((Date.now) ? Date.now() : (new Date()).getTime());
   294              this.loaded = 0;
   295              this.bitrate = 0;
   296              this.getBitrate = function (now, loaded, interval) {
   297                  var timeDiff = now - this.timestamp;
   298                  if (!this.bitrate || !interval || timeDiff > interval) {
   299                      this.bitrate = (loaded - this.loaded) * (1000 / timeDiff) * 8;
   300                      this.loaded = loaded;
   301                      this.timestamp = now;
   302                  }
   303                  return this.bitrate;
   304              };
   305          },
   306  
   307          _isXHRUpload: function (options) {
   308              return !options.forceIframeTransport &&
   309                  ((!options.multipart && $.support.xhrFileUpload) ||
   310                  $.support.xhrFormDataFileUpload);
   311          },
   312  
   313          _getFormData: function (options) {
   314              var formData;
   315              if ($.type(options.formData) === 'function') {
   316                  return options.formData(options.form);
   317              }
   318              if ($.isArray(options.formData)) {
   319                  return options.formData;
   320              }
   321              if ($.type(options.formData) === 'object') {
   322                  formData = [];
   323                  $.each(options.formData, function (name, value) {
   324                      formData.push({name: name, value: value});
   325                  });
   326                  return formData;
   327              }
   328              return [];
   329          },
   330  
   331          _getTotal: function (files) {
   332              var total = 0;
   333              $.each(files, function (index, file) {
   334                  total += file.size || 1;
   335              });
   336              return total;
   337          },
   338  
   339          _initProgressObject: function (obj) {
   340              var progress = {
   341                  loaded: 0,
   342                  total: 0,
   343                  bitrate: 0
   344              };
   345              if (obj._progress) {
   346                  $.extend(obj._progress, progress);
   347              } else {
   348                  obj._progress = progress;
   349              }
   350          },
   351  
   352          _initResponseObject: function (obj) {
   353              var prop;
   354              if (obj._response) {
   355                  for (prop in obj._response) {
   356                      if (obj._response.hasOwnProperty(prop)) {
   357                          delete obj._response[prop];
   358                      }
   359                  }
   360              } else {
   361                  obj._response = {};
   362              }
   363          },
   364  
   365          _onProgress: function (e, data) {
   366              if (e.lengthComputable) {
   367                  var now = ((Date.now) ? Date.now() : (new Date()).getTime()),
   368                      loaded;
   369                  if (data._time && data.progressInterval &&
   370                          (now - data._time < data.progressInterval) &&
   371                          e.loaded !== e.total) {
   372                      return;
   373                  }
   374                  data._time = now;
   375                  loaded = Math.floor(
   376                      e.loaded / e.total * (data.chunkSize || data._progress.total)
   377                  ) + (data.uploadedBytes || 0);
   378                  // Add the difference from the previously loaded state
   379                  // to the global loaded counter:
   380                  this._progress.loaded += (loaded - data._progress.loaded);
   381                  this._progress.bitrate = this._bitrateTimer.getBitrate(
   382                      now,
   383                      this._progress.loaded,
   384                      data.bitrateInterval
   385                  );
   386                  data._progress.loaded = data.loaded = loaded;
   387                  data._progress.bitrate = data.bitrate = data._bitrateTimer.getBitrate(
   388                      now,
   389                      loaded,
   390                      data.bitrateInterval
   391                  );
   392                  // Trigger a custom progress event with a total data property set
   393                  // to the file size(s) of the current upload and a loaded data
   394                  // property calculated accordingly:
   395                  this._trigger(
   396                      'progress',
   397                      $.Event('progress', {delegatedEvent: e}),
   398                      data
   399                  );
   400                  // Trigger a global progress event for all current file uploads,
   401                  // including ajax calls queued for sequential file uploads:
   402                  this._trigger(
   403                      'progressall',
   404                      $.Event('progressall', {delegatedEvent: e}),
   405                      this._progress
   406                  );
   407              }
   408          },
   409  
   410          _initProgressListener: function (options) {
   411              var that = this,
   412                  xhr = options.xhr ? options.xhr() : $.ajaxSettings.xhr();
   413              // Accesss to the native XHR object is required to add event listeners
   414              // for the upload progress event:
   415              if (xhr.upload) {
   416                  $(xhr.upload).bind('progress', function (e) {
   417                      var oe = e.originalEvent;
   418                      // Make sure the progress event properties get copied over:
   419                      e.lengthComputable = oe.lengthComputable;
   420                      e.loaded = oe.loaded;
   421                      e.total = oe.total;
   422                      that._onProgress(e, options);
   423                  });
   424                  options.xhr = function () {
   425                      return xhr;
   426                  };
   427              }
   428          },
   429  
   430          _isInstanceOf: function (type, obj) {
   431              // Cross-frame instanceof check
   432              return Object.prototype.toString.call(obj) === '[object ' + type + ']';
   433          },
   434  
   435          _initXHRData: function (options) {
   436              var that = this,
   437                  formData,
   438                  file = options.files[0],
   439                  // Ignore non-multipart setting if not supported:
   440                  multipart = options.multipart || !$.support.xhrFileUpload,
   441                  paramName = $.type(options.paramName) === 'array' ?
   442                      options.paramName[0] : options.paramName;
   443              options.headers = $.extend({}, options.headers);
   444              if (options.contentRange) {
   445                  options.headers['Content-Range'] = options.contentRange;
   446              }
   447              if (!multipart || options.blob || !this._isInstanceOf('File', file)) {
   448                  options.headers['Content-Disposition'] = 'attachment; filename="' +
   449                      encodeURI(file.name) + '"';
   450              }
   451              if (!multipart) {
   452                  options.contentType = file.type || 'application/octet-stream';
   453                  options.data = options.blob || file;
   454              } else if ($.support.xhrFormDataFileUpload) {
   455                  if (options.postMessage) {
   456                      // window.postMessage does not allow sending FormData
   457                      // objects, so we just add the File/Blob objects to
   458                      // the formData array and let the postMessage window
   459                      // create the FormData object out of this array:
   460                      formData = this._getFormData(options);
   461                      if (options.blob) {
   462                          formData.push({
   463                              name: paramName,
   464                              value: options.blob
   465                          });
   466                      } else {
   467                          $.each(options.files, function (index, file) {
   468                              formData.push({
   469                                  name: ($.type(options.paramName) === 'array' &&
   470                                      options.paramName[index]) || paramName,
   471                                  value: file
   472                              });
   473                          });
   474                      }
   475                  } else {
   476                      if (that._isInstanceOf('FormData', options.formData)) {
   477                          formData = options.formData;
   478                      } else {
   479                          formData = new FormData();
   480                          $.each(this._getFormData(options), function (index, field) {
   481                              formData.append(field.name, field.value);
   482                          });
   483                      }
   484                      if (options.blob) {
   485                          formData.append(paramName, options.blob, file.name);
   486                      } else {
   487                          $.each(options.files, function (index, file) {
   488                              // This check allows the tests to run with
   489                              // dummy objects:
   490                              if (that._isInstanceOf('File', file) ||
   491                                      that._isInstanceOf('Blob', file)) {
   492                                  formData.append(
   493                                      ($.type(options.paramName) === 'array' &&
   494                                          options.paramName[index]) || paramName,
   495                                      file,
   496                                      file.uploadName || file.name
   497                                  );
   498                              }
   499                          });
   500                      }
   501                  }
   502                  options.data = formData;
   503              }
   504              // Blob reference is not needed anymore, free memory:
   505              options.blob = null;
   506          },
   507  
   508          _initIframeSettings: function (options) {
   509              var targetHost = $('<a></a>').prop('href', options.url).prop('host');
   510              // Setting the dataType to iframe enables the iframe transport:
   511              options.dataType = 'iframe ' + (options.dataType || '');
   512              // The iframe transport accepts a serialized array as form data:
   513              options.formData = this._getFormData(options);
   514              // Add redirect url to form data on cross-domain uploads:
   515              if (options.redirect && targetHost && targetHost !== location.host) {
   516                  options.formData.push({
   517                      name: options.redirectParamName || 'redirect',
   518                      value: options.redirect
   519                  });
   520              }
   521          },
   522  
   523          _initDataSettings: function (options) {
   524              if (this._isXHRUpload(options)) {
   525                  if (!this._chunkedUpload(options, true)) {
   526                      if (!options.data) {
   527                          this._initXHRData(options);
   528                      }
   529                      this._initProgressListener(options);
   530                  }
   531                  if (options.postMessage) {
   532                      // Setting the dataType to postmessage enables the
   533                      // postMessage transport:
   534                      options.dataType = 'postmessage ' + (options.dataType || '');
   535                  }
   536              } else {
   537                  this._initIframeSettings(options);
   538              }
   539          },
   540  
   541          _getParamName: function (options) {
   542              var fileInput = $(options.fileInput),
   543                  paramName = options.paramName;
   544              if (!paramName) {
   545                  paramName = [];
   546                  fileInput.each(function () {
   547                      var input = $(this),
   548                          name = input.prop('name') || 'files[]',
   549                          i = (input.prop('files') || [1]).length;
   550                      while (i) {
   551                          paramName.push(name);
   552                          i -= 1;
   553                      }
   554                  });
   555                  if (!paramName.length) {
   556                      paramName = [fileInput.prop('name') || 'files[]'];
   557                  }
   558              } else if (!$.isArray(paramName)) {
   559                  paramName = [paramName];
   560              }
   561              return paramName;
   562          },
   563  
   564          _initFormSettings: function (options) {
   565              // Retrieve missing options from the input field and the
   566              // associated form, if available:
   567              if (!options.form || !options.form.length) {
   568                  options.form = $(options.fileInput.prop('form'));
   569                  // If the given file input doesn't have an associated form,
   570                  // use the default widget file input's form:
   571                  if (!options.form.length) {
   572                      options.form = $(this.options.fileInput.prop('form'));
   573                  }
   574              }
   575              options.paramName = this._getParamName(options);
   576              if (!options.url) {
   577                  options.url = options.form.prop('action') || location.href;
   578              }
   579              // The HTTP request method must be "POST" or "PUT":
   580              options.type = (options.type ||
   581                  ($.type(options.form.prop('method')) === 'string' &&
   582                      options.form.prop('method')) || ''
   583                  ).toUpperCase();
   584              if (options.type !== 'POST' && options.type !== 'PUT' &&
   585                      options.type !== 'PATCH') {
   586                  options.type = 'POST';
   587              }
   588              if (!options.formAcceptCharset) {
   589                  options.formAcceptCharset = options.form.attr('accept-charset');
   590              }
   591          },
   592  
   593          _getAJAXSettings: function (data) {
   594              var options = $.extend({}, this.options, data);
   595              this._initFormSettings(options);
   596              this._initDataSettings(options);
   597              return options;
   598          },
   599  
   600          // jQuery 1.6 doesn't provide .state(),
   601          // while jQuery 1.8+ removed .isRejected() and .isResolved():
   602          _getDeferredState: function (deferred) {
   603              if (deferred.state) {
   604                  return deferred.state();
   605              }
   606              if (deferred.isResolved()) {
   607                  return 'resolved';
   608              }
   609              if (deferred.isRejected()) {
   610                  return 'rejected';
   611              }
   612              return 'pending';
   613          },
   614  
   615          // Maps jqXHR callbacks to the equivalent
   616          // methods of the given Promise object:
   617          _enhancePromise: function (promise) {
   618              promise.success = promise.done;
   619              promise.error = promise.fail;
   620              promise.complete = promise.always;
   621              return promise;
   622          },
   623  
   624          // Creates and returns a Promise object enhanced with
   625          // the jqXHR methods abort, success, error and complete:
   626          _getXHRPromise: function (resolveOrReject, context, args) {
   627              var dfd = $.Deferred(),
   628                  promise = dfd.promise();
   629              context = context || this.options.context || promise;
   630              if (resolveOrReject === true) {
   631                  dfd.resolveWith(context, args);
   632              } else if (resolveOrReject === false) {
   633                  dfd.rejectWith(context, args);
   634              }
   635              promise.abort = dfd.promise;
   636              return this._enhancePromise(promise);
   637          },
   638  
   639          // Adds convenience methods to the data callback argument:
   640          _addConvenienceMethods: function (e, data) {
   641              var that = this,
   642                  getPromise = function (args) {
   643                      return $.Deferred().resolveWith(that, args).promise();
   644                  };
   645              data.process = function (resolveFunc, rejectFunc) {
   646                  if (resolveFunc || rejectFunc) {
   647                      data._processQueue = this._processQueue =
   648                          (this._processQueue || getPromise([this])).pipe(
   649                              function () {
   650                                  if (data.errorThrown) {
   651                                      return $.Deferred()
   652                                          .rejectWith(that, [data]).promise();
   653                                  }
   654                                  return getPromise(arguments);
   655                              }
   656                          ).pipe(resolveFunc, rejectFunc);
   657                  }
   658                  return this._processQueue || getPromise([this]);
   659              };
   660              data.submit = function () {
   661                  if (this.state() !== 'pending') {
   662                      data.jqXHR = this.jqXHR =
   663                          (that._trigger(
   664                              'submit',
   665                              $.Event('submit', {delegatedEvent: e}),
   666                              this
   667                          ) !== false) && that._onSend(e, this);
   668                  }
   669                  return this.jqXHR || that._getXHRPromise();
   670              };
   671              data.abort = function () {
   672                  if (this.jqXHR) {
   673                      return this.jqXHR.abort();
   674                  }
   675                  this.errorThrown = 'abort';
   676                  that._trigger('fail', null, this);
   677                  return that._getXHRPromise(false);
   678              };
   679              data.state = function () {
   680                  if (this.jqXHR) {
   681                      return that._getDeferredState(this.jqXHR);
   682                  }
   683                  if (this._processQueue) {
   684                      return that._getDeferredState(this._processQueue);
   685                  }
   686              };
   687              data.processing = function () {
   688                  return !this.jqXHR && this._processQueue && that
   689                      ._getDeferredState(this._processQueue) === 'pending';
   690              };
   691              data.progress = function () {
   692                  return this._progress;
   693              };
   694              data.response = function () {
   695                  return this._response;
   696              };
   697          },
   698  
   699          // Parses the Range header from the server response
   700          // and returns the uploaded bytes:
   701          _getUploadedBytes: function (jqXHR) {
   702              var range = jqXHR.getResponseHeader('Range'),
   703                  parts = range && range.split('-'),
   704                  upperBytesPos = parts && parts.length > 1 &&
   705                      parseInt(parts[1], 10);
   706              return upperBytesPos && upperBytesPos + 1;
   707          },
   708  
   709          // Uploads a file in multiple, sequential requests
   710          // by splitting the file up in multiple blob chunks.
   711          // If the second parameter is true, only tests if the file
   712          // should be uploaded in chunks, but does not invoke any
   713          // upload requests:
   714          _chunkedUpload: function (options, testOnly) {
   715              options.uploadedBytes = options.uploadedBytes || 0;
   716              var that = this,
   717                  file = options.files[0],
   718                  fs = file.size,
   719                  ub = options.uploadedBytes,
   720                  mcs = options.maxChunkSize || fs,
   721                  slice = this._blobSlice,
   722                  dfd = $.Deferred(),
   723                  promise = dfd.promise(),
   724                  jqXHR,
   725                  upload;
   726              if (!(this._isXHRUpload(options) && slice && (ub || mcs < fs)) ||
   727                      options.data) {
   728                  return false;
   729              }
   730              if (testOnly) {
   731                  return true;
   732              }
   733              if (ub >= fs) {
   734                  file.error = options.i18n('uploadedBytes');
   735                  return this._getXHRPromise(
   736                      false,
   737                      options.context,
   738                      [null, 'error', file.error]
   739                  );
   740              }
   741              // The chunk upload method:
   742              upload = function () {
   743                  // Clone the options object for each chunk upload:
   744                  var o = $.extend({}, options),
   745                      currentLoaded = o._progress.loaded;
   746                  o.blob = slice.call(
   747                      file,
   748                      ub,
   749                      ub + mcs,
   750                      file.type
   751                  );
   752                  // Store the current chunk size, as the blob itself
   753                  // will be dereferenced after data processing:
   754                  o.chunkSize = o.blob.size;
   755                  // Expose the chunk bytes position range:
   756                  o.contentRange = 'bytes ' + ub + '-' +
   757                      (ub + o.chunkSize - 1) + '/' + fs;
   758                  // Process the upload data (the blob and potential form data):
   759                  that._initXHRData(o);
   760                  // Add progress listeners for this chunk upload:
   761                  that._initProgressListener(o);
   762                  jqXHR = ((that._trigger('chunksend', null, o) !== false && $.ajax(o)) ||
   763                          that._getXHRPromise(false, o.context))
   764                      .done(function (result, textStatus, jqXHR) {
   765                          ub = that._getUploadedBytes(jqXHR) ||
   766                              (ub + o.chunkSize);
   767                          // Create a progress event if no final progress event
   768                          // with loaded equaling total has been triggered
   769                          // for this chunk:
   770                          if (currentLoaded + o.chunkSize - o._progress.loaded) {
   771                              that._onProgress($.Event('progress', {
   772                                  lengthComputable: true,
   773                                  loaded: ub - o.uploadedBytes,
   774                                  total: ub - o.uploadedBytes
   775                              }), o);
   776                          }
   777                          options.uploadedBytes = o.uploadedBytes = ub;
   778                          o.result = result;
   779                          o.textStatus = textStatus;
   780                          o.jqXHR = jqXHR;
   781                          that._trigger('chunkdone', null, o);
   782                          that._trigger('chunkalways', null, o);
   783                          if (ub < fs) {
   784                              // File upload not yet complete,
   785                              // continue with the next chunk:
   786                              upload();
   787                          } else {
   788                              dfd.resolveWith(
   789                                  o.context,
   790                                  [result, textStatus, jqXHR]
   791                              );
   792                          }
   793                      })
   794                      .fail(function (jqXHR, textStatus, errorThrown) {
   795                          o.jqXHR = jqXHR;
   796                          o.textStatus = textStatus;
   797                          o.errorThrown = errorThrown;
   798                          that._trigger('chunkfail', null, o);
   799                          that._trigger('chunkalways', null, o);
   800                          dfd.rejectWith(
   801                              o.context,
   802                              [jqXHR, textStatus, errorThrown]
   803                          );
   804                      });
   805              };
   806              this._enhancePromise(promise);
   807              promise.abort = function () {
   808                  return jqXHR.abort();
   809              };
   810              upload();
   811              return promise;
   812          },
   813  
   814          _beforeSend: function (e, data) {
   815              if (this._active === 0) {
   816                  // the start callback is triggered when an upload starts
   817                  // and no other uploads are currently running,
   818                  // equivalent to the global ajaxStart event:
   819                  this._trigger('start');
   820                  // Set timer for global bitrate progress calculation:
   821                  this._bitrateTimer = new this._BitrateTimer();
   822                  // Reset the global progress values:
   823                  this._progress.loaded = this._progress.total = 0;
   824                  this._progress.bitrate = 0;
   825              }
   826              // Make sure the container objects for the .response() and
   827              // .progress() methods on the data object are available
   828              // and reset to their initial state:
   829              this._initResponseObject(data);
   830              this._initProgressObject(data);
   831              data._progress.loaded = data.loaded = data.uploadedBytes || 0;
   832              data._progress.total = data.total = this._getTotal(data.files) || 1;
   833              data._progress.bitrate = data.bitrate = 0;
   834              this._active += 1;
   835              // Initialize the global progress values:
   836              this._progress.loaded += data.loaded;
   837              this._progress.total += data.total;
   838          },
   839  
   840          _onDone: function (result, textStatus, jqXHR, options) {
   841              var total = options._progress.total,
   842                  response = options._response;
   843              if (options._progress.loaded < total) {
   844                  // Create a progress event if no final progress event
   845                  // with loaded equaling total has been triggered:
   846                  this._onProgress($.Event('progress', {
   847                      lengthComputable: true,
   848                      loaded: total,
   849                      total: total
   850                  }), options);
   851              }
   852              response.result = options.result = result;
   853              response.textStatus = options.textStatus = textStatus;
   854              response.jqXHR = options.jqXHR = jqXHR;
   855              this._trigger('done', null, options);
   856          },
   857  
   858          _onFail: function (jqXHR, textStatus, errorThrown, options) {
   859              var response = options._response;
   860              if (options.recalculateProgress) {
   861                  // Remove the failed (error or abort) file upload from
   862                  // the global progress calculation:
   863                  this._progress.loaded -= options._progress.loaded;
   864                  this._progress.total -= options._progress.total;
   865              }
   866              response.jqXHR = options.jqXHR = jqXHR;
   867              response.textStatus = options.textStatus = textStatus;
   868              response.errorThrown = options.errorThrown = errorThrown;
   869              this._trigger('fail', null, options);
   870          },
   871  
   872          _onAlways: function (jqXHRorResult, textStatus, jqXHRorError, options) {
   873              // jqXHRorResult, textStatus and jqXHRorError are added to the
   874              // options object via done and fail callbacks
   875              this._trigger('always', null, options);
   876          },
   877  
   878          _onSend: function (e, data) {
   879              if (!data.submit) {
   880                  this._addConvenienceMethods(e, data);
   881              }
   882              var that = this,
   883                  jqXHR,
   884                  aborted,
   885                  slot,
   886                  pipe,
   887                  options = that._getAJAXSettings(data),
   888                  send = function () {
   889                      that._sending += 1;
   890                      // Set timer for bitrate progress calculation:
   891                      options._bitrateTimer = new that._BitrateTimer();
   892                      jqXHR = jqXHR || (
   893                          ((aborted || that._trigger(
   894                              'send',
   895                              $.Event('send', {delegatedEvent: e}),
   896                              options
   897                          ) === false) &&
   898                          that._getXHRPromise(false, options.context, aborted)) ||
   899                          that._chunkedUpload(options) || $.ajax(options)
   900                      ).done(function (result, textStatus, jqXHR) {
   901                          that._onDone(result, textStatus, jqXHR, options);
   902                      }).fail(function (jqXHR, textStatus, errorThrown) {
   903                          that._onFail(jqXHR, textStatus, errorThrown, options);
   904                      }).always(function (jqXHRorResult, textStatus, jqXHRorError) {
   905                          that._onAlways(
   906                              jqXHRorResult,
   907                              textStatus,
   908                              jqXHRorError,
   909                              options
   910                          );
   911                          that._sending -= 1;
   912                          that._active -= 1;
   913                          if (options.limitConcurrentUploads &&
   914                                  options.limitConcurrentUploads > that._sending) {
   915                              // Start the next queued upload,
   916                              // that has not been aborted:
   917                              var nextSlot = that._slots.shift();
   918                              while (nextSlot) {
   919                                  if (that._getDeferredState(nextSlot) === 'pending') {
   920                                      nextSlot.resolve();
   921                                      break;
   922                                  }
   923                                  nextSlot = that._slots.shift();
   924                              }
   925                          }
   926                          if (that._active === 0) {
   927                              // The stop callback is triggered when all uploads have
   928                              // been completed, equivalent to the global ajaxStop event:
   929                              that._trigger('stop');
   930                          }
   931                      });
   932                      return jqXHR;
   933                  };
   934              this._beforeSend(e, options);
   935              if (this.options.sequentialUploads ||
   936                      (this.options.limitConcurrentUploads &&
   937                      this.options.limitConcurrentUploads <= this._sending)) {
   938                  if (this.options.limitConcurrentUploads > 1) {
   939                      slot = $.Deferred();
   940                      this._slots.push(slot);
   941                      pipe = slot.pipe(send);
   942                  } else {
   943                      this._sequence = this._sequence.pipe(send, send);
   944                      pipe = this._sequence;
   945                  }
   946                  // Return the piped Promise object, enhanced with an abort method,
   947                  // which is delegated to the jqXHR object of the current upload,
   948                  // and jqXHR callbacks mapped to the equivalent Promise methods:
   949                  pipe.abort = function () {
   950                      aborted = [undefined, 'abort', 'abort'];
   951                      if (!jqXHR) {
   952                          if (slot) {
   953                              slot.rejectWith(options.context, aborted);
   954                          }
   955                          return send();
   956                      }
   957                      return jqXHR.abort();
   958                  };
   959                  return this._enhancePromise(pipe);
   960              }
   961              return send();
   962          },
   963  
   964          _onAdd: function (e, data) {
   965              var that = this,
   966                  result = true,
   967                  options = $.extend({}, this.options, data),
   968                  files = data.files,
   969                  filesLength = files.length,
   970                  limit = options.limitMultiFileUploads,
   971                  limitSize = options.limitMultiFileUploadSize,
   972                  overhead = options.limitMultiFileUploadSizeOverhead,
   973                  batchSize = 0,
   974                  paramName = this._getParamName(options),
   975                  paramNameSet,
   976                  paramNameSlice,
   977                  fileSet,
   978                  i,
   979                  j = 0;
   980              if (limitSize && (!filesLength || files[0].size === undefined)) {
   981                  limitSize = undefined;
   982              }
   983              if (!(options.singleFileUploads || limit || limitSize) ||
   984                      !this._isXHRUpload(options)) {
   985                  fileSet = [files];
   986                  paramNameSet = [paramName];
   987              } else if (!(options.singleFileUploads || limitSize) && limit) {
   988                  fileSet = [];
   989                  paramNameSet = [];
   990                  for (i = 0; i < filesLength; i += limit) {
   991                      fileSet.push(files.slice(i, i + limit));
   992                      paramNameSlice = paramName.slice(i, i + limit);
   993                      if (!paramNameSlice.length) {
   994                          paramNameSlice = paramName;
   995                      }
   996                      paramNameSet.push(paramNameSlice);
   997                  }
   998              } else if (!options.singleFileUploads && limitSize) {
   999                  fileSet = [];
  1000                  paramNameSet = [];
  1001                  for (i = 0; i < filesLength; i = i + 1) {
  1002                      batchSize += files[i].size + overhead;
  1003                      if (i + 1 === filesLength ||
  1004                              ((batchSize + files[i + 1].size + overhead) > limitSize) ||
  1005                              (limit && i + 1 - j >= limit)) {
  1006                          fileSet.push(files.slice(j, i + 1));
  1007                          paramNameSlice = paramName.slice(j, i + 1);
  1008                          if (!paramNameSlice.length) {
  1009                              paramNameSlice = paramName;
  1010                          }
  1011                          paramNameSet.push(paramNameSlice);
  1012                          j = i + 1;
  1013                          batchSize = 0;
  1014                      }
  1015                  }
  1016              } else {
  1017                  paramNameSet = paramName;
  1018              }
  1019              data.originalFiles = files;
  1020              $.each(fileSet || files, function (index, element) {
  1021                  var newData = $.extend({}, data);
  1022                  newData.files = fileSet ? element : [element];
  1023                  newData.paramName = paramNameSet[index];
  1024                  that._initResponseObject(newData);
  1025                  that._initProgressObject(newData);
  1026                  that._addConvenienceMethods(e, newData);
  1027                  result = that._trigger(
  1028                      'add',
  1029                      $.Event('add', {delegatedEvent: e}),
  1030                      newData
  1031                  );
  1032                  return result;
  1033              });
  1034              return result;
  1035          },
  1036  
  1037          _replaceFileInput: function (data) {
  1038              var input = data.fileInput,
  1039                  inputClone = input.clone(true);
  1040              // Add a reference for the new cloned file input to the data argument:
  1041              data.fileInputClone = inputClone;
  1042              $('<form></form>').append(inputClone)[0].reset();
  1043              // Detaching allows to insert the fileInput on another form
  1044              // without loosing the file input value:
  1045              input.after(inputClone).detach();
  1046              // Avoid memory leaks with the detached file input:
  1047              $.cleanData(input.unbind('remove'));
  1048              // Replace the original file input element in the fileInput
  1049              // elements set with the clone, which has been copied including
  1050              // event handlers:
  1051              this.options.fileInput = this.options.fileInput.map(function (i, el) {
  1052                  if (el === input[0]) {
  1053                      return inputClone[0];
  1054                  }
  1055                  return el;
  1056              });
  1057              // If the widget has been initialized on the file input itself,
  1058              // override this.element with the file input clone:
  1059              if (input[0] === this.element[0]) {
  1060                  this.element = inputClone;
  1061              }
  1062          },
  1063  
  1064          _handleFileTreeEntry: function (entry, path) {
  1065              var that = this,
  1066                  dfd = $.Deferred(),
  1067                  errorHandler = function (e) {
  1068                      if (e && !e.entry) {
  1069                          e.entry = entry;
  1070                      }
  1071                      // Since $.when returns immediately if one
  1072                      // Deferred is rejected, we use resolve instead.
  1073                      // This allows valid files and invalid items
  1074                      // to be returned together in one set:
  1075                      dfd.resolve([e]);
  1076                  },
  1077                  successHandler = function (entries) {
  1078                      that._handleFileTreeEntries(
  1079                          entries,
  1080                          path + entry.name + '/'
  1081                      ).done(function (files) {
  1082                          dfd.resolve(files);
  1083                      }).fail(errorHandler);
  1084                  },
  1085                  readEntries = function () {
  1086                      dirReader.readEntries(function (results) {
  1087                          if (!results.length) {
  1088                              successHandler(entries);
  1089                          } else {
  1090                              entries = entries.concat(results);
  1091                              readEntries();
  1092                          }
  1093                      }, errorHandler);
  1094                  },
  1095                  dirReader, entries = [];
  1096              path = path || '';
  1097              if (entry.isFile) {
  1098                  if (entry._file) {
  1099                      // Workaround for Chrome bug #149735
  1100                      entry._file.relativePath = path;
  1101                      dfd.resolve(entry._file);
  1102                  } else {
  1103                      entry.file(function (file) {
  1104                          file.relativePath = path;
  1105                          dfd.resolve(file);
  1106                      }, errorHandler);
  1107                  }
  1108              } else if (entry.isDirectory) {
  1109                  dirReader = entry.createReader();
  1110                  readEntries();
  1111              } else {
  1112                  // Return an empy list for file system items
  1113                  // other than files or directories:
  1114                  dfd.resolve([]);
  1115              }
  1116              return dfd.promise();
  1117          },
  1118  
  1119          _handleFileTreeEntries: function (entries, path) {
  1120              var that = this;
  1121              return $.when.apply(
  1122                  $,
  1123                  $.map(entries, function (entry) {
  1124                      return that._handleFileTreeEntry(entry, path);
  1125                  })
  1126              ).pipe(function () {
  1127                  return Array.prototype.concat.apply(
  1128                      [],
  1129                      arguments
  1130                  );
  1131              });
  1132          },
  1133  
  1134          _getDroppedFiles: function (dataTransfer) {
  1135              dataTransfer = dataTransfer || {};
  1136              var items = dataTransfer.items;
  1137              if (items && items.length && (items[0].webkitGetAsEntry ||
  1138                      items[0].getAsEntry)) {
  1139                  return this._handleFileTreeEntries(
  1140                      $.map(items, function (item) {
  1141                          var entry;
  1142                          if (item.webkitGetAsEntry) {
  1143                              entry = item.webkitGetAsEntry();
  1144                              if (entry) {
  1145                                  // Workaround for Chrome bug #149735:
  1146                                  entry._file = item.getAsFile();
  1147                              }
  1148                              return entry;
  1149                          }
  1150                          return item.getAsEntry();
  1151                      })
  1152                  );
  1153              }
  1154              return $.Deferred().resolve(
  1155                  $.makeArray(dataTransfer.files)
  1156              ).promise();
  1157          },
  1158  
  1159          _getSingleFileInputFiles: function (fileInput) {
  1160              fileInput = $(fileInput);
  1161              var entries = fileInput.prop('webkitEntries') ||
  1162                      fileInput.prop('entries'),
  1163                  files,
  1164                  value;
  1165              if (entries && entries.length) {
  1166                  return this._handleFileTreeEntries(entries);
  1167              }
  1168              files = $.makeArray(fileInput.prop('files'));
  1169              if (!files.length) {
  1170                  value = fileInput.prop('value');
  1171                  if (!value) {
  1172                      return $.Deferred().resolve([]).promise();
  1173                  }
  1174                  // If the files property is not available, the browser does not
  1175                  // support the File API and we add a pseudo File object with
  1176                  // the input value as name with path information removed:
  1177                  files = [{name: value.replace(/^.*\\/, '')}];
  1178              } else if (files[0].name === undefined && files[0].fileName) {
  1179                  // File normalization for Safari 4 and Firefox 3:
  1180                  $.each(files, function (index, file) {
  1181                      file.name = file.fileName;
  1182                      file.size = file.fileSize;
  1183                  });
  1184              }
  1185              return $.Deferred().resolve(files).promise();
  1186          },
  1187  
  1188          _getFileInputFiles: function (fileInput) {
  1189              if (!(fileInput instanceof $) || fileInput.length === 1) {
  1190                  return this._getSingleFileInputFiles(fileInput);
  1191              }
  1192              return $.when.apply(
  1193                  $,
  1194                  $.map(fileInput, this._getSingleFileInputFiles)
  1195              ).pipe(function () {
  1196                  return Array.prototype.concat.apply(
  1197                      [],
  1198                      arguments
  1199                  );
  1200              });
  1201          },
  1202  
  1203          _onChange: function (e) {
  1204              var that = this,
  1205                  data = {
  1206                      fileInput: $(e.target),
  1207                      form: $(e.target.form)
  1208                  };
  1209              this._getFileInputFiles(data.fileInput).always(function (files) {
  1210                  data.files = files;
  1211                  if (that.options.replaceFileInput) {
  1212                      that._replaceFileInput(data);
  1213                  }
  1214                  if (that._trigger(
  1215                          'change',
  1216                          $.Event('change', {delegatedEvent: e}),
  1217                          data
  1218                      ) !== false) {
  1219                      that._onAdd(e, data);
  1220                  }
  1221              });
  1222          },
  1223  
  1224          _onPaste: function (e) {
  1225              var items = e.originalEvent && e.originalEvent.clipboardData &&
  1226                      e.originalEvent.clipboardData.items,
  1227                  data = {files: []};
  1228              if (items && items.length) {
  1229                  $.each(items, function (index, item) {
  1230                      var file = item.getAsFile && item.getAsFile();
  1231                      if (file) {
  1232                          data.files.push(file);
  1233                      }
  1234                  });
  1235                  if (this._trigger(
  1236                          'paste',
  1237                          $.Event('paste', {delegatedEvent: e}),
  1238                          data
  1239                      ) !== false) {
  1240                      this._onAdd(e, data);
  1241                  }
  1242              }
  1243          },
  1244  
  1245          _onDrop: function (e) {
  1246              e.dataTransfer = e.originalEvent && e.originalEvent.dataTransfer;
  1247              var that = this,
  1248                  dataTransfer = e.dataTransfer,
  1249                  data = {};
  1250              if (dataTransfer && dataTransfer.files && dataTransfer.files.length) {
  1251                  e.preventDefault();
  1252                  this._getDroppedFiles(dataTransfer).always(function (files) {
  1253                      data.files = files;
  1254                      if (that._trigger(
  1255                              'drop',
  1256                              $.Event('drop', {delegatedEvent: e}),
  1257                              data
  1258                          ) !== false) {
  1259                          that._onAdd(e, data);
  1260                      }
  1261                  });
  1262              }
  1263          },
  1264  
  1265          _onDragOver: getDragHandler('dragover'),
  1266  
  1267          _onDragEnter: getDragHandler('dragenter'),
  1268  
  1269          _onDragLeave: getDragHandler('dragleave'),
  1270  
  1271          _initEventHandlers: function () {
  1272              if (this._isXHRUpload(this.options)) {
  1273                  this._on(this.options.dropZone, {
  1274                      dragover: this._onDragOver,
  1275                      drop: this._onDrop,
  1276                      // event.preventDefault() on dragenter is required for IE10+:
  1277                      dragenter: this._onDragEnter,
  1278                      // dragleave is not required, but added for completeness:
  1279                      dragleave: this._onDragLeave
  1280                  });
  1281                  this._on(this.options.pasteZone, {
  1282                      paste: this._onPaste
  1283                  });
  1284              }
  1285              if ($.support.fileInput) {
  1286                  this._on(this.options.fileInput, {
  1287                      change: this._onChange
  1288                  });
  1289              }
  1290          },
  1291  
  1292          _destroyEventHandlers: function () {
  1293              this._off(this.options.dropZone, 'dragenter dragleave dragover drop');
  1294              this._off(this.options.pasteZone, 'paste');
  1295              this._off(this.options.fileInput, 'change');
  1296          },
  1297  
  1298          _setOption: function (key, value) {
  1299              var reinit = $.inArray(key, this._specialOptions) !== -1;
  1300              if (reinit) {
  1301                  this._destroyEventHandlers();
  1302              }
  1303              this._super(key, value);
  1304              if (reinit) {
  1305                  this._initSpecialOptions();
  1306                  this._initEventHandlers();
  1307              }
  1308          },
  1309  
  1310          _initSpecialOptions: function () {
  1311              var options = this.options;
  1312              if (options.fileInput === undefined) {
  1313                  options.fileInput = this.element.is('input[type="file"]') ?
  1314                          this.element : this.element.find('input[type="file"]');
  1315              } else if (!(options.fileInput instanceof $)) {
  1316                  options.fileInput = $(options.fileInput);
  1317              }
  1318              if (!(options.dropZone instanceof $)) {
  1319                  options.dropZone = $(options.dropZone);
  1320              }
  1321              if (!(options.pasteZone instanceof $)) {
  1322                  options.pasteZone = $(options.pasteZone);
  1323              }
  1324          },
  1325  
  1326          _getRegExp: function (str) {
  1327              var parts = str.split('/'),
  1328                  modifiers = parts.pop();
  1329              parts.shift();
  1330              return new RegExp(parts.join('/'), modifiers);
  1331          },
  1332  
  1333          _isRegExpOption: function (key, value) {
  1334              return key !== 'url' && $.type(value) === 'string' &&
  1335                  /^\/.*\/[igm]{0,3}$/.test(value);
  1336          },
  1337  
  1338          _initDataAttributes: function () {
  1339              var that = this,
  1340                  options = this.options,
  1341                  clone = $(this.element[0].cloneNode(false));
  1342              // Initialize options set via HTML5 data-attributes:
  1343              $.each(
  1344                  clone.data(),
  1345                  function (key, value) {
  1346                      var dataAttributeName = 'data-' +
  1347                          // Convert camelCase to hyphen-ated key:
  1348                          key.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
  1349                      if (clone.attr(dataAttributeName)) {
  1350                          if (that._isRegExpOption(key, value)) {
  1351                              value = that._getRegExp(value);
  1352                          }
  1353                          options[key] = value;
  1354                      }
  1355                  }
  1356              );
  1357          },
  1358  
  1359          _create: function () {
  1360              this._initDataAttributes();
  1361              this._initSpecialOptions();
  1362              this._slots = [];
  1363              this._sequence = this._getXHRPromise(true);
  1364              this._sending = this._active = 0;
  1365              this._initProgressObject(this);
  1366              this._initEventHandlers();
  1367          },
  1368  
  1369          // This method is exposed to the widget API and allows to query
  1370          // the number of active uploads:
  1371          active: function () {
  1372              return this._active;
  1373          },
  1374  
  1375          // This method is exposed to the widget API and allows to query
  1376          // the widget upload progress.
  1377          // It returns an object with loaded, total and bitrate properties
  1378          // for the running uploads:
  1379          progress: function () {
  1380              return this._progress;
  1381          },
  1382  
  1383          // This method is exposed to the widget API and allows adding files
  1384          // using the fileupload API. The data parameter accepts an object which
  1385          // must have a files property and can contain additional options:
  1386          // .fileupload('add', {files: filesList});
  1387          add: function (data) {
  1388              var that = this;
  1389              if (!data || this.options.disabled) {
  1390                  return;
  1391              }
  1392              if (data.fileInput && !data.files) {
  1393                  this._getFileInputFiles(data.fileInput).always(function (files) {
  1394                      data.files = files;
  1395                      that._onAdd(null, data);
  1396                  });
  1397              } else {
  1398                  data.files = $.makeArray(data.files);
  1399                  this._onAdd(null, data);
  1400              }
  1401          },
  1402  
  1403          // This method is exposed to the widget API and allows sending files
  1404          // using the fileupload API. The data parameter accepts an object which
  1405          // must have a files or fileInput property and can contain additional options:
  1406          // .fileupload('send', {files: filesList});
  1407          // The method returns a Promise object for the file upload call.
  1408          send: function (data) {
  1409              if (data && !this.options.disabled) {
  1410                  if (data.fileInput && !data.files) {
  1411                      var that = this,
  1412                          dfd = $.Deferred(),
  1413                          promise = dfd.promise(),
  1414                          jqXHR,
  1415                          aborted;
  1416                      promise.abort = function () {
  1417                          aborted = true;
  1418                          if (jqXHR) {
  1419                              return jqXHR.abort();
  1420                          }
  1421                          dfd.reject(null, 'abort', 'abort');
  1422                          return promise;
  1423                      };
  1424                      this._getFileInputFiles(data.fileInput).always(
  1425                          function (files) {
  1426                              if (aborted) {
  1427                                  return;
  1428                              }
  1429                              if (!files.length) {
  1430                                  dfd.reject();
  1431                                  return;
  1432                              }
  1433                              data.files = files;
  1434                              jqXHR = that._onSend(null, data);
  1435                              jqXHR.then(
  1436                                  function (result, textStatus, jqXHR) {
  1437                                      dfd.resolve(result, textStatus, jqXHR);
  1438                                  },
  1439                                  function (jqXHR, textStatus, errorThrown) {
  1440                                      dfd.reject(jqXHR, textStatus, errorThrown);
  1441                                  }
  1442                              );
  1443                          }
  1444                      );
  1445                      return this._enhancePromise(promise);
  1446                  }
  1447                  data.files = $.makeArray(data.files);
  1448                  if (data.files.length) {
  1449                      return this._onSend(null, data);
  1450                  }
  1451              }
  1452              return this._getXHRPromise(false, data && data.context);
  1453          }
  1454  
  1455      });
  1456  
  1457  }));