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