file_upload 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (145) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/Rakefile +34 -0
  4. data/app/assets/javascripts/file_upload/application.js +13 -0
  5. data/app/assets/javascripts/file_upload.js.erb +148 -0
  6. data/app/assets/stylesheets/file_upload/application.css +13 -0
  7. data/app/assets/stylesheets/file_upload.css +3 -0
  8. data/app/controllers/file_upload/application_controller.rb +4 -0
  9. data/app/controllers/file_upload/redis_files_controller.rb +54 -0
  10. data/app/helpers/file_upload/application_helper.rb +4 -0
  11. data/app/helpers/file_upload/tag_helper.rb +25 -0
  12. data/app/models/file_upload/builder_mixin.rb +13 -0
  13. data/app/models/file_upload/redis_file.rb +159 -0
  14. data/app/models/file_upload/redis_file_readable.rb +21 -0
  15. data/app/models/file_upload/temp_file.rb +17 -0
  16. data/app/views/file_upload/redis_files/index.html.erb +1 -0
  17. data/app/views/file_upload/redis_files/new.html.erb +9 -0
  18. data/app/views/file_upload/redis_files/show.html.erb +15 -0
  19. data/app/views/file_upload/tags/_multiple.html.erb +44 -0
  20. data/app/views/file_upload/tags/_single.html.erb +46 -0
  21. data/app/views/layouts/file_upload/application.html.erb +14 -0
  22. data/config/routes.rb +6 -0
  23. data/lib/file_upload/engine.rb +40 -0
  24. data/lib/file_upload/version.rb +3 -0
  25. data/lib/file_upload.rb +7 -0
  26. data/lib/tasks/file_upload_tasks.rake +4 -0
  27. data/test/dummy/README.rdoc +28 -0
  28. data/test/dummy/Rakefile +6 -0
  29. data/test/dummy/app/assets/javascripts/application.js +15 -0
  30. data/test/dummy/app/assets/javascripts/jquery-1.10.2.min.js +6 -0
  31. data/test/dummy/app/assets/javascripts/jquery-ui.js +15003 -0
  32. data/test/dummy/app/assets/stylesheets/application.css +13 -0
  33. data/test/dummy/app/controllers/application_controller.rb +5 -0
  34. data/test/dummy/app/controllers/db_files_controller.rb +8 -0
  35. data/test/dummy/app/controllers/emails_controller.rb +43 -0
  36. data/test/dummy/app/controllers/users_controller.rb +42 -0
  37. data/test/dummy/app/helpers/application_helper.rb +2 -0
  38. data/test/dummy/app/models/db_file.rb +13 -0
  39. data/test/dummy/app/models/email.rb +10 -0
  40. data/test/dummy/app/models/user.rb +9 -0
  41. data/test/dummy/app/views/db_files/_db_file.html.erb +1 -0
  42. data/test/dummy/app/views/emails/_email.html.erb +3 -0
  43. data/test/dummy/app/views/emails/_form.html.erb +24 -0
  44. data/test/dummy/app/views/emails/edit.html.erb +3 -0
  45. data/test/dummy/app/views/emails/index.html.erb +5 -0
  46. data/test/dummy/app/views/emails/new.html.erb +3 -0
  47. data/test/dummy/app/views/emails/show.html.erb +14 -0
  48. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  49. data/test/dummy/app/views/users/_form.html.erb +20 -0
  50. data/test/dummy/app/views/users/_user.html.erb +1 -0
  51. data/test/dummy/app/views/users/edit.html.erb +3 -0
  52. data/test/dummy/app/views/users/index.html.erb +5 -0
  53. data/test/dummy/app/views/users/new.html.erb +3 -0
  54. data/test/dummy/app/views/users/show.html.erb +8 -0
  55. data/test/dummy/bin/bundle +3 -0
  56. data/test/dummy/bin/rails +4 -0
  57. data/test/dummy/bin/rake +4 -0
  58. data/test/dummy/config/application.rb +28 -0
  59. data/test/dummy/config/boot.rb +5 -0
  60. data/test/dummy/config/database.yml +31 -0
  61. data/test/dummy/config/environment.rb +5 -0
  62. data/test/dummy/config/environments/development.rb +29 -0
  63. data/test/dummy/config/environments/production.rb +80 -0
  64. data/test/dummy/config/environments/test.rb +36 -0
  65. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  66. data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  67. data/test/dummy/config/initializers/inflections.rb +16 -0
  68. data/test/dummy/config/initializers/mime_types.rb +5 -0
  69. data/test/dummy/config/initializers/secret_token.rb +12 -0
  70. data/test/dummy/config/initializers/session_store.rb +3 -0
  71. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  72. data/test/dummy/config/locales/en.yml +23 -0
  73. data/test/dummy/config/routes.rb +7 -0
  74. data/test/dummy/config.ru +4 -0
  75. data/test/dummy/db/development.sqlite3 +0 -0
  76. data/test/dummy/db/migrate/20130820025256_add_users.rb +21 -0
  77. data/test/dummy/db/schema.rb +34 -0
  78. data/test/dummy/log/development.log +15541 -0
  79. data/test/dummy/log/test.log +26004 -0
  80. data/test/dummy/public/404.html +58 -0
  81. data/test/dummy/public/422.html +58 -0
  82. data/test/dummy/public/500.html +57 -0
  83. data/test/dummy/public/favicon.ico +0 -0
  84. data/test/dummy/tmp/cache/assets/development/sprockets/1027254ad9f9317017cee3f9f844a862 +0 -0
  85. data/test/dummy/tmp/cache/assets/development/sprockets/13fe41fee1fe35b49d145bcc06610705 +0 -0
  86. data/test/dummy/tmp/cache/assets/development/sprockets/21e0ff88cf13ca60a86a9b629cdca847 +0 -0
  87. data/test/dummy/tmp/cache/assets/development/sprockets/24cf35cebd10e0755a5585f9e486aa35 +0 -0
  88. data/test/dummy/tmp/cache/assets/development/sprockets/27413a86db5db5977d5dd1d87c724907 +0 -0
  89. data/test/dummy/tmp/cache/assets/development/sprockets/2f5173deea6c795b8fdde723bb4b63af +0 -0
  90. data/test/dummy/tmp/cache/assets/development/sprockets/2f5c6d9c2c3107f94ad59710d3b31f75 +0 -0
  91. data/test/dummy/tmp/cache/assets/development/sprockets/3131541e8089635bc8595e2da83d1d81 +0 -0
  92. data/test/dummy/tmp/cache/assets/development/sprockets/32f1d616324cf52e9876bb83b9b0841a +0 -0
  93. data/test/dummy/tmp/cache/assets/development/sprockets/33474263b3841cd98ef63cde45dc85b2 +0 -0
  94. data/test/dummy/tmp/cache/assets/development/sprockets/357970feca3ac29060c1e3861e2c0953 +0 -0
  95. data/test/dummy/tmp/cache/assets/development/sprockets/3fa0d13a4820d4aa56f86ed4af4b88fc +0 -0
  96. data/test/dummy/tmp/cache/assets/development/sprockets/4e07f6f4475a0dca9066eec990db088a +0 -0
  97. data/test/dummy/tmp/cache/assets/development/sprockets/5512d0b5788ce193fd26bfadccd21304 +0 -0
  98. data/test/dummy/tmp/cache/assets/development/sprockets/5d5ab91fa7d1eee7502e703747d4e933 +0 -0
  99. data/test/dummy/tmp/cache/assets/development/sprockets/6b7e97595c37161f619adfd2b7724ea2 +0 -0
  100. data/test/dummy/tmp/cache/assets/development/sprockets/87fa3cc4e2e49762d146f3d1709b3fb0 +0 -0
  101. data/test/dummy/tmp/cache/assets/development/sprockets/8806d06f37e08b22f9546e50ab98d671 +0 -0
  102. data/test/dummy/tmp/cache/assets/development/sprockets/8cdad172c92eaf76e1b71c84f58a192e +0 -0
  103. data/test/dummy/tmp/cache/assets/development/sprockets/91e53bdf30a2b8e78bf99ca794addf70 +0 -0
  104. data/test/dummy/tmp/cache/assets/development/sprockets/96e2b2964d1d31d997a1af35ad0afed2 +0 -0
  105. data/test/dummy/tmp/cache/assets/development/sprockets/ab5c07d7e81fb5d5cf7460715f22bef6 +0 -0
  106. data/test/dummy/tmp/cache/assets/development/sprockets/b94837629e2fb2ef40ea66348764a9a2 +0 -0
  107. data/test/dummy/tmp/cache/assets/development/sprockets/c360bf7b66d29ebdf71b5814574b5fed +0 -0
  108. data/test/dummy/tmp/cache/assets/development/sprockets/cffd775d018f68ce5dba1ee0d951a994 +0 -0
  109. data/test/dummy/tmp/cache/assets/development/sprockets/d771ace226fc8215a3572e0aa35bb0d6 +0 -0
  110. data/test/dummy/tmp/cache/assets/development/sprockets/e046b0166566aeeb2da51f2bde543364 +0 -0
  111. data/test/dummy/tmp/cache/assets/development/sprockets/ec2d15616d5d09eb1693b761c72948ce +0 -0
  112. data/test/dummy/tmp/cache/assets/development/sprockets/f7cbd26ba1d28d48de824f0e94586655 +0 -0
  113. data/test/dummy/tmp/cache/assets/development/sprockets/fc0327db3eb7c687c31f2acf94871f86 +0 -0
  114. data/test/dummy/tmp/cache/assets/test/sprockets/1027254ad9f9317017cee3f9f844a862 +0 -0
  115. data/test/dummy/tmp/cache/assets/test/sprockets/13fe41fee1fe35b49d145bcc06610705 +0 -0
  116. data/test/dummy/tmp/cache/assets/test/sprockets/21e0ff88cf13ca60a86a9b629cdca847 +0 -0
  117. data/test/dummy/tmp/cache/assets/test/sprockets/24cf35cebd10e0755a5585f9e486aa35 +0 -0
  118. data/test/dummy/tmp/cache/assets/test/sprockets/27413a86db5db5977d5dd1d87c724907 +0 -0
  119. data/test/dummy/tmp/cache/assets/test/sprockets/2f5173deea6c795b8fdde723bb4b63af +0 -0
  120. data/test/dummy/tmp/cache/assets/test/sprockets/32f1d616324cf52e9876bb83b9b0841a +0 -0
  121. data/test/dummy/tmp/cache/assets/test/sprockets/357970feca3ac29060c1e3861e2c0953 +0 -0
  122. data/test/dummy/tmp/cache/assets/test/sprockets/3fa0d13a4820d4aa56f86ed4af4b88fc +0 -0
  123. data/test/dummy/tmp/cache/assets/test/sprockets/5d5ab91fa7d1eee7502e703747d4e933 +0 -0
  124. data/test/dummy/tmp/cache/assets/test/sprockets/6b7e97595c37161f619adfd2b7724ea2 +0 -0
  125. data/test/dummy/tmp/cache/assets/test/sprockets/87fa3cc4e2e49762d146f3d1709b3fb0 +0 -0
  126. data/test/dummy/tmp/cache/assets/test/sprockets/8806d06f37e08b22f9546e50ab98d671 +0 -0
  127. data/test/dummy/tmp/cache/assets/test/sprockets/ab5c07d7e81fb5d5cf7460715f22bef6 +0 -0
  128. data/test/dummy/tmp/cache/assets/test/sprockets/b94837629e2fb2ef40ea66348764a9a2 +0 -0
  129. data/test/dummy/tmp/cache/assets/test/sprockets/c360bf7b66d29ebdf71b5814574b5fed +0 -0
  130. data/test/dummy/tmp/cache/assets/test/sprockets/cffd775d018f68ce5dba1ee0d951a994 +0 -0
  131. data/test/dummy/tmp/cache/assets/test/sprockets/d771ace226fc8215a3572e0aa35bb0d6 +0 -0
  132. data/test/dummy/tmp/cache/assets/test/sprockets/e046b0166566aeeb2da51f2bde543364 +0 -0
  133. data/test/dummy/tmp/cache/assets/test/sprockets/ec2d15616d5d09eb1693b761c72948ce +0 -0
  134. data/test/dummy/tmp/cache/assets/test/sprockets/f7cbd26ba1d28d48de824f0e94586655 +0 -0
  135. data/test/dummy/tmp/cache/assets/test/sprockets/fc0327db3eb7c687c31f2acf94871f86 +0 -0
  136. data/test/fixtures/avatar.jpeg +0 -0
  137. data/test/fixtures/test_upload.txt +1 -0
  138. data/test/integration/multiple_upload_test.rb +131 -0
  139. data/test/integration/single_upload_test.rb +157 -0
  140. data/test/test_helper.rb +60 -0
  141. data/test/unit/redis_file_readable_test.rb +30 -0
  142. data/test/unit/redis_file_test.rb +30 -0
  143. data/vendor/assets/javascripts/jquery.fileupload.js +1113 -0
  144. data/vendor/assets/javascripts/load-image.js +232 -0
  145. metadata +401 -0
@@ -0,0 +1,1113 @@
1
+ /*
2
+ * jQuery File Upload Plugin 5.19.7
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
+ /*jslint nomen: true, unparam: true, regexp: true */
13
+ /*global define, window, document, File, Blob, FormData, location */
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
+ // The FileReader API is not actually used, but works as feature detection,
31
+ // as e.g. Safari supports XHR file uploads via the FormData API,
32
+ // but not non-multipart XHR file uploads:
33
+ $.support.xhrFileUpload = !!(window.XMLHttpRequestUpload && window.FileReader);
34
+ $.support.xhrFormDataFileUpload = !!window.FormData;
35
+
36
+ // The fileupload widget listens for change events on file input fields defined
37
+ // via fileInput setting and paste or drop events of the given dropZone.
38
+ // In addition to the default jQuery Widget methods, the fileupload widget
39
+ // exposes the "add" and "send" methods, to add or directly send files using
40
+ // the fileupload API.
41
+ // By default, files added via file input selection, paste, drag & drop or
42
+ // "add" method are uploaded immediately, but it is possible to override
43
+ // the "add" callback option to queue file uploads.
44
+ $.widget('blueimp.fileupload', {
45
+
46
+ options: {
47
+ // The drop target element(s), by the default the complete document.
48
+ // Set to null to disable drag & drop support:
49
+ dropZone: $(document),
50
+ // The paste target element(s), by the default the complete document.
51
+ // Set to null to disable paste support:
52
+ pasteZone: $(document),
53
+ // The file input field(s), that are listened to for change events.
54
+ // If undefined, it is set to the file input fields inside
55
+ // of the widget element on plugin initialization.
56
+ // Set to null to disable the change listener.
57
+ fileInput: undefined,
58
+ // By default, the file input field is replaced with a clone after
59
+ // each input field change event. This is required for iframe transport
60
+ // queues and allows change events to be fired for the same file
61
+ // selection, but can be disabled by setting the following option to false:
62
+ replaceFileInput: true,
63
+ // The parameter name for the file form data (the request argument name).
64
+ // If undefined or empty, the name property of the file input field is
65
+ // used, or "files[]" if the file input name property is also empty,
66
+ // can be a string or an array of strings:
67
+ paramName: undefined,
68
+ // By default, each file of a selection is uploaded using an individual
69
+ // request for XHR type uploads. Set to false to upload file
70
+ // selections in one request each:
71
+ singleFileUploads: true,
72
+ // To limit the number of files uploaded with one XHR request,
73
+ // set the following option to an integer greater than 0:
74
+ limitMultiFileUploads: undefined,
75
+ // Set the following option to true to issue all file upload requests
76
+ // in a sequential order:
77
+ sequentialUploads: false,
78
+ // To limit the number of concurrent uploads,
79
+ // set the following option to an integer greater than 0:
80
+ limitConcurrentUploads: undefined,
81
+ // Set the following option to true to force iframe transport uploads:
82
+ forceIframeTransport: false,
83
+ // Set the following option to the location of a redirect url on the
84
+ // origin server, for cross-domain iframe transport uploads:
85
+ redirect: undefined,
86
+ // The parameter name for the redirect url, sent as part of the form
87
+ // data and set to 'redirect' if this option is empty:
88
+ redirectParamName: undefined,
89
+ // Set the following option to the location of a postMessage window,
90
+ // to enable postMessage transport uploads:
91
+ postMessage: undefined,
92
+ // By default, XHR file uploads are sent as multipart/form-data.
93
+ // The iframe transport is always using multipart/form-data.
94
+ // Set to false to enable non-multipart XHR uploads:
95
+ multipart: true,
96
+ // To upload large files in smaller chunks, set the following option
97
+ // to a preferred maximum chunk size. If set to 0, null or undefined,
98
+ // or the browser does not support the required Blob API, files will
99
+ // be uploaded as a whole.
100
+ maxChunkSize: undefined,
101
+ // When a non-multipart upload or a chunked multipart upload has been
102
+ // aborted, this option can be used to resume the upload by setting
103
+ // it to the size of the already uploaded bytes. This option is most
104
+ // useful when modifying the options object inside of the "add" or
105
+ // "send" callbacks, as the options are cloned for each file upload.
106
+ uploadedBytes: undefined,
107
+ // By default, failed (abort or error) file uploads are removed from the
108
+ // global progress calculation. Set the following option to false to
109
+ // prevent recalculating the global progress data:
110
+ recalculateProgress: true,
111
+ // Interval in milliseconds to calculate and trigger progress events:
112
+ progressInterval: 100,
113
+ // Interval in milliseconds to calculate progress bitrate:
114
+ bitrateInterval: 500,
115
+
116
+ // Additional form data to be sent along with the file uploads can be set
117
+ // using this option, which accepts an array of objects with name and
118
+ // value properties, a function returning such an array, a FormData
119
+ // object (for XHR file uploads), or a simple object.
120
+ // The form of the first fileInput is given as parameter to the function:
121
+ formData: function (form) {
122
+ return form.serializeArray();
123
+ },
124
+
125
+ // The add callback is invoked as soon as files are added to the fileupload
126
+ // widget (via file input selection, drag & drop, paste or add API call).
127
+ // If the singleFileUploads option is enabled, this callback will be
128
+ // called once for each file in the selection for XHR file uplaods, else
129
+ // once for each file selection.
130
+ // The upload starts when the submit method is invoked on the data parameter.
131
+ // The data object contains a files property holding the added files
132
+ // and allows to override plugin options as well as define ajax settings.
133
+ // Listeners for this callback can also be bound the following way:
134
+ // .bind('fileuploadadd', func);
135
+ // data.submit() returns a Promise object and allows to attach additional
136
+ // handlers using jQuery's Deferred callbacks:
137
+ // data.submit().done(func).fail(func).always(func);
138
+ add: function (e, data) {
139
+ data.submit();
140
+ },
141
+
142
+ // Other callbacks:
143
+ // Callback for the submit event of each file upload:
144
+ // submit: function (e, data) {}, // .bind('fileuploadsubmit', func);
145
+ // Callback for the start of each file upload request:
146
+ // send: function (e, data) {}, // .bind('fileuploadsend', func);
147
+ // Callback for successful uploads:
148
+ // done: function (e, data) {}, // .bind('fileuploaddone', func);
149
+ // Callback for failed (abort or error) uploads:
150
+ // fail: function (e, data) {}, // .bind('fileuploadfail', func);
151
+ // Callback for completed (success, abort or error) requests:
152
+ // always: function (e, data) {}, // .bind('fileuploadalways', func);
153
+ // Callback for upload progress events:
154
+ // progress: function (e, data) {}, // .bind('fileuploadprogress', func);
155
+ // Callback for global upload progress events:
156
+ // progressall: function (e, data) {}, // .bind('fileuploadprogressall', func);
157
+ // Callback for uploads start, equivalent to the global ajaxStart event:
158
+ // start: function (e) {}, // .bind('fileuploadstart', func);
159
+ // Callback for uploads stop, equivalent to the global ajaxStop event:
160
+ // stop: function (e) {}, // .bind('fileuploadstop', func);
161
+ // Callback for change events of the fileInput(s):
162
+ // change: function (e, data) {}, // .bind('fileuploadchange', func);
163
+ // Callback for paste events to the pasteZone(s):
164
+ // paste: function (e, data) {}, // .bind('fileuploadpaste', func);
165
+ // Callback for drop events of the dropZone(s):
166
+ // drop: function (e, data) {}, // .bind('fileuploaddrop', func);
167
+ // Callback for dragover events of the dropZone(s):
168
+ // dragover: function (e) {}, // .bind('fileuploaddragover', func);
169
+
170
+ // The plugin options are used as settings object for the ajax calls.
171
+ // The following are jQuery ajax settings required for the file uploads:
172
+ processData: false,
173
+ contentType: false,
174
+ cache: false
175
+ },
176
+
177
+ // A list of options that require a refresh after assigning a new value:
178
+ _refreshOptionsList: [
179
+ 'fileInput',
180
+ 'dropZone',
181
+ 'pasteZone',
182
+ 'multipart',
183
+ 'forceIframeTransport'
184
+ ],
185
+
186
+ _BitrateTimer: function () {
187
+ this.timestamp = +(new Date());
188
+ this.loaded = 0;
189
+ this.bitrate = 0;
190
+ this.getBitrate = function (now, loaded, interval) {
191
+ var timeDiff = now - this.timestamp;
192
+ if (!this.bitrate || !interval || timeDiff > interval) {
193
+ this.bitrate = (loaded - this.loaded) * (1000 / timeDiff) * 8;
194
+ this.loaded = loaded;
195
+ this.timestamp = now;
196
+ }
197
+ return this.bitrate;
198
+ };
199
+ },
200
+
201
+ _isXHRUpload: function (options) {
202
+ return !options.forceIframeTransport &&
203
+ ((!options.multipart && $.support.xhrFileUpload) ||
204
+ $.support.xhrFormDataFileUpload);
205
+ },
206
+
207
+ _getFormData: function (options) {
208
+ var formData;
209
+ if (typeof options.formData === 'function') {
210
+ return options.formData(options.form);
211
+ }
212
+ if ($.isArray(options.formData)) {
213
+ return options.formData;
214
+ }
215
+ if (options.formData) {
216
+ formData = [];
217
+ $.each(options.formData, function (name, value) {
218
+ formData.push({name: name, value: value});
219
+ });
220
+ return formData;
221
+ }
222
+ return [];
223
+ },
224
+
225
+ _getTotal: function (files) {
226
+ var total = 0;
227
+ $.each(files, function (index, file) {
228
+ total += file.size || 1;
229
+ });
230
+ return total;
231
+ },
232
+
233
+ _onProgress: function (e, data) {
234
+ if (e.lengthComputable) {
235
+ var now = +(new Date()),
236
+ total,
237
+ loaded;
238
+ if (data._time && data.progressInterval &&
239
+ (now - data._time < data.progressInterval) &&
240
+ e.loaded !== e.total) {
241
+ return;
242
+ }
243
+ data._time = now;
244
+ total = data.total || this._getTotal(data.files);
245
+ loaded = parseInt(
246
+ e.loaded / e.total * (data.chunkSize || total),
247
+ 10
248
+ ) + (data.uploadedBytes || 0);
249
+ this._loaded += loaded - (data.loaded || data.uploadedBytes || 0);
250
+ data.lengthComputable = true;
251
+ data.loaded = loaded;
252
+ data.total = total;
253
+ data.bitrate = data._bitrateTimer.getBitrate(
254
+ now,
255
+ loaded,
256
+ data.bitrateInterval
257
+ );
258
+ // Trigger a custom progress event with a total data property set
259
+ // to the file size(s) of the current upload and a loaded data
260
+ // property calculated accordingly:
261
+ this._trigger('progress', e, data);
262
+ // Trigger a global progress event for all current file uploads,
263
+ // including ajax calls queued for sequential file uploads:
264
+ this._trigger('progressall', e, {
265
+ lengthComputable: true,
266
+ loaded: this._loaded,
267
+ total: this._total,
268
+ bitrate: this._bitrateTimer.getBitrate(
269
+ now,
270
+ this._loaded,
271
+ data.bitrateInterval
272
+ )
273
+ });
274
+ }
275
+ },
276
+
277
+ _initProgressListener: function (options) {
278
+ var that = this,
279
+ xhr = options.xhr ? options.xhr() : $.ajaxSettings.xhr();
280
+ // Accesss to the native XHR object is required to add event listeners
281
+ // for the upload progress event:
282
+ if (xhr.upload) {
283
+ $(xhr.upload).bind('progress', function (e) {
284
+ var oe = e.originalEvent;
285
+ // Make sure the progress event properties get copied over:
286
+ e.lengthComputable = oe.lengthComputable;
287
+ e.loaded = oe.loaded;
288
+ e.total = oe.total;
289
+ that._onProgress(e, options);
290
+ });
291
+ options.xhr = function () {
292
+ return xhr;
293
+ };
294
+ }
295
+ },
296
+
297
+ _initXHRData: function (options) {
298
+ var formData,
299
+ file = options.files[0],
300
+ // Ignore non-multipart setting if not supported:
301
+ multipart = options.multipart || !$.support.xhrFileUpload,
302
+ paramName = options.paramName[0];
303
+ options.headers = options.headers || {};
304
+ if (options.contentRange) {
305
+ options.headers['Content-Range'] = options.contentRange;
306
+ }
307
+ if (!multipart) {
308
+ options.headers['Content-Disposition'] = 'attachment; filename="' +
309
+ encodeURI(file.name) + '"';
310
+ options.contentType = file.type;
311
+ options.data = options.blob || file;
312
+ } else if ($.support.xhrFormDataFileUpload) {
313
+ if (options.postMessage) {
314
+ // window.postMessage does not allow sending FormData
315
+ // objects, so we just add the File/Blob objects to
316
+ // the formData array and let the postMessage window
317
+ // create the FormData object out of this array:
318
+ formData = this._getFormData(options);
319
+ if (options.blob) {
320
+ formData.push({
321
+ name: paramName,
322
+ value: options.blob
323
+ });
324
+ } else {
325
+ $.each(options.files, function (index, file) {
326
+ formData.push({
327
+ name: options.paramName[index] || paramName,
328
+ value: file
329
+ });
330
+ });
331
+ }
332
+ } else {
333
+ if (options.formData instanceof FormData) {
334
+ formData = options.formData;
335
+ } else {
336
+ formData = new FormData();
337
+ $.each(this._getFormData(options), function (index, field) {
338
+ formData.append(field.name, field.value);
339
+ });
340
+ }
341
+ if (options.blob) {
342
+ options.headers['Content-Disposition'] = 'attachment; filename="' +
343
+ encodeURI(file.name) + '"';
344
+ formData.append(paramName, options.blob, file.name);
345
+ } else {
346
+ $.each(options.files, function (index, file) {
347
+ // Files are also Blob instances, but some browsers
348
+ // (Firefox 3.6) support the File API but not Blobs.
349
+ // This check allows the tests to run with
350
+ // dummy objects:
351
+ if ((window.Blob && file instanceof Blob) ||
352
+ (window.File && file instanceof File)) {
353
+ formData.append(
354
+ options.paramName[index] || paramName,
355
+ file,
356
+ file.name
357
+ );
358
+ }
359
+ });
360
+ }
361
+ }
362
+ options.data = formData;
363
+ }
364
+ // Blob reference is not needed anymore, free memory:
365
+ options.blob = null;
366
+ },
367
+
368
+ _initIframeSettings: function (options) {
369
+ // Setting the dataType to iframe enables the iframe transport:
370
+ options.dataType = 'iframe ' + (options.dataType || '');
371
+ // The iframe transport accepts a serialized array as form data:
372
+ options.formData = this._getFormData(options);
373
+ // Add redirect url to form data on cross-domain uploads:
374
+ if (options.redirect && $('<a></a>').prop('href', options.url)
375
+ .prop('host') !== location.host) {
376
+ options.formData.push({
377
+ name: options.redirectParamName || 'redirect',
378
+ value: options.redirect
379
+ });
380
+ }
381
+ },
382
+
383
+ _initDataSettings: function (options) {
384
+ if (this._isXHRUpload(options)) {
385
+ if (!this._chunkedUpload(options, true)) {
386
+ if (!options.data) {
387
+ this._initXHRData(options);
388
+ }
389
+ this._initProgressListener(options);
390
+ }
391
+ if (options.postMessage) {
392
+ // Setting the dataType to postmessage enables the
393
+ // postMessage transport:
394
+ options.dataType = 'postmessage ' + (options.dataType || '');
395
+ }
396
+ } else {
397
+ this._initIframeSettings(options, 'iframe');
398
+ }
399
+ },
400
+
401
+ _getParamName: function (options) {
402
+ var fileInput = $(options.fileInput),
403
+ paramName = options.paramName;
404
+ if (!paramName) {
405
+ paramName = [];
406
+ fileInput.each(function () {
407
+ var input = $(this),
408
+ name = input.prop('name') || 'files[]',
409
+ i = (input.prop('files') || [1]).length;
410
+ while (i) {
411
+ paramName.push(name);
412
+ i -= 1;
413
+ }
414
+ });
415
+ if (!paramName.length) {
416
+ paramName = [fileInput.prop('name') || 'files[]'];
417
+ }
418
+ } else if (!$.isArray(paramName)) {
419
+ paramName = [paramName];
420
+ }
421
+ return paramName;
422
+ },
423
+
424
+ _initFormSettings: function (options) {
425
+ // Retrieve missing options from the input field and the
426
+ // associated form, if available:
427
+ if (!options.form || !options.form.length) {
428
+ options.form = $(options.fileInput.prop('form'));
429
+ // If the given file input doesn't have an associated form,
430
+ // use the default widget file input's form:
431
+ if (!options.form.length) {
432
+ options.form = $(this.options.fileInput.prop('form'));
433
+ }
434
+ }
435
+ options.paramName = this._getParamName(options);
436
+ if (!options.url) {
437
+ options.url = options.form.prop('action') || location.href;
438
+ }
439
+ // The HTTP request method must be "POST" or "PUT":
440
+ options.type = (options.type || options.form.prop('method') || '')
441
+ .toUpperCase();
442
+ if (options.type !== 'POST' && options.type !== 'PUT') {
443
+ options.type = 'POST';
444
+ }
445
+ if (!options.formAcceptCharset) {
446
+ options.formAcceptCharset = options.form.attr('accept-charset');
447
+ }
448
+ },
449
+
450
+ _getAJAXSettings: function (data) {
451
+ var options = $.extend({}, this.options, data);
452
+ this._initFormSettings(options);
453
+ this._initDataSettings(options);
454
+ return options;
455
+ },
456
+
457
+ // Maps jqXHR callbacks to the equivalent
458
+ // methods of the given Promise object:
459
+ _enhancePromise: function (promise) {
460
+ promise.success = promise.done;
461
+ promise.error = promise.fail;
462
+ promise.complete = promise.always;
463
+ return promise;
464
+ },
465
+
466
+ // Creates and returns a Promise object enhanced with
467
+ // the jqXHR methods abort, success, error and complete:
468
+ _getXHRPromise: function (resolveOrReject, context, args) {
469
+ var dfd = $.Deferred(),
470
+ promise = dfd.promise();
471
+ context = context || this.options.context || promise;
472
+ if (resolveOrReject === true) {
473
+ dfd.resolveWith(context, args);
474
+ } else if (resolveOrReject === false) {
475
+ dfd.rejectWith(context, args);
476
+ }
477
+ promise.abort = dfd.promise;
478
+ return this._enhancePromise(promise);
479
+ },
480
+
481
+ // Parses the Range header from the server response
482
+ // and returns the uploaded bytes:
483
+ _getUploadedBytes: function (jqXHR) {
484
+ var range = jqXHR.getResponseHeader('Range'),
485
+ parts = range && range.split('-'),
486
+ upperBytesPos = parts && parts.length > 1 &&
487
+ parseInt(parts[1], 10);
488
+ return upperBytesPos && upperBytesPos + 1;
489
+ },
490
+
491
+ // Uploads a file in multiple, sequential requests
492
+ // by splitting the file up in multiple blob chunks.
493
+ // If the second parameter is true, only tests if the file
494
+ // should be uploaded in chunks, but does not invoke any
495
+ // upload requests:
496
+ _chunkedUpload: function (options, testOnly) {
497
+ var that = this,
498
+ file = options.files[0],
499
+ fs = file.size,
500
+ ub = options.uploadedBytes = options.uploadedBytes || 0,
501
+ mcs = options.maxChunkSize || fs,
502
+ slice = file.slice || file.webkitSlice || file.mozSlice,
503
+ dfd = $.Deferred(),
504
+ promise = dfd.promise(),
505
+ jqXHR,
506
+ upload;
507
+ if (!(this._isXHRUpload(options) && slice && (ub || mcs < fs)) ||
508
+ options.data) {
509
+ return false;
510
+ }
511
+ if (testOnly) {
512
+ return true;
513
+ }
514
+ if (ub >= fs) {
515
+ file.error = 'Uploaded bytes exceed file size';
516
+ return this._getXHRPromise(
517
+ false,
518
+ options.context,
519
+ [null, 'error', file.error]
520
+ );
521
+ }
522
+ // The chunk upload method:
523
+ upload = function (i) {
524
+ // Clone the options object for each chunk upload:
525
+ var o = $.extend({}, options);
526
+ o.blob = slice.call(
527
+ file,
528
+ ub,
529
+ ub + mcs,
530
+ file.type
531
+ );
532
+ // Store the current chunk size, as the blob itself
533
+ // will be dereferenced after data processing:
534
+ o.chunkSize = o.blob.size;
535
+ // Expose the chunk bytes position range:
536
+ o.contentRange = 'bytes ' + ub + '-' +
537
+ (ub + o.chunkSize - 1) + '/' + fs;
538
+ // Process the upload data (the blob and potential form data):
539
+ that._initXHRData(o);
540
+ // Add progress listeners for this chunk upload:
541
+ that._initProgressListener(o);
542
+ jqXHR = ($.ajax(o) || that._getXHRPromise(false, o.context))
543
+ .done(function (result, textStatus, jqXHR) {
544
+ ub = that._getUploadedBytes(jqXHR) ||
545
+ (ub + o.chunkSize);
546
+ // Create a progress event if upload is done and
547
+ // no progress event has been invoked for this chunk:
548
+ if (!o.loaded) {
549
+ that._onProgress($.Event('progress', {
550
+ lengthComputable: true,
551
+ loaded: ub - o.uploadedBytes,
552
+ total: ub - o.uploadedBytes
553
+ }), o);
554
+ }
555
+ options.uploadedBytes = o.uploadedBytes = ub;
556
+ if (ub < fs) {
557
+ // File upload not yet complete,
558
+ // continue with the next chunk:
559
+ upload();
560
+ } else {
561
+ dfd.resolveWith(
562
+ o.context,
563
+ [result, textStatus, jqXHR]
564
+ );
565
+ }
566
+ })
567
+ .fail(function (jqXHR, textStatus, errorThrown) {
568
+ dfd.rejectWith(
569
+ o.context,
570
+ [jqXHR, textStatus, errorThrown]
571
+ );
572
+ });
573
+ };
574
+ this._enhancePromise(promise);
575
+ promise.abort = function () {
576
+ return jqXHR.abort();
577
+ };
578
+ upload();
579
+ return promise;
580
+ },
581
+
582
+ _beforeSend: function (e, data) {
583
+ if (this._active === 0) {
584
+ // the start callback is triggered when an upload starts
585
+ // and no other uploads are currently running,
586
+ // equivalent to the global ajaxStart event:
587
+ this._trigger('start');
588
+ // Set timer for global bitrate progress calculation:
589
+ this._bitrateTimer = new this._BitrateTimer();
590
+ }
591
+ this._active += 1;
592
+ // Initialize the global progress values:
593
+ this._loaded += data.uploadedBytes || 0;
594
+ this._total += this._getTotal(data.files);
595
+ },
596
+
597
+ _onDone: function (result, textStatus, jqXHR, options) {
598
+ if (!this._isXHRUpload(options)) {
599
+ // Create a progress event for each iframe load:
600
+ this._onProgress($.Event('progress', {
601
+ lengthComputable: true,
602
+ loaded: 1,
603
+ total: 1
604
+ }), options);
605
+ }
606
+ options.result = result;
607
+ options.textStatus = textStatus;
608
+ options.jqXHR = jqXHR;
609
+ this._trigger('done', null, options);
610
+ },
611
+
612
+ _onFail: function (jqXHR, textStatus, errorThrown, options) {
613
+ options.jqXHR = jqXHR;
614
+ options.textStatus = textStatus;
615
+ options.errorThrown = errorThrown;
616
+ this._trigger('fail', null, options);
617
+ if (options.recalculateProgress) {
618
+ // Remove the failed (error or abort) file upload from
619
+ // the global progress calculation:
620
+ this._loaded -= options.loaded || options.uploadedBytes || 0;
621
+ this._total -= options.total || this._getTotal(options.files);
622
+ }
623
+ },
624
+
625
+ _onAlways: function (jqXHRorResult, textStatus, jqXHRorError, options) {
626
+ this._active -= 1;
627
+ options.textStatus = textStatus;
628
+ if (jqXHRorError && jqXHRorError.always) {
629
+ options.jqXHR = jqXHRorError;
630
+ options.result = jqXHRorResult;
631
+ } else {
632
+ options.jqXHR = jqXHRorResult;
633
+ options.errorThrown = jqXHRorError;
634
+ }
635
+ this._trigger('always', null, options);
636
+ if (this._active === 0) {
637
+ // The stop callback is triggered when all uploads have
638
+ // been completed, equivalent to the global ajaxStop event:
639
+ this._trigger('stop');
640
+ // Reset the global progress values:
641
+ this._loaded = this._total = 0;
642
+ this._bitrateTimer = null;
643
+ }
644
+ },
645
+
646
+ _onSend: function (e, data) {
647
+ var that = this,
648
+ jqXHR,
649
+ aborted,
650
+ slot,
651
+ pipe,
652
+ options = that._getAJAXSettings(data),
653
+ send = function () {
654
+ that._sending += 1;
655
+ // Set timer for bitrate progress calculation:
656
+ options._bitrateTimer = new that._BitrateTimer();
657
+ jqXHR = jqXHR || (
658
+ ((aborted || that._trigger('send', e, options) === false) &&
659
+ that._getXHRPromise(false, options.context, aborted)) ||
660
+ that._chunkedUpload(options) || $.ajax(options)
661
+ ).done(function (result, textStatus, jqXHR) {
662
+ that._onDone(result, textStatus, jqXHR, options);
663
+ }).fail(function (jqXHR, textStatus, errorThrown) {
664
+ that._onFail(jqXHR, textStatus, errorThrown, options);
665
+ }).always(function (jqXHRorResult, textStatus, jqXHRorError) {
666
+ that._sending -= 1;
667
+ that._onAlways(
668
+ jqXHRorResult,
669
+ textStatus,
670
+ jqXHRorError,
671
+ options
672
+ );
673
+ if (options.limitConcurrentUploads &&
674
+ options.limitConcurrentUploads > that._sending) {
675
+ // Start the next queued upload,
676
+ // that has not been aborted:
677
+ var nextSlot = that._slots.shift(),
678
+ isPending;
679
+ while (nextSlot) {
680
+ // jQuery 1.6 doesn't provide .state(),
681
+ // while jQuery 1.8+ removed .isRejected():
682
+ isPending = nextSlot.state ?
683
+ nextSlot.state() === 'pending' :
684
+ !nextSlot.isRejected();
685
+ if (isPending) {
686
+ nextSlot.resolve();
687
+ break;
688
+ }
689
+ nextSlot = that._slots.shift();
690
+ }
691
+ }
692
+ });
693
+ return jqXHR;
694
+ };
695
+ this._beforeSend(e, options);
696
+ if (this.options.sequentialUploads ||
697
+ (this.options.limitConcurrentUploads &&
698
+ this.options.limitConcurrentUploads <= this._sending)) {
699
+ if (this.options.limitConcurrentUploads > 1) {
700
+ slot = $.Deferred();
701
+ this._slots.push(slot);
702
+ pipe = slot.pipe(send);
703
+ } else {
704
+ pipe = (this._sequence = this._sequence.pipe(send, send));
705
+ }
706
+ // Return the piped Promise object, enhanced with an abort method,
707
+ // which is delegated to the jqXHR object of the current upload,
708
+ // and jqXHR callbacks mapped to the equivalent Promise methods:
709
+ pipe.abort = function () {
710
+ aborted = [undefined, 'abort', 'abort'];
711
+ if (!jqXHR) {
712
+ if (slot) {
713
+ slot.rejectWith(options.context, aborted);
714
+ }
715
+ return send();
716
+ }
717
+ return jqXHR.abort();
718
+ };
719
+ return this._enhancePromise(pipe);
720
+ }
721
+ return send();
722
+ },
723
+
724
+ _onAdd: function (e, data) {
725
+ var that = this,
726
+ result = true,
727
+ options = $.extend({}, this.options, data),
728
+ limit = options.limitMultiFileUploads,
729
+ paramName = this._getParamName(options),
730
+ paramNameSet,
731
+ paramNameSlice,
732
+ fileSet,
733
+ i;
734
+ if (!(options.singleFileUploads || limit) ||
735
+ !this._isXHRUpload(options)) {
736
+ fileSet = [data.files];
737
+ paramNameSet = [paramName];
738
+ } else if (!options.singleFileUploads && limit) {
739
+ fileSet = [];
740
+ paramNameSet = [];
741
+ for (i = 0; i < data.files.length; i += limit) {
742
+ fileSet.push(data.files.slice(i, i + limit));
743
+ paramNameSlice = paramName.slice(i, i + limit);
744
+ if (!paramNameSlice.length) {
745
+ paramNameSlice = paramName;
746
+ }
747
+ paramNameSet.push(paramNameSlice);
748
+ }
749
+ } else {
750
+ paramNameSet = paramName;
751
+ }
752
+ data.originalFiles = data.files;
753
+ $.each(fileSet || data.files, function (index, element) {
754
+ var newData = $.extend({}, data);
755
+ newData.files = fileSet ? element : [element];
756
+ newData.paramName = paramNameSet[index];
757
+ newData.submit = function () {
758
+ newData.jqXHR = this.jqXHR =
759
+ (that._trigger('submit', e, this) !== false) &&
760
+ that._onSend(e, this);
761
+ return this.jqXHR;
762
+ };
763
+ result = that._trigger('add', e, newData);
764
+ return result;
765
+ });
766
+ return result;
767
+ },
768
+
769
+ _replaceFileInput: function (input) {
770
+ var inputClone = input.clone(true);
771
+ $('<form></form>').append(inputClone)[0].reset();
772
+ // Detaching allows to insert the fileInput on another form
773
+ // without loosing the file input value:
774
+ input.after(inputClone).detach();
775
+ // Avoid memory leaks with the detached file input:
776
+ $.cleanData(input.unbind('remove'));
777
+ // Replace the original file input element in the fileInput
778
+ // elements set with the clone, which has been copied including
779
+ // event handlers:
780
+ this.options.fileInput = this.options.fileInput.map(function (i, el) {
781
+ if (el === input[0]) {
782
+ return inputClone[0];
783
+ }
784
+ return el;
785
+ });
786
+ // If the widget has been initialized on the file input itself,
787
+ // override this.element with the file input clone:
788
+ if (input[0] === this.element[0]) {
789
+ this.element = inputClone;
790
+ }
791
+ },
792
+
793
+ _handleFileTreeEntry: function (entry, path) {
794
+ var that = this,
795
+ dfd = $.Deferred(),
796
+ errorHandler = function (e) {
797
+ if (e && !e.entry) {
798
+ e.entry = entry;
799
+ }
800
+ // Since $.when returns immediately if one
801
+ // Deferred is rejected, we use resolve instead.
802
+ // This allows valid files and invalid items
803
+ // to be returned together in one set:
804
+ dfd.resolve([e]);
805
+ },
806
+ dirReader;
807
+ path = path || '';
808
+ if (entry.isFile) {
809
+ if (entry._file) {
810
+ // Workaround for Chrome bug #149735
811
+ entry._file.relativePath = path;
812
+ dfd.resolve(entry._file);
813
+ } else {
814
+ entry.file(function (file) {
815
+ file.relativePath = path;
816
+ dfd.resolve(file);
817
+ }, errorHandler);
818
+ }
819
+ } else if (entry.isDirectory) {
820
+ dirReader = entry.createReader();
821
+ dirReader.readEntries(function (entries) {
822
+ that._handleFileTreeEntries(
823
+ entries,
824
+ path + entry.name + '/'
825
+ ).done(function (files) {
826
+ dfd.resolve(files);
827
+ }).fail(errorHandler);
828
+ }, errorHandler);
829
+ } else {
830
+ // Return an empy list for file system items
831
+ // other than files or directories:
832
+ dfd.resolve([]);
833
+ }
834
+ return dfd.promise();
835
+ },
836
+
837
+ _handleFileTreeEntries: function (entries, path) {
838
+ var that = this;
839
+ return $.when.apply(
840
+ $,
841
+ $.map(entries, function (entry) {
842
+ return that._handleFileTreeEntry(entry, path);
843
+ })
844
+ ).pipe(function () {
845
+ return Array.prototype.concat.apply(
846
+ [],
847
+ arguments
848
+ );
849
+ });
850
+ },
851
+
852
+ _getDroppedFiles: function (dataTransfer) {
853
+ dataTransfer = dataTransfer || {};
854
+ var items = dataTransfer.items;
855
+ if (items && items.length && (items[0].webkitGetAsEntry ||
856
+ items[0].getAsEntry)) {
857
+ return this._handleFileTreeEntries(
858
+ $.map(items, function (item) {
859
+ var entry;
860
+ if (item.webkitGetAsEntry) {
861
+ entry = item.webkitGetAsEntry();
862
+ if (entry) {
863
+ // Workaround for Chrome bug #149735:
864
+ entry._file = item.getAsFile();
865
+ }
866
+ return entry;
867
+ }
868
+ return item.getAsEntry();
869
+ })
870
+ );
871
+ }
872
+ return $.Deferred().resolve(
873
+ $.makeArray(dataTransfer.files)
874
+ ).promise();
875
+ },
876
+
877
+ _getSingleFileInputFiles: function (fileInput) {
878
+ fileInput = $(fileInput);
879
+ var entries = fileInput.prop('webkitEntries') ||
880
+ fileInput.prop('entries'),
881
+ files,
882
+ value;
883
+ if (entries && entries.length) {
884
+ return this._handleFileTreeEntries(entries);
885
+ }
886
+ files = $.makeArray(fileInput.prop('files'));
887
+ if (!files.length) {
888
+ value = fileInput.prop('value');
889
+ if (!value) {
890
+ return $.Deferred().resolve([]).promise();
891
+ }
892
+ // If the files property is not available, the browser does not
893
+ // support the File API and we add a pseudo File object with
894
+ // the input value as name with path information removed:
895
+ files = [{name: value.replace(/^.*\\/, '')}];
896
+ } else if (files[0].name === undefined && files[0].fileName) {
897
+ // File normalization for Safari 4 and Firefox 3:
898
+ $.each(files, function (index, file) {
899
+ file.name = file.fileName;
900
+ file.size = file.fileSize;
901
+ });
902
+ }
903
+ return $.Deferred().resolve(files).promise();
904
+ },
905
+
906
+ _getFileInputFiles: function (fileInput) {
907
+ if (!(fileInput instanceof $) || fileInput.length === 1) {
908
+ return this._getSingleFileInputFiles(fileInput);
909
+ }
910
+ return $.when.apply(
911
+ $,
912
+ $.map(fileInput, this._getSingleFileInputFiles)
913
+ ).pipe(function () {
914
+ return Array.prototype.concat.apply(
915
+ [],
916
+ arguments
917
+ );
918
+ });
919
+ },
920
+
921
+ _onChange: function (e) {
922
+ var that = this,
923
+ data = {
924
+ fileInput: $(e.target),
925
+ form: $(e.target.form)
926
+ };
927
+ this._getFileInputFiles(data.fileInput).always(function (files) {
928
+ data.files = files;
929
+ if (that.options.replaceFileInput) {
930
+ that._replaceFileInput(data.fileInput);
931
+ }
932
+ if (that._trigger('change', e, data) !== false) {
933
+ that._onAdd(e, data);
934
+ }
935
+ });
936
+ },
937
+
938
+ _onPaste: function (e) {
939
+ var cbd = e.originalEvent.clipboardData,
940
+ items = (cbd && cbd.items) || [],
941
+ data = {files: []};
942
+ $.each(items, function (index, item) {
943
+ var file = item.getAsFile && item.getAsFile();
944
+ if (file) {
945
+ data.files.push(file);
946
+ }
947
+ });
948
+ if (this._trigger('paste', e, data) === false ||
949
+ this._onAdd(e, data) === false) {
950
+ return false;
951
+ }
952
+ },
953
+
954
+ _onDrop: function (e) {
955
+ var that = this,
956
+ dataTransfer = e.dataTransfer = e.originalEvent.dataTransfer,
957
+ data = {};
958
+ if (dataTransfer && dataTransfer.files && dataTransfer.files.length) {
959
+ e.preventDefault();
960
+ }
961
+ this._getDroppedFiles(dataTransfer).always(function (files) {
962
+ data.files = files;
963
+ if (that._trigger('drop', e, data) !== false) {
964
+ that._onAdd(e, data);
965
+ }
966
+ });
967
+ },
968
+
969
+ _onDragOver: function (e) {
970
+ var dataTransfer = e.dataTransfer = e.originalEvent.dataTransfer;
971
+ if (this._trigger('dragover', e) === false) {
972
+ return false;
973
+ }
974
+ if (dataTransfer && $.inArray('Files', dataTransfer.types) !== -1) {
975
+ dataTransfer.dropEffect = 'copy';
976
+ e.preventDefault();
977
+ }
978
+ },
979
+
980
+ _initEventHandlers: function () {
981
+ if (this._isXHRUpload(this.options)) {
982
+ this._on(this.options.dropZone, {
983
+ dragover: this._onDragOver,
984
+ drop: this._onDrop
985
+ });
986
+ this._on(this.options.pasteZone, {
987
+ paste: this._onPaste
988
+ });
989
+ }
990
+ this._on(this.options.fileInput, {
991
+ change: this._onChange
992
+ });
993
+ },
994
+
995
+ _destroyEventHandlers: function () {
996
+ this._off(this.options.dropZone, 'dragover drop');
997
+ this._off(this.options.pasteZone, 'paste');
998
+ this._off(this.options.fileInput, 'change');
999
+ },
1000
+
1001
+ _setOption: function (key, value) {
1002
+ var refresh = $.inArray(key, this._refreshOptionsList) !== -1;
1003
+ if (refresh) {
1004
+ this._destroyEventHandlers();
1005
+ }
1006
+ this._super(key, value);
1007
+ if (refresh) {
1008
+ this._initSpecialOptions();
1009
+ this._initEventHandlers();
1010
+ }
1011
+ },
1012
+
1013
+ _initSpecialOptions: function () {
1014
+ var options = this.options;
1015
+ if (options.fileInput === undefined) {
1016
+ options.fileInput = this.element.is('input[type="file"]') ?
1017
+ this.element : this.element.find('input[type="file"]');
1018
+ } else if (!(options.fileInput instanceof $)) {
1019
+ options.fileInput = $(options.fileInput);
1020
+ }
1021
+ if (!(options.dropZone instanceof $)) {
1022
+ options.dropZone = $(options.dropZone);
1023
+ }
1024
+ if (!(options.pasteZone instanceof $)) {
1025
+ options.pasteZone = $(options.pasteZone);
1026
+ }
1027
+ },
1028
+
1029
+ _create: function () {
1030
+ var options = this.options;
1031
+ // Initialize options set via HTML5 data-attributes:
1032
+ $.extend(options, $(this.element[0].cloneNode(false)).data());
1033
+ this._initSpecialOptions();
1034
+ this._slots = [];
1035
+ this._sequence = this._getXHRPromise(true);
1036
+ this._sending = this._active = this._loaded = this._total = 0;
1037
+ this._initEventHandlers();
1038
+ },
1039
+
1040
+ _destroy: function () {
1041
+ this._destroyEventHandlers();
1042
+ },
1043
+
1044
+ // This method is exposed to the widget API and allows adding files
1045
+ // using the fileupload API. The data parameter accepts an object which
1046
+ // must have a files property and can contain additional options:
1047
+ // .fileupload('add', {files: filesList});
1048
+ add: function (data) {
1049
+ var that = this;
1050
+ if (!data || this.options.disabled) {
1051
+ return;
1052
+ }
1053
+ if (data.fileInput && !data.files) {
1054
+ this._getFileInputFiles(data.fileInput).always(function (files) {
1055
+ data.files = files;
1056
+ that._onAdd(null, data);
1057
+ });
1058
+ } else {
1059
+ data.files = $.makeArray(data.files);
1060
+ this._onAdd(null, data);
1061
+ }
1062
+ },
1063
+
1064
+ // This method is exposed to the widget API and allows sending files
1065
+ // using the fileupload API. The data parameter accepts an object which
1066
+ // must have a files or fileInput property and can contain additional options:
1067
+ // .fileupload('send', {files: filesList});
1068
+ // The method returns a Promise object for the file upload call.
1069
+ send: function (data) {
1070
+ if (data && !this.options.disabled) {
1071
+ if (data.fileInput && !data.files) {
1072
+ var that = this,
1073
+ dfd = $.Deferred(),
1074
+ promise = dfd.promise(),
1075
+ jqXHR,
1076
+ aborted;
1077
+ promise.abort = function () {
1078
+ aborted = true;
1079
+ if (jqXHR) {
1080
+ return jqXHR.abort();
1081
+ }
1082
+ dfd.reject(null, 'abort', 'abort');
1083
+ return promise;
1084
+ };
1085
+ this._getFileInputFiles(data.fileInput).always(
1086
+ function (files) {
1087
+ if (aborted) {
1088
+ return;
1089
+ }
1090
+ data.files = files;
1091
+ jqXHR = that._onSend(null, data).then(
1092
+ function (result, textStatus, jqXHR) {
1093
+ dfd.resolve(result, textStatus, jqXHR);
1094
+ },
1095
+ function (jqXHR, textStatus, errorThrown) {
1096
+ dfd.reject(jqXHR, textStatus, errorThrown);
1097
+ }
1098
+ );
1099
+ }
1100
+ );
1101
+ return this._enhancePromise(promise);
1102
+ }
1103
+ data.files = $.makeArray(data.files);
1104
+ if (data.files.length) {
1105
+ return this._onSend(null, data);
1106
+ }
1107
+ }
1108
+ return this._getXHRPromise(false, data && data.context);
1109
+ }
1110
+
1111
+ });
1112
+
1113
+ }));