plupload-rails 1.0.6 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (71) hide show
  1. checksums.yaml +7 -0
  2. data/GPL-LICENSE.txt +278 -0
  3. data/MIT-LICENSE.txt +20 -0
  4. data/README.md +44 -15
  5. data/app/assets/javascripts/plupload.settings.js.erb +4 -4
  6. data/lib/plupload/rails/version.rb +2 -3
  7. data/plupload-rails.gemspec +4 -3
  8. data/vendor/assets/images/jquery.plupload.queue/backgrounds.gif +0 -0
  9. data/vendor/assets/images/jquery.plupload.queue/buttons-disabled.png +0 -0
  10. data/vendor/assets/images/jquery.plupload.queue/buttons.png +0 -0
  11. data/vendor/assets/images/jquery.plupload.queue/delete.gif +0 -0
  12. data/vendor/assets/images/jquery.plupload.queue/done.gif +0 -0
  13. data/vendor/assets/images/jquery.plupload.queue/error.gif +0 -0
  14. data/vendor/assets/images/jquery.plupload.queue/throbber.gif +0 -0
  15. data/vendor/assets/images/jquery.plupload.queue/transp50.png +0 -0
  16. data/vendor/assets/images/jquery.ui.plupload/loading.gif +0 -0
  17. data/vendor/assets/images/jquery.ui.plupload/plupload.png +0 -0
  18. data/vendor/assets/javascripts/jquery.plupload.queue.js +414 -1
  19. data/vendor/assets/javascripts/jquery.ui.plupload.js +1216 -1
  20. data/vendor/assets/javascripts/moxie.js +10439 -0
  21. data/vendor/assets/javascripts/plupload.dev.js +2083 -0
  22. data/vendor/assets/javascripts/plupload/i18n/bs.js +1 -0
  23. data/vendor/assets/javascripts/plupload/i18n/cs.js +1 -14
  24. data/vendor/assets/javascripts/plupload/i18n/cy.js +1 -0
  25. data/vendor/assets/javascripts/plupload/i18n/da.js +1 -12
  26. data/vendor/assets/javascripts/plupload/i18n/de.js +1 -24
  27. data/vendor/assets/javascripts/plupload/i18n/el.js +1 -0
  28. data/vendor/assets/javascripts/plupload/i18n/en.js +1 -0
  29. data/vendor/assets/javascripts/plupload/i18n/es.js +1 -25
  30. data/vendor/assets/javascripts/plupload/i18n/et.js +1 -0
  31. data/vendor/assets/javascripts/plupload/i18n/fa.js +1 -0
  32. data/vendor/assets/javascripts/plupload/i18n/fi.js +1 -33
  33. data/vendor/assets/javascripts/plupload/i18n/fr.js +1 -25
  34. data/vendor/assets/javascripts/plupload/i18n/hr.js +1 -25
  35. data/vendor/assets/javascripts/plupload/i18n/hu.js +1 -33
  36. data/vendor/assets/javascripts/plupload/i18n/hy.js +1 -0
  37. data/vendor/assets/javascripts/plupload/i18n/it.js +1 -24
  38. data/vendor/assets/javascripts/plupload/i18n/ja.js +1 -37
  39. data/vendor/assets/javascripts/plupload/i18n/ka.js +1 -0
  40. data/vendor/assets/javascripts/plupload/i18n/ko.js +1 -0
  41. data/vendor/assets/javascripts/plupload/i18n/lt.js +1 -0
  42. data/vendor/assets/javascripts/plupload/i18n/lv.js +1 -33
  43. data/vendor/assets/javascripts/plupload/i18n/nl.js +1 -21
  44. data/vendor/assets/javascripts/plupload/i18n/pl.js +1 -24
  45. data/vendor/assets/javascripts/plupload/i18n/pt_BR.js +1 -0
  46. data/vendor/assets/javascripts/plupload/i18n/ro.js +1 -24
  47. data/vendor/assets/javascripts/plupload/i18n/ru.js +1 -21
  48. data/vendor/assets/javascripts/plupload/i18n/sk.js +1 -0
  49. data/vendor/assets/javascripts/plupload/i18n/sr.js +1 -14
  50. data/vendor/assets/javascripts/plupload/i18n/sv.js +1 -12
  51. data/vendor/assets/javascripts/plupload/i18n/th_TH.js +1 -0
  52. data/vendor/assets/javascripts/plupload/i18n/tr.js +1 -0
  53. data/vendor/assets/javascripts/plupload/i18n/uk_UA.js +1 -0
  54. data/vendor/assets/javascripts/plupload/i18n/zh_CN.js +1 -0
  55. data/vendor/assets/misc/Moxie.swf +0 -0
  56. data/vendor/assets/misc/Moxie.xap +0 -0
  57. data/vendor/assets/stylesheets/jquery.plupload.queue.scss +177 -177
  58. data/vendor/assets/stylesheets/jquery.ui.plupload.scss +362 -147
  59. metadata +42 -28
  60. data/vendor/assets/images/jquery.ui.plupload/plupload-bw.png +0 -0
  61. data/vendor/assets/javascripts/plupload.browserplus.js +0 -1
  62. data/vendor/assets/javascripts/plupload.flash.js +0 -1
  63. data/vendor/assets/javascripts/plupload.full.js +0 -2
  64. data/vendor/assets/javascripts/plupload.gears.js +0 -1
  65. data/vendor/assets/javascripts/plupload.html4.js +0 -1
  66. data/vendor/assets/javascripts/plupload.html5.js +0 -1
  67. data/vendor/assets/javascripts/plupload.js +0 -2
  68. data/vendor/assets/javascripts/plupload.silverlight.js +0 -1
  69. data/vendor/assets/javascripts/plupload/i18n/pt-br.js +0 -35
  70. data/vendor/assets/misc/plupload.flash.swf +0 -0
  71. data/vendor/assets/misc/plupload.silverlight.xap +0 -0
@@ -0,0 +1,2083 @@
1
+ /**
2
+ * Plupload - multi-runtime File Uploader
3
+ * v2.0.0beta
4
+ *
5
+ * Copyright 2013, Moxiecode Systems AB
6
+ * Released under GPL License.
7
+ *
8
+ * License: http://www.plupload.com/license
9
+ * Contributing: http://www.plupload.com/contributing
10
+ *
11
+ * Date: 2012-11-30
12
+ */
13
+ /**
14
+ * Plupload.js
15
+ *
16
+ * Copyright 2013, Moxiecode Systems AB
17
+ * Released under GPL License.
18
+ *
19
+ * License: http://www.plupload.com/license
20
+ * Contributing: http://www.plupload.com/contributing
21
+ */
22
+
23
+ /*global mOxie:true */
24
+
25
+ ;(function(window, o, undef) {
26
+
27
+ var delay = window.setTimeout
28
+ , fileFilters = {}
29
+ ;
30
+
31
+ // convert plupload features to caps acceptable by mOxie
32
+ function normalizeCaps(settings) {
33
+ var features = settings.required_features, caps = {};
34
+
35
+ function resolve(feature, value, strict) {
36
+ // Feature notation is deprecated, use caps (this thing here is required for backward compatibility)
37
+ var map = {
38
+ chunks: 'slice_blob',
39
+ resize: 'send_binary_string',
40
+ jpgresize: 'send_binary_string',
41
+ pngresize: 'send_binary_string',
42
+ progress: 'report_upload_progress',
43
+ multi_selection: 'select_multiple',
44
+ max_file_size: 'access_binary',
45
+ dragdrop: 'drag_and_drop',
46
+ drop_element: 'drag_and_drop',
47
+ headers: 'send_custom_headers',
48
+ canSendBinary: 'send_binary',
49
+ triggerDialog: 'summon_file_dialog'
50
+ };
51
+
52
+ if (map[feature]) {
53
+ caps[map[feature]] = value;
54
+ } else if (!strict) {
55
+ caps[feature] = value;
56
+ }
57
+ }
58
+
59
+ if (typeof(features) === 'string') {
60
+ plupload.each(features.split(/\s*,\s*/), function(feature) {
61
+ resolve(feature, true);
62
+ });
63
+ } else if (typeof(features) === 'object') {
64
+ plupload.each(features, function(value, feature) {
65
+ resolve(feature, value);
66
+ });
67
+ } else if (features === true) {
68
+ // check settings for required features
69
+ if (!settings.multipart) { // special care for multipart: false
70
+ caps.send_binary_string = true;
71
+ }
72
+
73
+ if (settings.chunk_size > 0) {
74
+ caps.slice_blob = true;
75
+ }
76
+
77
+ plupload.each(settings, function(value, feature) {
78
+ resolve(feature, !!value, true); // strict check
79
+ });
80
+ }
81
+
82
+ return caps;
83
+ }
84
+
85
+ /**
86
+ * @module plupload
87
+ * @static
88
+ */
89
+ var plupload = {
90
+ /**
91
+ * Plupload version will be replaced on build.
92
+ *
93
+ * @property VERSION
94
+ * @for Plupload
95
+ * @static
96
+ * @final
97
+ */
98
+ VERSION : '2.0.0beta',
99
+
100
+ /**
101
+ * Inital state of the queue and also the state ones it's finished all it's uploads.
102
+ *
103
+ * @property STOPPED
104
+ * @static
105
+ * @final
106
+ */
107
+ STOPPED : 1,
108
+
109
+ /**
110
+ * Upload process is running
111
+ *
112
+ * @property STARTED
113
+ * @static
114
+ * @final
115
+ */
116
+ STARTED : 2,
117
+
118
+ /**
119
+ * File is queued for upload
120
+ *
121
+ * @property QUEUED
122
+ * @static
123
+ * @final
124
+ */
125
+ QUEUED : 1,
126
+
127
+ /**
128
+ * File is being uploaded
129
+ *
130
+ * @property UPLOADING
131
+ * @static
132
+ * @final
133
+ */
134
+ UPLOADING : 2,
135
+
136
+ /**
137
+ * File has failed to be uploaded
138
+ *
139
+ * @property FAILED
140
+ * @static
141
+ * @final
142
+ */
143
+ FAILED : 4,
144
+
145
+ /**
146
+ * File has been uploaded successfully
147
+ *
148
+ * @property DONE
149
+ * @static
150
+ * @final
151
+ */
152
+ DONE : 5,
153
+
154
+ // Error constants used by the Error event
155
+
156
+ /**
157
+ * Generic error for example if an exception is thrown inside Silverlight.
158
+ *
159
+ * @property GENERIC_ERROR
160
+ * @static
161
+ * @final
162
+ */
163
+ GENERIC_ERROR : -100,
164
+
165
+ /**
166
+ * HTTP transport error. For example if the server produces a HTTP status other than 200.
167
+ *
168
+ * @property HTTP_ERROR
169
+ * @static
170
+ * @final
171
+ */
172
+ HTTP_ERROR : -200,
173
+
174
+ /**
175
+ * Generic I/O error. For exampe if it wasn't possible to open the file stream on local machine.
176
+ *
177
+ * @property IO_ERROR
178
+ * @static
179
+ * @final
180
+ */
181
+ IO_ERROR : -300,
182
+
183
+ /**
184
+ * Generic I/O error. For exampe if it wasn't possible to open the file stream on local machine.
185
+ *
186
+ * @property SECURITY_ERROR
187
+ * @static
188
+ * @final
189
+ */
190
+ SECURITY_ERROR : -400,
191
+
192
+ /**
193
+ * Initialization error. Will be triggered if no runtime was initialized.
194
+ *
195
+ * @property INIT_ERROR
196
+ * @static
197
+ * @final
198
+ */
199
+ INIT_ERROR : -500,
200
+
201
+ /**
202
+ * File size error. If the user selects a file that is too large it will be blocked and an error of this type will be triggered.
203
+ *
204
+ * @property FILE_SIZE_ERROR
205
+ * @static
206
+ * @final
207
+ */
208
+ FILE_SIZE_ERROR : -600,
209
+
210
+ /**
211
+ * File extension error. If the user selects a file that isn't valid according to the filters setting.
212
+ *
213
+ * @property FILE_EXTENSION_ERROR
214
+ * @static
215
+ * @final
216
+ */
217
+ FILE_EXTENSION_ERROR : -601,
218
+
219
+ /**
220
+ * Duplicate file error. If prevent_duplicates is set to true and user selects the same file again.
221
+ *
222
+ * @property FILE_DUPLICATE_ERROR
223
+ * @static
224
+ * @final
225
+ */
226
+ FILE_DUPLICATE_ERROR : -602,
227
+
228
+ /**
229
+ * Runtime will try to detect if image is proper one. Otherwise will throw this error.
230
+ *
231
+ * @property IMAGE_FORMAT_ERROR
232
+ * @static
233
+ * @final
234
+ */
235
+ IMAGE_FORMAT_ERROR : -700,
236
+
237
+ /**
238
+ * While working on the image runtime will try to detect if the operation may potentially run out of memeory and will throw this error.
239
+ *
240
+ * @property IMAGE_MEMORY_ERROR
241
+ * @static
242
+ * @final
243
+ */
244
+ IMAGE_MEMORY_ERROR : -701,
245
+
246
+ /**
247
+ * Each runtime has an upper limit on a dimension of the image it can handle. If bigger, will throw this error.
248
+ *
249
+ * @property IMAGE_DIMENSIONS_ERROR
250
+ * @static
251
+ * @final
252
+ */
253
+ IMAGE_DIMENSIONS_ERROR : -702,
254
+
255
+ /**
256
+ * Mime type lookup table.
257
+ *
258
+ * @property mimeTypes
259
+ * @type Object
260
+ * @final
261
+ */
262
+ mimeTypes : o.mimes,
263
+
264
+ /**
265
+ * In some cases sniffing is the only way around :(
266
+ */
267
+ ua: o.ua,
268
+
269
+ /**
270
+ * Gets the true type of the built-in object (better version of typeof).
271
+ * @credits Angus Croll (http://javascriptweblog.wordpress.com/)
272
+ *
273
+ * @method typeOf
274
+ * @static
275
+ * @param {Object} o Object to check.
276
+ * @return {String} Object [[Class]]
277
+ */
278
+ typeOf: o.typeOf,
279
+
280
+ /**
281
+ * Extends the specified object with another object.
282
+ *
283
+ * @method extend
284
+ * @static
285
+ * @param {Object} target Object to extend.
286
+ * @param {Object..} obj Multiple objects to extend with.
287
+ * @return {Object} Same as target, the extended object.
288
+ */
289
+ extend : o.extend,
290
+
291
+ /**
292
+ * Generates an unique ID. This is 99.99% unique since it takes the current time and 5 random numbers.
293
+ * The only way a user would be able to get the same ID is if the two persons at the same exact milisecond manages
294
+ * to get 5 the same random numbers between 0-65535 it also uses a counter so each call will be guaranteed to be page unique.
295
+ * It's more probable for the earth to be hit with an ansteriod. You can also if you want to be 100% sure set the plupload.guidPrefix property
296
+ * to an user unique key.
297
+ *
298
+ * @method guid
299
+ * @static
300
+ * @return {String} Virtually unique id.
301
+ */
302
+ guid : o.guid,
303
+
304
+ /**
305
+ * Executes the callback function for each item in array/object. If you return false in the
306
+ * callback it will break the loop.
307
+ *
308
+ * @method each
309
+ * @static
310
+ * @param {Object} obj Object to iterate.
311
+ * @param {function} callback Callback function to execute for each item.
312
+ */
313
+ each : o.each,
314
+
315
+ /**
316
+ * Returns the absolute x, y position of an Element. The position will be returned in a object with x, y fields.
317
+ *
318
+ * @method getPos
319
+ * @static
320
+ * @param {Element} node HTML element or element id to get x, y position from.
321
+ * @param {Element} root Optional root element to stop calculations at.
322
+ * @return {object} Absolute position of the specified element object with x, y fields.
323
+ */
324
+ getPos : o.getPos,
325
+
326
+ /**
327
+ * Returns the size of the specified node in pixels.
328
+ *
329
+ * @method getSize
330
+ * @static
331
+ * @param {Node} node Node to get the size of.
332
+ * @return {Object} Object with a w and h property.
333
+ */
334
+ getSize : o.getSize,
335
+
336
+ /**
337
+ * Encodes the specified string.
338
+ *
339
+ * @method xmlEncode
340
+ * @static
341
+ * @param {String} s String to encode.
342
+ * @return {String} Encoded string.
343
+ */
344
+ xmlEncode : function(str) {
345
+ var xmlEncodeChars = {'<' : 'lt', '>' : 'gt', '&' : 'amp', '"' : 'quot', '\'' : '#39'}, xmlEncodeRegExp = /[<>&\"\']/g;
346
+
347
+ return str ? ('' + str).replace(xmlEncodeRegExp, function(chr) {
348
+ return xmlEncodeChars[chr] ? '&' + xmlEncodeChars[chr] + ';' : chr;
349
+ }) : str;
350
+ },
351
+
352
+ /**
353
+ * Forces anything into an array.
354
+ *
355
+ * @method toArray
356
+ * @static
357
+ * @param {Object} obj Object with length field.
358
+ * @return {Array} Array object containing all items.
359
+ */
360
+ toArray : o.toArray,
361
+
362
+ /**
363
+ * Find an element in array and return it's index if present, otherwise return -1.
364
+ *
365
+ * @method inArray
366
+ * @static
367
+ * @param {mixed} needle Element to find
368
+ * @param {Array} array
369
+ * @return {Int} Index of the element, or -1 if not found
370
+ */
371
+ inArray : o.inArray,
372
+
373
+ /**
374
+ * Extends the language pack object with new items.
375
+ *
376
+ * @method addI18n
377
+ * @static
378
+ * @param {Object} pack Language pack items to add.
379
+ * @return {Object} Extended language pack object.
380
+ */
381
+ addI18n : o.addI18n,
382
+
383
+ /**
384
+ * Translates the specified string by checking for the english string in the language pack lookup.
385
+ *
386
+ * @method translate
387
+ * @static
388
+ * @param {String} str String to look for.
389
+ * @return {String} Translated string or the input string if it wasn't found.
390
+ */
391
+ translate : o.translate,
392
+
393
+ /**
394
+ * Checks if object is empty.
395
+ *
396
+ * @method isEmptyObj
397
+ * @static
398
+ * @param {Object} obj Object to check.
399
+ * @return {Boolean}
400
+ */
401
+ isEmptyObj : o.isEmptyObj,
402
+
403
+ /**
404
+ * Checks if specified DOM element has specified class.
405
+ *
406
+ * @method hasClass
407
+ * @static
408
+ * @param {Object} obj DOM element like object to add handler to.
409
+ * @param {String} name Class name
410
+ */
411
+ hasClass : o.hasClass,
412
+
413
+ /**
414
+ * Adds specified className to specified DOM element.
415
+ *
416
+ * @method addClass
417
+ * @static
418
+ * @param {Object} obj DOM element like object to add handler to.
419
+ * @param {String} name Class name
420
+ */
421
+ addClass : o.addClass,
422
+
423
+ /**
424
+ * Removes specified className from specified DOM element.
425
+ *
426
+ * @method removeClass
427
+ * @static
428
+ * @param {Object} obj DOM element like object to add handler to.
429
+ * @param {String} name Class name
430
+ */
431
+ removeClass : o.removeClass,
432
+
433
+ /**
434
+ * Returns a given computed style of a DOM element.
435
+ *
436
+ * @method getStyle
437
+ * @static
438
+ * @param {Object} obj DOM element like object.
439
+ * @param {String} name Style you want to get from the DOM element
440
+ */
441
+ getStyle : o.getStyle,
442
+
443
+ /**
444
+ * Adds an event handler to the specified object and store reference to the handler
445
+ * in objects internal Plupload registry (@see removeEvent).
446
+ *
447
+ * @method addEvent
448
+ * @static
449
+ * @param {Object} obj DOM element like object to add handler to.
450
+ * @param {String} name Name to add event listener to.
451
+ * @param {Function} callback Function to call when event occurs.
452
+ * @param {String} (optional) key that might be used to add specifity to the event record.
453
+ */
454
+ addEvent : o.addEvent,
455
+
456
+ /**
457
+ * Remove event handler from the specified object. If third argument (callback)
458
+ * is not specified remove all events with the specified name.
459
+ *
460
+ * @method removeEvent
461
+ * @static
462
+ * @param {Object} obj DOM element to remove event listener(s) from.
463
+ * @param {String} name Name of event listener to remove.
464
+ * @param {Function|String} (optional) might be a callback or unique key to match.
465
+ */
466
+ removeEvent: o.removeEvent,
467
+
468
+ /**
469
+ * Remove all kind of events from the specified object
470
+ *
471
+ * @method removeAllEvents
472
+ * @static
473
+ * @param {Object} obj DOM element to remove event listeners from.
474
+ * @param {String} (optional) unique key to match, when removing events.
475
+ */
476
+ removeAllEvents: o.removeAllEvents,
477
+
478
+ /**
479
+ * Cleans the specified name from national characters (diacritics). The result will be a name with only a-z, 0-9 and _.
480
+ *
481
+ * @method cleanName
482
+ * @static
483
+ * @param {String} s String to clean up.
484
+ * @return {String} Cleaned string.
485
+ */
486
+ cleanName : function(name) {
487
+ var i, lookup;
488
+
489
+ // Replace diacritics
490
+ lookup = [
491
+ /[\300-\306]/g, 'A', /[\340-\346]/g, 'a',
492
+ /\307/g, 'C', /\347/g, 'c',
493
+ /[\310-\313]/g, 'E', /[\350-\353]/g, 'e',
494
+ /[\314-\317]/g, 'I', /[\354-\357]/g, 'i',
495
+ /\321/g, 'N', /\361/g, 'n',
496
+ /[\322-\330]/g, 'O', /[\362-\370]/g, 'o',
497
+ /[\331-\334]/g, 'U', /[\371-\374]/g, 'u'
498
+ ];
499
+
500
+ for (i = 0; i < lookup.length; i += 2) {
501
+ name = name.replace(lookup[i], lookup[i + 1]);
502
+ }
503
+
504
+ // Replace whitespace
505
+ name = name.replace(/\s+/g, '_');
506
+
507
+ // Remove anything else
508
+ name = name.replace(/[^a-z0-9_\-\.]+/gi, '');
509
+
510
+ return name;
511
+ },
512
+
513
+ /**
514
+ * Builds a full url out of a base URL and an object with items to append as query string items.
515
+ *
516
+ * @method buildUrl
517
+ * @static
518
+ * @param {String} url Base URL to append query string items to.
519
+ * @param {Object} items Name/value object to serialize as a querystring.
520
+ * @return {String} String with url + serialized query string items.
521
+ */
522
+ buildUrl : function(url, items) {
523
+ var query = '';
524
+
525
+ plupload.each(items, function(value, name) {
526
+ query += (query ? '&' : '') + encodeURIComponent(name) + '=' + encodeURIComponent(value);
527
+ });
528
+
529
+ if (query) {
530
+ url += (url.indexOf('?') > 0 ? '&' : '?') + query;
531
+ }
532
+
533
+ return url;
534
+ },
535
+
536
+ /**
537
+ * Formats the specified number as a size string for example 1024 becomes 1 KB.
538
+ *
539
+ * @method formatSize
540
+ * @static
541
+ * @param {Number} size Size to format as string.
542
+ * @return {String} Formatted size string.
543
+ */
544
+ formatSize : function(size) {
545
+ if (size === undef || /\D/.test(size)) {
546
+ return plupload.translate('N/A');
547
+ }
548
+
549
+ // TB
550
+ if (size > 1099511627776) {
551
+ return Math.round(size / 1099511627776, 1) + " " + plupload.translate('tb');
552
+ }
553
+
554
+ // GB
555
+ if (size > 1073741824) {
556
+ return Math.round(size / 1073741824, 1) + " " + plupload.translate('gb');
557
+ }
558
+
559
+ // MB
560
+ if (size > 1048576) {
561
+ return Math.round(size / 1048576, 1) + " " + plupload.translate('mb');
562
+ }
563
+
564
+ // KB
565
+ if (size > 1024) {
566
+ return Math.round(size / 1024, 1) + " " + plupload.translate('kb');
567
+ }
568
+
569
+ return size + " " + plupload.translate('b');
570
+ },
571
+
572
+
573
+ /**
574
+ * Parses the specified size string into a byte value. For example 10kb becomes 10240.
575
+ *
576
+ * @method parseSize
577
+ * @static
578
+ * @param {String|Number} size String to parse or number to just pass through.
579
+ * @return {Number} Size in bytes.
580
+ */
581
+ parseSize : o.parseSizeStr,
582
+
583
+
584
+ /**
585
+ * A way to predict what runtime will be choosen in the current environment with the
586
+ * specified settings.
587
+ *
588
+ * @method predictRuntime
589
+ * @static
590
+ * @param {Object|String} config Plupload settings to check
591
+ * @param {String} [runtimes] Comma-separated list of runtimes to check against
592
+ * @return {String} Type of compatible runtime
593
+ */
594
+ predictRuntime : function(config, runtimes) {
595
+ var up, runtime;
596
+ if (runtimes) {
597
+ config.runtimes = runtimes;
598
+ }
599
+ up = new plupload.Uploader(config);
600
+ runtime = up.runtime;
601
+ up.destroy();
602
+ return runtime;
603
+ },
604
+
605
+ /**
606
+ * Registers a filter that will be executed for each file added to the queue.
607
+ * If callback returns false, file will not be added.
608
+ *
609
+ * Callback receives two arguments: a value for the filter as it was specified in settings.filters
610
+ * and a file to be filtered. Callback is executed in the context of uploader instance.
611
+ *
612
+ * @method addFileFilter
613
+ * @static
614
+ * @param {String} name Name of the filter by which it can be referenced in settings.filters
615
+ * @param {String} cb Callback - the actual routine that every added file must pass
616
+ */
617
+ addFileFilter: function(name, cb) {
618
+ fileFilters[name] = cb;
619
+ }
620
+ };
621
+
622
+
623
+ plupload.addFileFilter('mime_types', (function() {
624
+ var _filters, _extRegExp;
625
+
626
+ // Convert extensions to regexp
627
+ function getExtRegExp(filters) {
628
+ var extensionsRegExp = [];
629
+
630
+ plupload.each(filters, function(filter) {
631
+ plupload.each(filter.extensions.split(/,/), function(ext) {
632
+ if (/^\s*\*\s*$/.test(ext)) {
633
+ extensionsRegExp.push('\\.*');
634
+ } else {
635
+ extensionsRegExp.push('\\.' + ext.replace(new RegExp('[' + ('/^$.*+?|()[]{}\\'.replace(/./g, '\\$&')) + ']', 'g'), '\\$&'));
636
+ }
637
+ });
638
+ });
639
+
640
+ return new RegExp('(' + extensionsRegExp.join('|') + ')$', 'i');
641
+ }
642
+
643
+ return function(filters, file, cb) {
644
+ if (!_extRegExp || filters != _filters) { // make sure we do it only once, unless filters got changed
645
+ _extRegExp = getExtRegExp(filters);
646
+ _filters = [].slice.call(filters);
647
+ }
648
+
649
+ if (!_extRegExp.test(file.name)) {
650
+ this.trigger('Error', {
651
+ code : plupload.FILE_EXTENSION_ERROR,
652
+ message : plupload.translate('File extension error.'),
653
+ file : file
654
+ });
655
+ cb(false);
656
+ } else {
657
+ cb(true);
658
+ }
659
+ };
660
+ }()));
661
+
662
+
663
+ plupload.addFileFilter('max_file_size', function(maxSize, file, cb) {
664
+ var undef;
665
+
666
+ // Invalid file size
667
+ if (file.size !== undef && maxSize && file.size > maxSize) {
668
+ this.trigger('Error', {
669
+ code : plupload.FILE_SIZE_ERROR,
670
+ message : plupload.translate('File size error.'),
671
+ file : file
672
+ });
673
+ cb(false);
674
+ } else {
675
+ cb(true);
676
+ }
677
+ });
678
+
679
+
680
+ plupload.addFileFilter('prevent_duplicates', function(value, file, cb) {
681
+ if (value) {
682
+ var ii = this.files.length;
683
+ while (ii--) {
684
+ // Compare by name and size (size might be 0 or undefined, but still equivalent for both)
685
+ if (file.name === this.files[ii].name && file.size === this.files[ii].size) {
686
+ this.trigger('Error', {
687
+ code : plupload.FILE_DUPLICATE_ERROR,
688
+ message : plupload.translate('Duplicate file error.'),
689
+ file : file
690
+ });
691
+ cb(false);
692
+ return;
693
+ }
694
+ }
695
+ }
696
+ cb(true);
697
+ });
698
+
699
+
700
+ /**
701
+ @class Uploader
702
+ @constructor
703
+
704
+ @param {Object} settings For detailed information about each option check documentation.
705
+ @param {String|DOMElement} settings.browse_button id of the DOM element or DOM element itself to use as file dialog trigger.
706
+ @param {String} settings.url URL of the server-side upload handler.
707
+ @param {Number|String} [settings.chunk_size=0] Chunk size in bytes to slice the file into. Shorcuts with b, kb, mb, gb, tb suffixes also supported. `e.g. 204800 or "204800b" or "200kb"`. By default - disabled.
708
+ @param {String} [settings.container] id of the DOM element to use as a container for uploader structures. Defaults to document.body.
709
+ @param {String|DOMElement} [settings.drop_element] id of the DOM element or DOM element itself to use as a drop zone for Drag-n-Drop.
710
+ @param {String} [settings.file_data_name="file"] Name for the file field in Multipart formated message.
711
+ @param {Object} [settings.filters={}] Set of file type filters.
712
+ @param {Array} [settings.filters.mime_types=[]] List of file types to accept, each one defined by title and list of extensions. `e.g. {title : "Image files", extensions : "jpg,jpeg,gif,png"}`. Dispatches `plupload.FILE_EXTENSION_ERROR`
713
+ @param {String|Number} [settings.filters.max_file_size=0] Maximum file size that the user can pick, in bytes. Optionally supports b, kb, mb, gb, tb suffixes. `e.g. "10mb" or "1gb"`. By default - not set. Dispatches `plupload.FILE_SIZE_ERROR`.
714
+ @param {Boolean} [settings.filters.prevent_duplicates=false] Do not let duplicates into the queue. Dispatches `plupload.FILE_DUPLICATE_ERROR`.
715
+ @param {String} [settings.flash_swf_url] URL of the Flash swf.
716
+ @param {Object} [settings.headers] Custom headers to send with the upload. Hash of name/value pairs.
717
+ @param {Number} [settings.max_retries=0] How many times to retry the chunk or file, before triggering Error event.
718
+ @param {Boolean} [settings.multipart=true] Whether to send file and additional parameters as Multipart formated message.
719
+ @param {Object} [settings.multipart_params] Hash of key/value pairs to send with every file upload.
720
+ @param {Boolean} [settings.multi_selection=true] Enable ability to select multiple files at once in file dialog.
721
+ @param {String|Object} [settings.required_features] Either comma-separated list or hash of required features that chosen runtime should absolutely possess.
722
+ @param {Object} [settings.resize] Enable resizng of images on client-side. Applies to `image/jpeg` and `image/png` only. `e.g. {width : 200, height : 200, quality : 90, crop: true}`
723
+ @param {Number} [settings.resize.width] If image is bigger, it will be resized.
724
+ @param {Number} [settings.resize.height] If image is bigger, it will be resized.
725
+ @param {Number} [settings.resize.quality=90] Compression quality for jpegs (1-100).
726
+ @param {Boolean} [settings.resize.crop=false] Whether to crop images to exact dimensions. By default they will be resized proportionally.
727
+ @param {String} [settings.runtimes="html5,flash,silverlight,html4"] Comma separated list of runtimes, that Plupload will try in turn, moving to the next if previous fails.
728
+ @param {String} [settings.silverlight_xap_url] URL of the Silverlight xap.
729
+ @param {Boolean} [settings.unique_names=false] If true will generate unique filenames for uploaded files.
730
+ */
731
+ plupload.Uploader = function(settings) {
732
+ /**
733
+ * Fires when the current RunTime has been initialized.
734
+ *
735
+ * @event Init
736
+ * @param {plupload.Uploader} uploader Uploader instance sending the event.
737
+ */
738
+
739
+ /**
740
+ * Fires after the init event incase you need to perform actions there.
741
+ *
742
+ * @event PostInit
743
+ * @param {plupload.Uploader} uploader Uploader instance sending the event.
744
+ */
745
+
746
+ /**
747
+ * Fires when the silverlight/flash or other shim needs to move.
748
+ *
749
+ * @event Refresh
750
+ * @param {plupload.Uploader} uploader Uploader instance sending the event.
751
+ */
752
+
753
+ /**
754
+ * Fires when the overall state is being changed for the upload queue.
755
+ *
756
+ * @event StateChanged
757
+ * @param {plupload.Uploader} uploader Uploader instance sending the event.
758
+ */
759
+
760
+ /**
761
+ * Fires when a file is to be uploaded by the runtime.
762
+ *
763
+ * @event UploadFile
764
+ * @param {plupload.Uploader} uploader Uploader instance sending the event.
765
+ * @param {plupload.File} file File to be uploaded.
766
+ */
767
+
768
+ /**
769
+ * Fires when just before a file is uploaded. This event enables you to override settings
770
+ * on the uploader instance before the file is uploaded.
771
+ *
772
+ * @event BeforeUpload
773
+ * @param {plupload.Uploader} uploader Uploader instance sending the event.
774
+ * @param {plupload.File} file File to be uploaded.
775
+ */
776
+
777
+ /**
778
+ * Fires when the file queue is changed. In other words when files are added/removed to the files array of the uploader instance.
779
+ *
780
+ * @event QueueChanged
781
+ * @param {plupload.Uploader} uploader Uploader instance sending the event.
782
+ */
783
+
784
+ /**
785
+ * Fires while a file is being uploaded. Use this event to update the current file upload progress.
786
+ *
787
+ * @event UploadProgress
788
+ * @param {plupload.Uploader} uploader Uploader instance sending the event.
789
+ * @param {plupload.File} file File that is currently being uploaded.
790
+ */
791
+
792
+ /**
793
+ * Fires while a file was removed from queue.
794
+ *
795
+ * @event FilesRemoved
796
+ * @param {plupload.Uploader} uploader Uploader instance sending the event.
797
+ * @param {Array} files Array of files that got removed.
798
+ */
799
+
800
+ /**
801
+ * Fires after files were filtered and added to the queue.
802
+ *
803
+ * @event FilesAdded
804
+ * @param {plupload.Uploader} uploader Uploader instance sending the event.
805
+ * @param {Array} files Array of file objects that were added to queue by the user.
806
+ */
807
+
808
+ /**
809
+ * Fires when a file is successfully uploaded.
810
+ *
811
+ * @event FileUploaded
812
+ * @param {plupload.Uploader} uploader Uploader instance sending the event.
813
+ * @param {plupload.File} file File that was uploaded.
814
+ * @param {Object} response Object with response properties.
815
+ */
816
+
817
+ /**
818
+ * Fires when file chunk is uploaded.
819
+ *
820
+ * @event ChunkUploaded
821
+ * @param {plupload.Uploader} uploader Uploader instance sending the event.
822
+ * @param {plupload.File} file File that the chunk was uploaded for.
823
+ * @param {Object} response Object with response properties.
824
+ */
825
+
826
+ /**
827
+ * Fires when all files in a queue are uploaded.
828
+ *
829
+ * @event UploadComplete
830
+ * @param {plupload.Uploader} uploader Uploader instance sending the event.
831
+ * @param {Array} files Array of file objects that was added to queue/selected by the user.
832
+ */
833
+
834
+ /**
835
+ * Fires when a error occurs.
836
+ *
837
+ * @event Error
838
+ * @param {plupload.Uploader} uploader Uploader instance sending the event.
839
+ * @param {Object} error Contains code, message and sometimes file and other details.
840
+ */
841
+
842
+ /**
843
+ * Fires when destroy method is called.
844
+ *
845
+ * @event Destroy
846
+ * @param {plupload.Uploader} uploader Uploader instance sending the event.
847
+ */
848
+ var files = [], events = {}, required_caps = {},
849
+ startTime, total, disabled = false,
850
+ fileInput, fileDrop, xhr;
851
+
852
+
853
+ // Private methods
854
+ function uploadNext() {
855
+ var file, count = 0, i;
856
+
857
+ if (this.state == plupload.STARTED) {
858
+ // Find first QUEUED file
859
+ for (i = 0; i < files.length; i++) {
860
+ if (!file && files[i].status == plupload.QUEUED) {
861
+ file = files[i];
862
+ if (this.trigger("BeforeUpload", file)) {
863
+ file.status = plupload.UPLOADING;
864
+ this.trigger("UploadFile", file);
865
+ }
866
+ } else {
867
+ count++;
868
+ }
869
+ }
870
+
871
+ // All files are DONE or FAILED
872
+ if (count == files.length) {
873
+ if (this.state !== plupload.STOPPED) {
874
+ this.state = plupload.STOPPED;
875
+ this.trigger("StateChanged");
876
+ }
877
+ this.trigger("UploadComplete", files);
878
+ }
879
+ }
880
+ }
881
+
882
+ function calcFile(file) {
883
+ file.percent = file.size > 0 ? Math.ceil(file.loaded / file.size * 100) : 100;
884
+ calc();
885
+ }
886
+
887
+ function calc() {
888
+ var i, file;
889
+
890
+ // Reset stats
891
+ total.reset();
892
+
893
+ // Check status, size, loaded etc on all files
894
+ for (i = 0; i < files.length; i++) {
895
+ file = files[i];
896
+
897
+ if (file.size !== undef) {
898
+ // We calculate totals based on original file size
899
+ total.size += file.origSize;
900
+
901
+ // Since we cannot predict file size after resize, we do opposite and
902
+ // interpolate loaded amount to match magnitude of total
903
+ total.loaded += file.loaded * file.origSize / file.size;
904
+ } else {
905
+ total.size = undef;
906
+ }
907
+
908
+ if (file.status == plupload.DONE) {
909
+ total.uploaded++;
910
+ } else if (file.status == plupload.FAILED) {
911
+ total.failed++;
912
+ } else {
913
+ total.queued++;
914
+ }
915
+ }
916
+
917
+ // If we couldn't calculate a total file size then use the number of files to calc percent
918
+ if (total.size === undef) {
919
+ total.percent = files.length > 0 ? Math.ceil(total.uploaded / files.length * 100) : 0;
920
+ } else {
921
+ total.bytesPerSec = Math.ceil(total.loaded / ((+new Date() - startTime || 1) / 1000.0));
922
+ total.percent = total.size > 0 ? Math.ceil(total.loaded / total.size * 100) : 0;
923
+ }
924
+ }
925
+
926
+ function initControls() {
927
+ var self = this, initialized = 0;
928
+
929
+ // common settings
930
+ var options = {
931
+ accept: settings.filters.mime_types,
932
+ runtime_order: settings.runtimes,
933
+ required_caps: required_caps,
934
+ swf_url: settings.flash_swf_url,
935
+ xap_url: settings.silverlight_xap_url
936
+ };
937
+
938
+ // add runtime specific options if any
939
+ plupload.each(settings.runtimes.split(/\s*,\s*/), function(runtime) {
940
+ if (settings[runtime]) {
941
+ options[runtime] = settings[runtime];
942
+ }
943
+ });
944
+
945
+ o.inSeries([
946
+ function(cb) {
947
+ // Initialize file dialog trigger
948
+ if (settings.browse_button) {
949
+ fileInput = new o.FileInput(plupload.extend({}, options, {
950
+ name: settings.file_data_name,
951
+ multiple: settings.multi_selection,
952
+ container: settings.container,
953
+ browse_button: settings.browse_button
954
+ }));
955
+
956
+ fileInput.onready = function() {
957
+ var info = o.Runtime.getInfo(this.ruid);
958
+
959
+ // for backward compatibility
960
+ o.extend(self.features, {
961
+ chunks: info.can('slice_blob'),
962
+ multipart: info.can('send_multipart'),
963
+ multi_selection: info.can('select_multiple')
964
+ });
965
+
966
+ initialized++;
967
+ cb();
968
+ };
969
+
970
+ fileInput.onchange = function() {
971
+ self.addFile(this.files);
972
+ };
973
+
974
+ fileInput.bind('mouseenter mouseleave mousedown mouseup', function(e) {
975
+ if (!disabled) {
976
+ var bButton = o.get(settings.browse_button);
977
+ if (bButton) {
978
+ if (settings.browse_button_hover) {
979
+ if ('mouseenter' === e.type) {
980
+ o.addClass(bButton, settings.browse_button_hover);
981
+ } else if ('mouseleave' === e.type) {
982
+ o.removeClass(bButton, settings.browse_button_hover);
983
+ }
984
+ }
985
+
986
+ if (settings.browse_button_active) {
987
+ if ('mousedown' === e.type) {
988
+ o.addClass(bButton, settings.browse_button_active);
989
+ } else if ('mouseup' === e.type) {
990
+ o.removeClass(bButton, settings.browse_button_active);
991
+ }
992
+ }
993
+ bButton = null;
994
+ }
995
+ }
996
+ });
997
+
998
+ fileInput.bind('error runtimeerror', function() {
999
+ fileInput = null;
1000
+ cb();
1001
+ });
1002
+
1003
+ fileInput.init();
1004
+ } else {
1005
+ cb();
1006
+ }
1007
+ },
1008
+
1009
+ function(cb) {
1010
+ // Initialize drag/drop interface if requested
1011
+ if (settings.drop_element) {
1012
+ fileDrop = new o.FileDrop(plupload.extend({}, options, {
1013
+ drop_zone: settings.drop_element
1014
+ }));
1015
+
1016
+ fileDrop.onready = function() {
1017
+ var info = o.Runtime.getInfo(this.ruid);
1018
+
1019
+ self.features.dragdrop = info.can('drag_and_drop');
1020
+
1021
+ initialized++;
1022
+ cb();
1023
+ };
1024
+
1025
+ fileDrop.ondrop = function() {
1026
+ self.addFile(this.files);
1027
+ };
1028
+
1029
+ fileDrop.bind('error runtimeerror', function() {
1030
+ fileDrop = null;
1031
+ cb();
1032
+ });
1033
+
1034
+ fileDrop.init();
1035
+ } else {
1036
+ cb();
1037
+ }
1038
+ }
1039
+ ],
1040
+ function() {
1041
+ if (typeof(settings.init) == "function") {
1042
+ settings.init(self);
1043
+ } else {
1044
+ plupload.each(settings.init, function(func, name) {
1045
+ self.bind(name, func);
1046
+ });
1047
+ }
1048
+
1049
+ if (initialized) {
1050
+ self.trigger('PostInit');
1051
+ } else {
1052
+ self.trigger('Error', {
1053
+ code : plupload.INIT_ERROR,
1054
+ message : plupload.translate('Init error.')
1055
+ });
1056
+ }
1057
+ });
1058
+ }
1059
+
1060
+ function runtimeCan(file, cap) {
1061
+ if (file.ruid) {
1062
+ var info = o.Runtime.getInfo(file.ruid);
1063
+ if (info) {
1064
+ return info.can(cap);
1065
+ }
1066
+ }
1067
+ return false;
1068
+ }
1069
+
1070
+ function resizeImage(blob, params, cb) {
1071
+ var img = new o.Image();
1072
+
1073
+ try {
1074
+ img.onload = function() {
1075
+ img.downsize(params.width, params.height, params.crop, params.preserve_headers);
1076
+ };
1077
+
1078
+ img.onresize = function() {
1079
+ cb(this.getAsBlob(blob.type, params.quality));
1080
+ this.destroy();
1081
+ };
1082
+
1083
+ img.onerror = function() {
1084
+ cb(blob);
1085
+ };
1086
+
1087
+ img.load(blob);
1088
+ } catch(ex) {
1089
+ cb(blob);
1090
+ }
1091
+ }
1092
+
1093
+
1094
+ // Inital total state
1095
+ total = new plupload.QueueProgress();
1096
+
1097
+ // Default settings
1098
+ settings = plupload.extend({
1099
+ runtimes: o.Runtime.order,
1100
+ max_retries: 0,
1101
+ multipart : true,
1102
+ multi_selection : true,
1103
+ file_data_name : 'file',
1104
+ flash_swf_url : 'js/Moxie.swf',
1105
+ silverlight_xap_url : 'js/Moxie.xap',
1106
+ send_chunk_number: true // whether to send chunks and chunk numbers, or total and offset bytes
1107
+ }, settings);
1108
+
1109
+ // Resize defaults
1110
+ if (settings.resize) {
1111
+ settings.resize = plupload.extend({
1112
+ preserve_headers: true,
1113
+ crop: false
1114
+ }, settings.resize);
1115
+ }
1116
+
1117
+ // Set file filters
1118
+ if (plupload.typeOf(settings.filters) === 'array') {
1119
+ settings.filters = {
1120
+ mime_types: settings.filters
1121
+ };
1122
+ }
1123
+ settings.filters = plupload.extend({
1124
+ mime_types: [],
1125
+ prevent_duplicates: !!settings.prevent_duplicates,
1126
+ max_file_size: settings.max_file_size
1127
+ }, settings.filters);
1128
+
1129
+
1130
+ // Convert settings
1131
+ settings.filters.max_file_size = plupload.parseSize(settings.filters.max_file_size) || 0;
1132
+ settings.chunk_size = plupload.parseSize(settings.chunk_size) || 0;
1133
+
1134
+ // Normalize the list of required capabilities
1135
+ settings.required_features = required_caps = normalizeCaps(plupload.extend({}, settings));
1136
+
1137
+
1138
+ // Add public methods
1139
+ plupload.extend(this, {
1140
+
1141
+ /**
1142
+ * Unique id for the Uploader instance.
1143
+ *
1144
+ * @property id
1145
+ * @type String
1146
+ */
1147
+ id : plupload.guid(),
1148
+
1149
+ /**
1150
+ * Current state of the total uploading progress. This one can either be plupload.STARTED or plupload.STOPPED.
1151
+ * These states are controlled by the stop/start methods. The default value is STOPPED.
1152
+ *
1153
+ * @property state
1154
+ * @type Number
1155
+ */
1156
+ state : plupload.STOPPED,
1157
+
1158
+ /**
1159
+ * Map of features that are available for the uploader runtime. Features will be filled
1160
+ * before the init event is called, these features can then be used to alter the UI for the end user.
1161
+ * Some of the current features that might be in this map is: dragdrop, chunks, jpgresize, pngresize.
1162
+ *
1163
+ * @property features
1164
+ * @type Object
1165
+ */
1166
+ features : {},
1167
+
1168
+ /**
1169
+ * Current runtime name.
1170
+ *
1171
+ * @property runtime
1172
+ * @type String
1173
+ */
1174
+ runtime : o.Runtime.thatCan(required_caps, settings.runtimes), // predict runtime
1175
+
1176
+ /**
1177
+ * Current upload queue, an array of File instances.
1178
+ *
1179
+ * @property files
1180
+ * @type Array
1181
+ * @see plupload.File
1182
+ */
1183
+ files : files,
1184
+
1185
+ /**
1186
+ * Object with name/value settings.
1187
+ *
1188
+ * @property settings
1189
+ * @type Object
1190
+ */
1191
+ settings : settings,
1192
+
1193
+ /**
1194
+ * Total progess information. How many files has been uploaded, total percent etc.
1195
+ *
1196
+ * @property total
1197
+ * @type plupload.QueueProgress
1198
+ */
1199
+ total : total,
1200
+
1201
+
1202
+ /**
1203
+ * Initializes the Uploader instance and adds internal event listeners.
1204
+ *
1205
+ * @method init
1206
+ */
1207
+ init : function() {
1208
+ var self = this;
1209
+
1210
+ settings.browse_button = o.get(settings.browse_button);
1211
+
1212
+ // Check if drop zone requested
1213
+ settings.drop_element = o.get(settings.drop_element);
1214
+
1215
+
1216
+ if (typeof(settings.preinit) == "function") {
1217
+ settings.preinit(self);
1218
+ } else {
1219
+ plupload.each(settings.preinit, function(func, name) {
1220
+ self.bind(name, func);
1221
+ });
1222
+ }
1223
+
1224
+
1225
+ // Check for required options
1226
+ if (!settings.browse_button || !settings.url) {
1227
+ this.trigger('Error', {
1228
+ code : plupload.INIT_ERROR,
1229
+ message : plupload.translate('Init error.')
1230
+ });
1231
+ return;
1232
+ }
1233
+
1234
+
1235
+ self.bind("FilesAdded", function(up, filteredFiles) {
1236
+ // Add files to queue
1237
+ [].push.apply(files, filteredFiles);
1238
+
1239
+ delay(function() {
1240
+ self.trigger("QueueChanged");
1241
+ self.refresh();
1242
+ }, 1);
1243
+ });
1244
+
1245
+ self.bind("CancelUpload", function() {
1246
+ if (xhr) {
1247
+ xhr.abort();
1248
+ }
1249
+ });
1250
+
1251
+ // Generate unique target filenames
1252
+ if (settings.unique_names) {
1253
+ self.bind("BeforeUpload", function(up, file) {
1254
+ var matches = file.name.match(/\.([^.]+)$/), ext = "part";
1255
+ if (matches) {
1256
+ ext = matches[1];
1257
+ }
1258
+ file.target_name = file.id + '.' + ext;
1259
+ });
1260
+ }
1261
+
1262
+ self.bind("UploadFile", function(up, file) {
1263
+ var url = up.settings.url, features = up.features, chunkSize = settings.chunk_size,
1264
+ retries = settings.max_retries,
1265
+ blob, offset = 0;
1266
+
1267
+ // make sure we start at a predictable offset
1268
+ if (file.loaded) {
1269
+ offset = file.loaded = chunkSize * Math.floor(file.loaded / chunkSize);
1270
+ }
1271
+
1272
+ function handleError() {
1273
+ if (retries-- > 0) {
1274
+ delay(uploadNextChunk, 1);
1275
+ } else {
1276
+ file.loaded = offset; // reset all progress
1277
+
1278
+ up.trigger('Error', {
1279
+ code : plupload.HTTP_ERROR,
1280
+ message : plupload.translate('HTTP Error.'),
1281
+ file : file,
1282
+ response : xhr.responseText,
1283
+ status : xhr.status,
1284
+ responseHeaders: xhr.getAllResponseHeaders()
1285
+ });
1286
+ }
1287
+ }
1288
+
1289
+ function uploadNextChunk() {
1290
+ var chunkBlob, formData, args, curChunkSize;
1291
+
1292
+ // File upload finished
1293
+ if (file.status == plupload.DONE || file.status == plupload.FAILED || up.state == plupload.STOPPED) {
1294
+ return;
1295
+ }
1296
+
1297
+ // Standard arguments
1298
+ args = {name : file.target_name || file.name};
1299
+
1300
+ if (chunkSize && features.chunks && blob.size > chunkSize) { // blob will be of type string if it was loaded in memory
1301
+ curChunkSize = Math.min(chunkSize, blob.size - offset);
1302
+ chunkBlob = blob.slice(offset, offset + curChunkSize);
1303
+ } else {
1304
+ curChunkSize = blob.size;
1305
+ chunkBlob = blob;
1306
+ }
1307
+
1308
+ // If chunking is enabled add corresponding args, no matter if file is bigger than chunk or smaller
1309
+ if (chunkSize && features.chunks) {
1310
+ // Setup query string arguments
1311
+ if (settings.send_chunk_number) {
1312
+ args.chunk = Math.ceil(offset / chunkSize);
1313
+ args.chunks = Math.ceil(blob.size / chunkSize);
1314
+ } else { // keep support for experimental chunk format, just in case
1315
+ args.offset = offset;
1316
+ args.total = blob.size;
1317
+ }
1318
+ }
1319
+
1320
+ xhr = new o.XMLHttpRequest();
1321
+
1322
+ // Do we have upload progress support
1323
+ if (xhr.upload) {
1324
+ xhr.upload.onprogress = function(e) {
1325
+ file.loaded = Math.min(file.size, offset + e.loaded);
1326
+ up.trigger('UploadProgress', file);
1327
+ };
1328
+ }
1329
+
1330
+ xhr.onload = function() {
1331
+ // check if upload made itself through
1332
+ if (xhr.status >= 400) {
1333
+ handleError();
1334
+ return;
1335
+ }
1336
+
1337
+ // Handle chunk response
1338
+ if (curChunkSize < blob.size) {
1339
+ chunkBlob.destroy();
1340
+
1341
+ offset += curChunkSize;
1342
+ file.loaded = Math.min(offset, blob.size);
1343
+
1344
+ up.trigger('ChunkUploaded', file, {
1345
+ offset : file.loaded,
1346
+ total : blob.size,
1347
+ response : xhr.responseText,
1348
+ status : xhr.status,
1349
+ responseHeaders: xhr.getAllResponseHeaders()
1350
+ });
1351
+
1352
+ // stock Android browser doesn't fire upload progress events, but in chunking mode we can fake them
1353
+ if (o.Env.browser === 'Android Browser') {
1354
+ // doesn't harm in general, but is not required anywhere else
1355
+ up.trigger('UploadProgress', file);
1356
+ }
1357
+ } else {
1358
+ file.loaded = file.size;
1359
+ }
1360
+
1361
+ chunkBlob = formData = null; // Free memory
1362
+
1363
+ // Check if file is uploaded
1364
+ if (!offset || offset >= blob.size) {
1365
+ // If file was modified, destory the copy
1366
+ if (file.size != file.origSize) {
1367
+ blob.destroy();
1368
+ blob = null;
1369
+ }
1370
+
1371
+ up.trigger('UploadProgress', file);
1372
+
1373
+ file.status = plupload.DONE;
1374
+
1375
+ up.trigger('FileUploaded', file, {
1376
+ response : xhr.responseText,
1377
+ status : xhr.status,
1378
+ responseHeaders: xhr.getAllResponseHeaders()
1379
+ });
1380
+ } else {
1381
+ // Still chunks left
1382
+ delay(uploadNextChunk, 1); // run detached, otherwise event handlers interfere
1383
+ }
1384
+ };
1385
+
1386
+ xhr.onerror = function() {
1387
+ handleError();
1388
+ };
1389
+
1390
+ xhr.onloadend = function() {
1391
+ this.destroy();
1392
+ xhr = null;
1393
+ };
1394
+
1395
+ // Build multipart request
1396
+ if (up.settings.multipart && features.multipart) {
1397
+
1398
+ args.name = file.target_name || file.name;
1399
+
1400
+ xhr.open("post", url, true);
1401
+
1402
+ // Set custom headers
1403
+ plupload.each(up.settings.headers, function(value, name) {
1404
+ xhr.setRequestHeader(name, value);
1405
+ });
1406
+
1407
+ formData = new o.FormData();
1408
+
1409
+ // Add multipart params
1410
+ plupload.each(plupload.extend(args, up.settings.multipart_params), function(value, name) {
1411
+ formData.append(name, value);
1412
+ });
1413
+
1414
+ // Add file and send it
1415
+ formData.append(up.settings.file_data_name, chunkBlob);
1416
+ xhr.send(formData, {
1417
+ runtime_order: up.settings.runtimes,
1418
+ required_caps: required_caps,
1419
+ swf_url: up.settings.flash_swf_url,
1420
+ xap_url: up.settings.silverlight_xap_url
1421
+ });
1422
+ } else {
1423
+ // if no multipart, send as binary stream
1424
+ url = plupload.buildUrl(up.settings.url, plupload.extend(args, up.settings.multipart_params));
1425
+
1426
+ xhr.open("post", url, true);
1427
+
1428
+ xhr.setRequestHeader('Content-Type', 'application/octet-stream'); // Binary stream header
1429
+
1430
+ // Set custom headers
1431
+ plupload.each(up.settings.headers, function(value, name) {
1432
+ xhr.setRequestHeader(name, value);
1433
+ });
1434
+
1435
+ xhr.send(chunkBlob, {
1436
+ runtime_order: up.settings.runtimes,
1437
+ required_caps: required_caps,
1438
+ swf_url: up.settings.flash_swf_url,
1439
+ xap_url: up.settings.silverlight_xap_url
1440
+ });
1441
+ }
1442
+ }
1443
+
1444
+ blob = file.getSource();
1445
+
1446
+ // Start uploading chunks
1447
+ if (!o.isEmptyObj(up.settings.resize) && runtimeCan(blob, 'send_binary_string') && !!~o.inArray(blob.type, ['image/jpeg', 'image/png'])) {
1448
+ // Resize if required
1449
+ resizeImage.call(this, blob, up.settings.resize, function(resizedBlob) {
1450
+ blob = resizedBlob;
1451
+ file.size = resizedBlob.size;
1452
+ uploadNextChunk();
1453
+ });
1454
+ } else {
1455
+ uploadNextChunk();
1456
+ }
1457
+ });
1458
+
1459
+ self.bind('UploadProgress', function(up, file) {
1460
+ calcFile(file);
1461
+ });
1462
+
1463
+ self.bind('StateChanged', function(up) {
1464
+ if (up.state == plupload.STARTED) {
1465
+ // Get start time to calculate bps
1466
+ startTime = (+new Date());
1467
+ } else if (up.state == plupload.STOPPED) {
1468
+ // Reset currently uploading files
1469
+ for (var i = up.files.length - 1; i >= 0; i--) {
1470
+ if (up.files[i].status == plupload.UPLOADING) {
1471
+ up.files[i].status = plupload.QUEUED;
1472
+ calc();
1473
+ }
1474
+ }
1475
+ }
1476
+ });
1477
+
1478
+ self.bind('QueueChanged', calc);
1479
+
1480
+ self.bind("Error", function(up, err) {
1481
+ // Set failed status if an error occured on a file
1482
+ if (err.file) {
1483
+ err.file.status = plupload.FAILED;
1484
+
1485
+ calcFile(err.file);
1486
+
1487
+ // Upload next file but detach it from the error event
1488
+ // since other custom listeners might want to stop the queue
1489
+ if (up.state == plupload.STARTED) {
1490
+ delay(function() {
1491
+ uploadNext.call(self);
1492
+ }, 1);
1493
+ }
1494
+ }
1495
+ });
1496
+
1497
+ self.bind("FileUploaded", function() {
1498
+ calc();
1499
+
1500
+ // Upload next file but detach it from the error event
1501
+ // since other custom listeners might want to stop the queue
1502
+ delay(function() {
1503
+ uploadNext.call(self);
1504
+ }, 1);
1505
+ });
1506
+
1507
+ // some dependent scripts hook onto Init to alter configuration options, raw UI, etc (like Queue Widget),
1508
+ // therefore we got to fire this one, before we dive into the actual initializaion
1509
+ self.trigger('Init', { runtime: this.runtime });
1510
+
1511
+ initControls.call(this);
1512
+ },
1513
+
1514
+ /**
1515
+ * Refreshes the upload instance by dispatching out a refresh event to all runtimes.
1516
+ * This would for example reposition flash/silverlight shims on the page.
1517
+ *
1518
+ * @method refresh
1519
+ */
1520
+ refresh : function() {
1521
+ if (fileInput) {
1522
+ fileInput.trigger("Refresh");
1523
+ }
1524
+ this.trigger("Refresh");
1525
+ },
1526
+
1527
+ /**
1528
+ * Starts uploading the queued files.
1529
+ *
1530
+ * @method start
1531
+ */
1532
+ start : function() {
1533
+ if (this.state != plupload.STARTED) {
1534
+ this.state = plupload.STARTED;
1535
+ this.trigger("StateChanged");
1536
+
1537
+ uploadNext.call(this);
1538
+ }
1539
+ },
1540
+
1541
+ /**
1542
+ * Stops the upload of the queued files.
1543
+ *
1544
+ * @method stop
1545
+ */
1546
+ stop : function() {
1547
+ if (this.state != plupload.STOPPED) {
1548
+ this.state = plupload.STOPPED;
1549
+ this.trigger("StateChanged");
1550
+ this.trigger("CancelUpload");
1551
+ }
1552
+ },
1553
+
1554
+
1555
+ /**
1556
+ * Disables/enables browse button on request.
1557
+ *
1558
+ * @method disableBrowse
1559
+ * @param {Boolean} disable Whether to disable or enable (default: true)
1560
+ */
1561
+ disableBrowse : function() {
1562
+ disabled = arguments[0] !== undef ? arguments[0] : true;
1563
+
1564
+ if (fileInput) {
1565
+ fileInput.disable(disabled);
1566
+ }
1567
+
1568
+ this.trigger("DisableBrowse", disabled);
1569
+ },
1570
+
1571
+ /**
1572
+ * Returns the specified file object by id.
1573
+ *
1574
+ * @method getFile
1575
+ * @param {String} id File id to look for.
1576
+ * @return {plupload.File} File object or undefined if it wasn't found;
1577
+ */
1578
+ getFile : function(id) {
1579
+ var i;
1580
+ for (i = files.length - 1; i >= 0; i--) {
1581
+ if (files[i].id === id) {
1582
+ return files[i];
1583
+ }
1584
+ }
1585
+ },
1586
+
1587
+ /**
1588
+ * Adds file to the queue programmatically. Can be native file, instance of Plupload.File,
1589
+ * instance of mOxie.File, input[type="file"] element, or array of these. Fires FilesAdded,
1590
+ * if any files were added to the queue. Otherwise nothing happens.
1591
+ *
1592
+ * @method addFile
1593
+ * @param {plupload.File|mOxie.File|File|Node|Array} file File or files to add to the queue.
1594
+ * @param {String} [fileName] If specified, will be used as a name for the file
1595
+ */
1596
+ addFile : function(file, fileName) {
1597
+ var self = this
1598
+ , queue = []
1599
+ , files = []
1600
+ , ruid
1601
+ ;
1602
+
1603
+ function getRUID() {
1604
+ var ctrl = fileDrop || fileInput;
1605
+ if (ctrl) {
1606
+ return ctrl.getRuntime().uid;
1607
+ }
1608
+ return false;
1609
+ }
1610
+
1611
+ function filterFile(file, cb) {
1612
+ var queue = [];
1613
+ o.each(self.settings.filters, function(rule, name) {
1614
+ if (fileFilters[name]) {
1615
+ queue.push(function(cb) {
1616
+ fileFilters[name].call(self, rule, file, function(res) {
1617
+ cb(!res);
1618
+ });
1619
+ });
1620
+ }
1621
+ });
1622
+ o.inSeries(queue, cb);
1623
+ }
1624
+
1625
+ /**
1626
+ * @method resolveFile
1627
+ * @private
1628
+ * @param {o.File|o.Blob|plupload.File|File|Blob|input[type="file"]} file
1629
+ */
1630
+ function resolveFile(file) {
1631
+ var type = o.typeOf(file);
1632
+
1633
+ // o.File
1634
+ if (file instanceof o.File) {
1635
+ if (!file.ruid && !file.isDetached()) {
1636
+ if (!ruid) { // weird case
1637
+ return false;
1638
+ }
1639
+ file.ruid = ruid;
1640
+ file.connectRuntime(ruid);
1641
+ }
1642
+ resolveFile(new plupload.File(file));
1643
+ }
1644
+ // o.Blob
1645
+ else if (file instanceof o.Blob) {
1646
+ resolveFile(file.getSource());
1647
+ file.destroy();
1648
+ }
1649
+ // plupload.File - final step for other branches
1650
+ else if (file instanceof plupload.File) {
1651
+ if (fileName) {
1652
+ file.name = fileName;
1653
+ }
1654
+
1655
+ queue.push(function(cb) {
1656
+ // run through the internal and user-defined filters, if any
1657
+ filterFile(file, function(err) {
1658
+ if (!err) {
1659
+ files.push(file);
1660
+ }
1661
+ cb();
1662
+ });
1663
+ });
1664
+ }
1665
+ // native File or blob
1666
+ else if (o.inArray(type, ['file', 'blob']) !== -1) {
1667
+ resolveFile(new o.File(null, file));
1668
+ }
1669
+ // input[type="file"]
1670
+ else if (type === 'node' && o.typeOf(file.files) === 'filelist') {
1671
+ // if we are dealing with input[type="file"]
1672
+ o.each(file.files, resolveFile);
1673
+ }
1674
+ // mixed array of any supported types (see above)
1675
+ else if (type === 'array') {
1676
+ fileName = null; // should never happen, but unset anyway to avoid funny situations
1677
+ o.each(file, resolveFile);
1678
+ }
1679
+ }
1680
+
1681
+ ruid = getRUID();
1682
+
1683
+ resolveFile(file);
1684
+
1685
+ if (queue.length) {
1686
+ o.inSeries(queue, function() {
1687
+ // if any files left after filtration, trigger FilesAdded
1688
+ if (files.length) {
1689
+ self.trigger("FilesAdded", files);
1690
+ }
1691
+ });
1692
+ }
1693
+ },
1694
+
1695
+ /**
1696
+ * Removes a specific file.
1697
+ *
1698
+ * @method removeFile
1699
+ * @param {plupload.File|String} file File to remove from queue.
1700
+ */
1701
+ removeFile : function(file) {
1702
+ var id = typeof(file) === 'string' ? file : file.id;
1703
+
1704
+ for (var i = files.length - 1; i >= 0; i--) {
1705
+ if (files[i].id === id) {
1706
+ return this.splice(i, 1)[0];
1707
+ }
1708
+ }
1709
+ },
1710
+
1711
+ /**
1712
+ * Removes part of the queue and returns the files removed. This will also trigger the FilesRemoved and QueueChanged events.
1713
+ *
1714
+ * @method splice
1715
+ * @param {Number} start (Optional) Start index to remove from.
1716
+ * @param {Number} length (Optional) Lengh of items to remove.
1717
+ * @return {Array} Array of files that was removed.
1718
+ */
1719
+ splice : function(start, length) {
1720
+ // Splice and trigger events
1721
+ var removed = files.splice(start === undef ? 0 : start, length === undef ? files.length : length);
1722
+
1723
+ this.trigger("FilesRemoved", removed);
1724
+ this.trigger("QueueChanged");
1725
+
1726
+ // Dispose any resources allocated by those files
1727
+ plupload.each(removed, function(file) {
1728
+ file.destroy();
1729
+ });
1730
+
1731
+ return removed;
1732
+ },
1733
+
1734
+ /**
1735
+ * Dispatches the specified event name and it's arguments to all listeners.
1736
+ *
1737
+ *
1738
+ * @method trigger
1739
+ * @param {String} name Event name to fire.
1740
+ * @param {Object..} Multiple arguments to pass along to the listener functions.
1741
+ */
1742
+ trigger : function(name) {
1743
+ var list = events[name.toLowerCase()], i, args;
1744
+
1745
+ // console.log(name, arguments);
1746
+
1747
+ if (list) {
1748
+ // Replace name with sender in args
1749
+ args = Array.prototype.slice.call(arguments);
1750
+ args[0] = this;
1751
+
1752
+ // Dispatch event to all listeners
1753
+ for (i = 0; i < list.length; i++) {
1754
+ // Fire event, break chain if false is returned
1755
+ if (list[i].func.apply(list[i].scope, args) === false) {
1756
+ return false;
1757
+ }
1758
+ }
1759
+ }
1760
+
1761
+ return true;
1762
+ },
1763
+
1764
+ /**
1765
+ * Check whether uploader has any listeners to the specified event.
1766
+ *
1767
+ * @method hasEventListener
1768
+ * @param {String} name Event name to check for.
1769
+ */
1770
+ hasEventListener : function(name) {
1771
+ return !!events[name.toLowerCase()];
1772
+ },
1773
+
1774
+ /**
1775
+ * Adds an event listener by name.
1776
+ *
1777
+ * @method bind
1778
+ * @param {String} name Event name to listen for.
1779
+ * @param {function} func Function to call ones the event gets fired.
1780
+ * @param {Object} scope Optional scope to execute the specified function in.
1781
+ */
1782
+ bind : function(name, func, scope) {
1783
+ var list;
1784
+
1785
+ name = name.toLowerCase();
1786
+ list = events[name] || [];
1787
+ list.push({func : func, scope : scope || this});
1788
+ events[name] = list;
1789
+ },
1790
+
1791
+ /**
1792
+ * Removes the specified event listener.
1793
+ *
1794
+ * @method unbind
1795
+ * @param {String} name Name of event to remove.
1796
+ * @param {function} func Function to remove from listener.
1797
+ */
1798
+ unbind : function(name) {
1799
+ name = name.toLowerCase();
1800
+
1801
+ var list = events[name], i, func = arguments[1];
1802
+
1803
+ if (list) {
1804
+ if (func !== undef) {
1805
+ for (i = list.length - 1; i >= 0; i--) {
1806
+ if (list[i].func === func) {
1807
+ list.splice(i, 1);
1808
+ break;
1809
+ }
1810
+ }
1811
+ } else {
1812
+ list = [];
1813
+ }
1814
+
1815
+ // delete event list if it has become empty
1816
+ if (!list.length) {
1817
+ delete events[name];
1818
+ }
1819
+ }
1820
+ },
1821
+
1822
+ /**
1823
+ * Removes all event listeners.
1824
+ *
1825
+ * @method unbindAll
1826
+ */
1827
+ unbindAll : function() {
1828
+ var self = this;
1829
+
1830
+ plupload.each(events, function(list, name) {
1831
+ self.unbind(name);
1832
+ });
1833
+ },
1834
+
1835
+ /**
1836
+ * Destroys Plupload instance and cleans after itself.
1837
+ *
1838
+ * @method destroy
1839
+ */
1840
+ destroy : function() {
1841
+ this.stop();
1842
+
1843
+ // Purge the queue
1844
+ plupload.each(files, function(file) {
1845
+ file.destroy();
1846
+ });
1847
+ files = [];
1848
+
1849
+ if (fileInput) {
1850
+ fileInput.destroy();
1851
+ fileInput = null;
1852
+ }
1853
+
1854
+ if (fileDrop) {
1855
+ fileDrop.destroy();
1856
+ fileDrop = null;
1857
+ }
1858
+
1859
+ required_caps = {};
1860
+ startTime = total = disabled = xhr = null;
1861
+
1862
+ this.trigger('Destroy');
1863
+
1864
+ // Clean-up after uploader itself
1865
+ this.unbindAll();
1866
+ events = {};
1867
+ }
1868
+ });
1869
+ };
1870
+
1871
+ /**
1872
+ * Constructs a new file instance.
1873
+ *
1874
+ * @class File
1875
+ * @constructor
1876
+ *
1877
+ * @param {Object} file Object containing file properties
1878
+ * @param {String} file.name Name of the file.
1879
+ * @param {Number} file.size File size.
1880
+ */
1881
+ plupload.File = (function() {
1882
+ var filepool = {};
1883
+
1884
+ function PluploadFile(file) {
1885
+
1886
+ plupload.extend(this, {
1887
+
1888
+ /**
1889
+ * File id this is a globally unique id for the specific file.
1890
+ *
1891
+ * @property id
1892
+ * @type String
1893
+ */
1894
+ id: plupload.guid(),
1895
+
1896
+ /**
1897
+ * File name for example "myfile.gif".
1898
+ *
1899
+ * @property name
1900
+ * @type String
1901
+ */
1902
+ name: file.name || file.fileName,
1903
+
1904
+ /**
1905
+ * File type, `e.g image/jpeg`
1906
+ *
1907
+ * @property type
1908
+ * @type String
1909
+ */
1910
+ type: file.type || '',
1911
+
1912
+ /**
1913
+ * File size in bytes (may change after client-side manupilation).
1914
+ *
1915
+ * @property size
1916
+ * @type Number
1917
+ */
1918
+ size: file.size || file.fileSize,
1919
+
1920
+ /**
1921
+ * Original file size in bytes.
1922
+ *
1923
+ * @property origSize
1924
+ * @type Number
1925
+ */
1926
+ origSize: file.size || file.fileSize,
1927
+
1928
+ /**
1929
+ * Number of bytes uploaded of the files total size.
1930
+ *
1931
+ * @property loaded
1932
+ * @type Number
1933
+ */
1934
+ loaded: 0,
1935
+
1936
+ /**
1937
+ * Number of percentage uploaded of the file.
1938
+ *
1939
+ * @property percent
1940
+ * @type Number
1941
+ */
1942
+ percent: 0,
1943
+
1944
+ /**
1945
+ * Status constant matching the plupload states QUEUED, UPLOADING, FAILED, DONE.
1946
+ *
1947
+ * @property status
1948
+ * @type Number
1949
+ * @see plupload
1950
+ */
1951
+ status: plupload.QUEUED,
1952
+
1953
+ /**
1954
+ * Date of last modification.
1955
+ *
1956
+ * @property lastModifiedDate
1957
+ * @type {String}
1958
+ */
1959
+ lastModifiedDate: file.lastModifiedDate || (new Date()).toLocaleString(), // Thu Aug 23 2012 19:40:00 GMT+0400 (GET)
1960
+
1961
+ /**
1962
+ * Returns native window.File object, when it's available.
1963
+ *
1964
+ * @method getNative
1965
+ * @return {window.File} or null, if plupload.File is of different origin
1966
+ */
1967
+ getNative: function() {
1968
+ var file = this.getSource().getSource();
1969
+ return o.inArray(o.typeOf(file), ['blob', 'file']) !== -1 ? file : null;
1970
+ },
1971
+
1972
+ /**
1973
+ * Returns mOxie.File - unified wrapper object that can be used across runtimes.
1974
+ *
1975
+ * @method getSource
1976
+ * @return {mOxie.File} or null
1977
+ */
1978
+ getSource: function() {
1979
+ if (!filepool[this.id]) {
1980
+ return null;
1981
+ }
1982
+ return filepool[this.id];
1983
+ },
1984
+
1985
+ /**
1986
+ * Destroys plupload.File object.
1987
+ *
1988
+ * @method destroy
1989
+ */
1990
+ destroy: function() {
1991
+ var src = this.getSource();
1992
+ if (src) {
1993
+ src.destroy();
1994
+ delete filepool[this.id];
1995
+ }
1996
+ }
1997
+ });
1998
+
1999
+ filepool[this.id] = file;
2000
+ }
2001
+
2002
+ return PluploadFile;
2003
+ }());
2004
+
2005
+
2006
+ /**
2007
+ * Constructs a queue progress.
2008
+ *
2009
+ * @class QueueProgress
2010
+ * @constructor
2011
+ */
2012
+ plupload.QueueProgress = function() {
2013
+ var self = this; // Setup alias for self to reduce code size when it's compressed
2014
+
2015
+ /**
2016
+ * Total queue file size.
2017
+ *
2018
+ * @property size
2019
+ * @type Number
2020
+ */
2021
+ self.size = 0;
2022
+
2023
+ /**
2024
+ * Total bytes uploaded.
2025
+ *
2026
+ * @property loaded
2027
+ * @type Number
2028
+ */
2029
+ self.loaded = 0;
2030
+
2031
+ /**
2032
+ * Number of files uploaded.
2033
+ *
2034
+ * @property uploaded
2035
+ * @type Number
2036
+ */
2037
+ self.uploaded = 0;
2038
+
2039
+ /**
2040
+ * Number of files failed to upload.
2041
+ *
2042
+ * @property failed
2043
+ * @type Number
2044
+ */
2045
+ self.failed = 0;
2046
+
2047
+ /**
2048
+ * Number of files yet to be uploaded.
2049
+ *
2050
+ * @property queued
2051
+ * @type Number
2052
+ */
2053
+ self.queued = 0;
2054
+
2055
+ /**
2056
+ * Total percent of the uploaded bytes.
2057
+ *
2058
+ * @property percent
2059
+ * @type Number
2060
+ */
2061
+ self.percent = 0;
2062
+
2063
+ /**
2064
+ * Bytes uploaded per second.
2065
+ *
2066
+ * @property bytesPerSec
2067
+ * @type Number
2068
+ */
2069
+ self.bytesPerSec = 0;
2070
+
2071
+ /**
2072
+ * Resets the progress to it's initial values.
2073
+ *
2074
+ * @method reset
2075
+ */
2076
+ self.reset = function() {
2077
+ self.size = self.loaded = self.uploaded = self.failed = self.queued = self.percent = self.bytesPerSec = 0;
2078
+ };
2079
+ };
2080
+
2081
+ window.plupload = plupload;
2082
+
2083
+ }(window, mOxie));