|
1 /* |
|
2 YUI 3.10.3 (build 2fb5187) |
|
3 Copyright 2013 Yahoo! Inc. All rights reserved. |
|
4 Licensed under the BSD License. |
|
5 http://yuilibrary.com/license/ |
|
6 */ |
|
7 |
|
8 YUI.add('uploader-queue', function (Y, NAME) { |
|
9 |
|
10 /** |
|
11 * The class manages a queue of files that should be uploaded to the server. |
|
12 * It initializes the required number of uploads, tracks them as they progress, |
|
13 * and automatically advances to the next upload when a preceding one has completed. |
|
14 * @module uploader-queue |
|
15 */ |
|
16 |
|
17 |
|
18 |
|
19 /** |
|
20 * This class manages a queue of files to be uploaded to the server. |
|
21 * @class Uploader.Queue |
|
22 * @extends Base |
|
23 * @constructor |
|
24 */ |
|
25 var UploaderQueue = function() { |
|
26 this.queuedFiles = []; |
|
27 this.uploadRetries = {}; |
|
28 this.numberOfUploads = 0; |
|
29 this.currentUploadedByteValues = {}; |
|
30 this.currentFiles = {}; |
|
31 this.totalBytesUploaded = 0; |
|
32 this.totalBytes = 0; |
|
33 |
|
34 UploaderQueue.superclass.constructor.apply(this, arguments); |
|
35 }; |
|
36 |
|
37 |
|
38 Y.extend(UploaderQueue, Y.Base, { |
|
39 |
|
40 /** |
|
41 * Stored value of the current queue state |
|
42 * @property _currentState |
|
43 * @type {String} |
|
44 * @protected |
|
45 * @default UploaderQueue.STOPPED |
|
46 */ |
|
47 _currentState: UploaderQueue.STOPPED, |
|
48 |
|
49 /** |
|
50 * Construction logic executed during UploaderQueue instantiation. |
|
51 * |
|
52 * @method initializer |
|
53 * @protected |
|
54 */ |
|
55 initializer : function () {}, |
|
56 |
|
57 /** |
|
58 * Handles and retransmits upload start event. |
|
59 * |
|
60 * @method _uploadStartHandler |
|
61 * @param event The event dispatched during the upload process. |
|
62 * @private |
|
63 */ |
|
64 _uploadStartHandler : function (event) { |
|
65 var updatedEvent = event; |
|
66 updatedEvent.file = event.target; |
|
67 updatedEvent.originEvent = event; |
|
68 |
|
69 this.fire("uploadstart", updatedEvent); |
|
70 }, |
|
71 |
|
72 /** |
|
73 * Handles and retransmits upload error event. |
|
74 * |
|
75 * @method _uploadErrorHandler |
|
76 * @param event The event dispatched during the upload process. |
|
77 * @private |
|
78 */ |
|
79 _uploadErrorHandler : function (event) { |
|
80 var errorAction = this.get("errorAction"), |
|
81 updatedEvent = event, |
|
82 fileid, |
|
83 retries; |
|
84 |
|
85 updatedEvent.file = event.target; |
|
86 updatedEvent.originEvent = event; |
|
87 |
|
88 this.numberOfUploads-=1; |
|
89 delete this.currentFiles[event.target.get("id")]; |
|
90 this._detachFileEvents(event.target); |
|
91 |
|
92 event.target.cancelUpload(); |
|
93 |
|
94 if (errorAction === UploaderQueue.STOP) { |
|
95 this.pauseUpload(); |
|
96 } |
|
97 |
|
98 else if (errorAction === UploaderQueue.RESTART_ASAP) { |
|
99 fileid = event.target.get("id"); |
|
100 retries = this.uploadRetries[fileid] || 0; |
|
101 |
|
102 if (retries < this.get("retryCount")) { |
|
103 this.uploadRetries[fileid] = retries + 1; |
|
104 this.addToQueueTop(event.target); |
|
105 } |
|
106 this._startNextFile(); |
|
107 } |
|
108 else if (errorAction === UploaderQueue.RESTART_AFTER) { |
|
109 fileid = event.target.get("id"); |
|
110 retries = this.uploadRetries[fileid] || 0; |
|
111 |
|
112 if (retries < this.get("retryCount")) { |
|
113 this.uploadRetries[fileid] = retries + 1; |
|
114 this.addToQueueBottom(event.target); |
|
115 } |
|
116 this._startNextFile(); |
|
117 } |
|
118 |
|
119 this.fire("uploaderror", updatedEvent); |
|
120 }, |
|
121 |
|
122 /** |
|
123 * Launches the upload of the next file in the queue. |
|
124 * |
|
125 * @method _startNextFile |
|
126 * @private |
|
127 */ |
|
128 _startNextFile : function () { |
|
129 if (this.queuedFiles.length > 0) { |
|
130 var currentFile = this.queuedFiles.shift(), |
|
131 fileId = currentFile.get("id"), |
|
132 parameters = this.get("perFileParameters"), |
|
133 fileParameters = parameters.hasOwnProperty(fileId) ? parameters[fileId] : parameters; |
|
134 |
|
135 this.currentUploadedByteValues[fileId] = 0; |
|
136 |
|
137 currentFile.on("uploadstart", this._uploadStartHandler, this); |
|
138 currentFile.on("uploadprogress", this._uploadProgressHandler, this); |
|
139 currentFile.on("uploadcomplete", this._uploadCompleteHandler, this); |
|
140 currentFile.on("uploaderror", this._uploadErrorHandler, this); |
|
141 currentFile.on("uploadcancel", this._uploadCancelHandler, this); |
|
142 |
|
143 currentFile.set("xhrHeaders", this.get("uploadHeaders")); |
|
144 currentFile.set("xhrWithCredentials", this.get("withCredentials")); |
|
145 |
|
146 currentFile.startUpload(this.get("uploadURL"), fileParameters, this.get("fileFieldName")); |
|
147 |
|
148 this._registerUpload(currentFile); |
|
149 } |
|
150 }, |
|
151 |
|
152 /** |
|
153 * Register a new upload process. |
|
154 * |
|
155 * @method _registerUpload |
|
156 * @private |
|
157 */ |
|
158 _registerUpload : function (file) { |
|
159 this.numberOfUploads += 1; |
|
160 this.currentFiles[file.get("id")] = file; |
|
161 }, |
|
162 |
|
163 /** |
|
164 * Unregisters a new upload process. |
|
165 * |
|
166 * @method _unregisterUpload |
|
167 * @private |
|
168 */ |
|
169 _unregisterUpload : function (file) { |
|
170 if (this.numberOfUploads > 0) { |
|
171 this.numberOfUploads -= 1; |
|
172 } |
|
173 |
|
174 delete this.currentFiles[file.get("id")]; |
|
175 delete this.uploadRetries[file.get("id")]; |
|
176 |
|
177 this._detachFileEvents(file); |
|
178 }, |
|
179 |
|
180 _detachFileEvents : function (file) { |
|
181 file.detach("uploadstart", this._uploadStartHandler); |
|
182 file.detach("uploadprogress", this._uploadProgressHandler); |
|
183 file.detach("uploadcomplete", this._uploadCompleteHandler); |
|
184 file.detach("uploaderror", this._uploadErrorHandler); |
|
185 file.detach("uploadcancel", this._uploadCancelHandler); |
|
186 }, |
|
187 |
|
188 /** |
|
189 * Handles and retransmits upload complete event. |
|
190 * |
|
191 * @method _uploadCompleteHandler |
|
192 * @param event The event dispatched during the upload process. |
|
193 * @private |
|
194 */ |
|
195 _uploadCompleteHandler : function (event) { |
|
196 |
|
197 this._unregisterUpload(event.target); |
|
198 |
|
199 this.totalBytesUploaded += event.target.get("size"); |
|
200 delete this.currentUploadedByteValues[event.target.get("id")]; |
|
201 |
|
202 |
|
203 if (this.queuedFiles.length > 0 && this._currentState === UploaderQueue.UPLOADING) { |
|
204 this._startNextFile(); |
|
205 } |
|
206 |
|
207 var updatedEvent = event, |
|
208 uploadedTotal = this.totalBytesUploaded, |
|
209 percentLoaded = Math.min(100, Math.round(10000*uploadedTotal/this.totalBytes) / 100); |
|
210 |
|
211 updatedEvent.file = event.target; |
|
212 updatedEvent.originEvent = event; |
|
213 |
|
214 Y.each(this.currentUploadedByteValues, function (value) { |
|
215 uploadedTotal += value; |
|
216 }); |
|
217 |
|
218 this.fire("totaluploadprogress", { |
|
219 bytesLoaded: uploadedTotal, |
|
220 bytesTotal: this.totalBytes, |
|
221 percentLoaded: percentLoaded |
|
222 }); |
|
223 |
|
224 this.fire("uploadcomplete", updatedEvent); |
|
225 |
|
226 if (this.queuedFiles.length === 0 && this.numberOfUploads <= 0) { |
|
227 this.fire("alluploadscomplete"); |
|
228 this._currentState = UploaderQueue.STOPPED; |
|
229 } |
|
230 }, |
|
231 |
|
232 /** |
|
233 * Handles and retransmits upload cancel event. |
|
234 * |
|
235 * @method _uploadCancelHandler |
|
236 * @param event The event dispatched during the upload process. |
|
237 * @private |
|
238 */ |
|
239 _uploadCancelHandler : function (event) { |
|
240 |
|
241 var updatedEvent = event; |
|
242 updatedEvent.originEvent = event; |
|
243 updatedEvent.file = event.target; |
|
244 |
|
245 this.fire("uploadcacel", updatedEvent); |
|
246 }, |
|
247 |
|
248 |
|
249 |
|
250 /** |
|
251 * Handles and retransmits upload progress event. |
|
252 * |
|
253 * @method _uploadProgressHandler |
|
254 * @param event The event dispatched during the upload process. |
|
255 * @private |
|
256 */ |
|
257 _uploadProgressHandler : function (event) { |
|
258 |
|
259 this.currentUploadedByteValues[event.target.get("id")] = event.bytesLoaded; |
|
260 |
|
261 var updatedEvent = event, |
|
262 uploadedTotal = this.totalBytesUploaded, |
|
263 percentLoaded = Math.min(100, Math.round(10000*uploadedTotal/this.totalBytes) / 100); |
|
264 |
|
265 updatedEvent.originEvent = event; |
|
266 updatedEvent.file = event.target; |
|
267 |
|
268 this.fire("uploadprogress", updatedEvent); |
|
269 |
|
270 Y.each(this.currentUploadedByteValues, function (value) { |
|
271 uploadedTotal += value; |
|
272 }); |
|
273 |
|
274 this.fire("totaluploadprogress", { |
|
275 bytesLoaded: uploadedTotal, |
|
276 bytesTotal: this.totalBytes, |
|
277 percentLoaded: percentLoaded |
|
278 }); |
|
279 }, |
|
280 |
|
281 /** |
|
282 * Starts uploading the queued up file list. |
|
283 * |
|
284 * @method startUpload |
|
285 */ |
|
286 startUpload: function() { |
|
287 this.queuedFiles = this.get("fileList").slice(0); |
|
288 this.numberOfUploads = 0; |
|
289 this.currentUploadedByteValues = {}; |
|
290 this.currentFiles = {}; |
|
291 this.totalBytesUploaded = 0; |
|
292 |
|
293 this._currentState = UploaderQueue.UPLOADING; |
|
294 |
|
295 while (this.numberOfUploads < this.get("simUploads") && this.queuedFiles.length > 0) { |
|
296 this._startNextFile(); |
|
297 } |
|
298 }, |
|
299 |
|
300 /** |
|
301 * Pauses the upload process. The ongoing file uploads |
|
302 * will complete after this method is called, but no |
|
303 * new ones will be launched. |
|
304 * |
|
305 * @method pauseUpload |
|
306 */ |
|
307 pauseUpload: function () { |
|
308 this._currentState = UploaderQueue.STOPPED; |
|
309 }, |
|
310 |
|
311 /** |
|
312 * Restarts a paused upload process. |
|
313 * |
|
314 * @method restartUpload |
|
315 */ |
|
316 restartUpload: function () { |
|
317 this._currentState = UploaderQueue.UPLOADING; |
|
318 while (this.numberOfUploads < this.get("simUploads")) { |
|
319 this._startNextFile(); |
|
320 } |
|
321 }, |
|
322 |
|
323 /** |
|
324 * If a particular file is stuck in an ongoing upload without |
|
325 * any progress events, this method allows to force its reupload |
|
326 * by cancelling its upload and immediately relaunching it. |
|
327 * |
|
328 * @method forceReupload |
|
329 * @param file {Y.File} The file to force reupload on. |
|
330 */ |
|
331 forceReupload : function (file) { |
|
332 var id = file.get("id"); |
|
333 if (this.currentFiles.hasOwnProperty(id)) { |
|
334 file.cancelUpload(); |
|
335 this._unregisterUpload(file); |
|
336 this.addToQueueTop(file); |
|
337 this._startNextFile(); |
|
338 } |
|
339 }, |
|
340 |
|
341 /** |
|
342 * Add a new file to the top of the queue (the upload will be |
|
343 * launched as soon as the current number of uploading files |
|
344 * drops below the maximum permissible value). |
|
345 * |
|
346 * @method addToQueueTop |
|
347 * @param file {Y.File} The file to add to the top of the queue. |
|
348 */ |
|
349 addToQueueTop: function (file) { |
|
350 this.queuedFiles.unshift(file); |
|
351 }, |
|
352 |
|
353 /** |
|
354 * Add a new file to the bottom of the queue (the upload will be |
|
355 * launched after all the other queued files are uploaded.) |
|
356 * |
|
357 * @method addToQueueBottom |
|
358 * @param file {Y.File} The file to add to the bottom of the queue. |
|
359 */ |
|
360 addToQueueBottom: function (file) { |
|
361 this.queuedFiles.push(file); |
|
362 }, |
|
363 |
|
364 /** |
|
365 * Cancels a specific file's upload. If no argument is passed, |
|
366 * all ongoing uploads are cancelled and the upload process is |
|
367 * stopped. |
|
368 * |
|
369 * @method cancelUpload |
|
370 * @param file {Y.File} An optional parameter - the file whose upload |
|
371 * should be cancelled. |
|
372 */ |
|
373 cancelUpload: function (file) { |
|
374 var id, |
|
375 i, |
|
376 fid; |
|
377 |
|
378 if (file) { |
|
379 id = file.get("id"); |
|
380 |
|
381 if (this.currentFiles[id]) { |
|
382 this.currentFiles[id].cancelUpload(); |
|
383 this._unregisterUpload(this.currentFiles[id]); |
|
384 if (this._currentState === UploaderQueue.UPLOADING) { |
|
385 this._startNextFile(); |
|
386 } |
|
387 } |
|
388 else { |
|
389 for (i = 0, len = this.queuedFiles.length; i < len; i++) { |
|
390 if (this.queuedFiles[i].get("id") === id) { |
|
391 this.queuedFiles.splice(i, 1); |
|
392 break; |
|
393 } |
|
394 } |
|
395 } |
|
396 } |
|
397 else { |
|
398 for (fid in this.currentFiles) { |
|
399 this.currentFiles[fid].cancelUpload(); |
|
400 this._unregisterUpload(this.currentFiles[fid]); |
|
401 } |
|
402 |
|
403 this.currentUploadedByteValues = {}; |
|
404 this.currentFiles = {}; |
|
405 this.totalBytesUploaded = 0; |
|
406 this.fire("alluploadscancelled"); |
|
407 this._currentState = UploaderQueue.STOPPED; |
|
408 } |
|
409 } |
|
410 }, { |
|
411 /** |
|
412 * Static constant for the value of the `errorAction` attribute: |
|
413 * prescribes the queue to continue uploading files in case of |
|
414 * an error. |
|
415 * @property CONTINUE |
|
416 * @readOnly |
|
417 * @type {String} |
|
418 * @static |
|
419 */ |
|
420 CONTINUE: "continue", |
|
421 |
|
422 /** |
|
423 * Static constant for the value of the `errorAction` attribute: |
|
424 * prescribes the queue to stop uploading files in case of |
|
425 * an error. |
|
426 * @property STOP |
|
427 * @readOnly |
|
428 * @type {String} |
|
429 * @static |
|
430 */ |
|
431 STOP: "stop", |
|
432 |
|
433 /** |
|
434 * Static constant for the value of the `errorAction` attribute: |
|
435 * prescribes the queue to restart a file upload immediately in case of |
|
436 * an error. |
|
437 * @property RESTART_ASAP |
|
438 * @readOnly |
|
439 * @type {String} |
|
440 * @static |
|
441 */ |
|
442 RESTART_ASAP: "restartasap", |
|
443 |
|
444 /** |
|
445 * Static constant for the value of the `errorAction` attribute: |
|
446 * prescribes the queue to restart an errored out file upload after |
|
447 * other files have finished uploading. |
|
448 * @property RESTART_AFTER |
|
449 * @readOnly |
|
450 * @type {String} |
|
451 * @static |
|
452 */ |
|
453 RESTART_AFTER: "restartafter", |
|
454 |
|
455 /** |
|
456 * Static constant for the value of the `_currentState` property: |
|
457 * implies that the queue is currently not uploading files. |
|
458 * @property STOPPED |
|
459 * @readOnly |
|
460 * @type {String} |
|
461 * @static |
|
462 */ |
|
463 STOPPED: "stopped", |
|
464 |
|
465 /** |
|
466 * Static constant for the value of the `_currentState` property: |
|
467 * implies that the queue is currently uploading files. |
|
468 * @property UPLOADING |
|
469 * @readOnly |
|
470 * @type {String} |
|
471 * @static |
|
472 */ |
|
473 UPLOADING: "uploading", |
|
474 |
|
475 /** |
|
476 * The identity of the class. |
|
477 * |
|
478 * @property NAME |
|
479 * @type String |
|
480 * @default 'uploaderqueue' |
|
481 * @readOnly |
|
482 * @protected |
|
483 * @static |
|
484 */ |
|
485 NAME: 'uploaderqueue', |
|
486 |
|
487 /** |
|
488 * Static property used to define the default attribute configuration of |
|
489 * the class. |
|
490 * |
|
491 * @property ATTRS |
|
492 * @type {Object} |
|
493 * @protected |
|
494 * @static |
|
495 */ |
|
496 ATTRS: { |
|
497 |
|
498 /** |
|
499 * Maximum number of simultaneous uploads; must be in the |
|
500 * range between 1 and 5. The value of `2` is default. It |
|
501 * is recommended that this value does not exceed 3. |
|
502 * @attribute simUploads |
|
503 * @type Number |
|
504 * @default 2 |
|
505 */ |
|
506 simUploads: { |
|
507 value: 2, |
|
508 validator: function (val) { |
|
509 return (val >= 1 && val <= 5); |
|
510 } |
|
511 }, |
|
512 |
|
513 /** |
|
514 * The action to take in case of error. The valid values for this attribute are: |
|
515 * `Y.Uploader.Queue.CONTINUE` (the upload process should continue on other files, |
|
516 * ignoring the error), `Y.Uploader.Queue.STOP` (the upload process |
|
517 * should stop completely), `Y.Uploader.Queue.RESTART_ASAP` (the upload |
|
518 * should restart immediately on the errored out file and continue as planned), or |
|
519 * Y.Uploader.Queue.RESTART_AFTER (the upload of the errored out file should restart |
|
520 * after all other files have uploaded) |
|
521 * @attribute errorAction |
|
522 * @type String |
|
523 * @default Y.Uploader.Queue.CONTINUE |
|
524 */ |
|
525 errorAction: { |
|
526 value: "continue", |
|
527 validator: function (val) { |
|
528 return ( |
|
529 val === UploaderQueue.CONTINUE || |
|
530 val === UploaderQueue.STOP || |
|
531 val === UploaderQueue.RESTART_ASAP || |
|
532 val === UploaderQueue.RESTART_AFTER |
|
533 ); |
|
534 } |
|
535 }, |
|
536 |
|
537 /** |
|
538 * The total number of bytes that has been uploaded. |
|
539 * @attribute bytesUploaded |
|
540 * @type Number |
|
541 */ |
|
542 bytesUploaded: { |
|
543 readOnly: true, |
|
544 value: 0 |
|
545 }, |
|
546 |
|
547 /** |
|
548 * The total number of bytes in the queue. |
|
549 * @attribute bytesTotal |
|
550 * @type Number |
|
551 */ |
|
552 bytesTotal: { |
|
553 readOnly: true, |
|
554 value: 0 |
|
555 }, |
|
556 |
|
557 /** |
|
558 * The queue file list. This file list should only be modified |
|
559 * before the upload has been started; modifying it after starting |
|
560 * the upload has no effect, and `addToQueueTop` or `addToQueueBottom` methods |
|
561 * should be used instead. |
|
562 * @attribute fileList |
|
563 * @type Array |
|
564 */ |
|
565 fileList: { |
|
566 value: [], |
|
567 lazyAdd: false, |
|
568 setter: function (val) { |
|
569 var newValue = val; |
|
570 Y.Array.each(newValue, function (value) { |
|
571 this.totalBytes += value.get("size"); |
|
572 }, this); |
|
573 |
|
574 return val; |
|
575 } |
|
576 }, |
|
577 |
|
578 /** |
|
579 * A String specifying what should be the POST field name for the file |
|
580 * content in the upload request. |
|
581 * |
|
582 * @attribute fileFieldName |
|
583 * @type {String} |
|
584 * @default Filedata |
|
585 */ |
|
586 fileFieldName: { |
|
587 value: "Filedata" |
|
588 }, |
|
589 |
|
590 /** |
|
591 * The URL to POST the file upload requests to. |
|
592 * |
|
593 * @attribute uploadURL |
|
594 * @type {String} |
|
595 * @default "" |
|
596 */ |
|
597 uploadURL: { |
|
598 value: "" |
|
599 }, |
|
600 |
|
601 /** |
|
602 * Additional HTTP headers that should be included |
|
603 * in the upload request. Due to Flash Player security |
|
604 * restrictions, this attribute is only honored in the |
|
605 * HTML5 Uploader. |
|
606 * |
|
607 * @attribute uploadHeaders |
|
608 * @type {Object} |
|
609 * @default {} |
|
610 */ |
|
611 uploadHeaders: { |
|
612 value: {} |
|
613 }, |
|
614 |
|
615 /** |
|
616 * A Boolean that specifies whether the file should be |
|
617 * uploaded with the appropriate user credentials for the |
|
618 * domain. Due to Flash Player security restrictions, this |
|
619 * attribute is only honored in the HTML5 Uploader. |
|
620 * |
|
621 * @attribute withCredentials |
|
622 * @type {Boolean} |
|
623 * @default true |
|
624 */ |
|
625 withCredentials: { |
|
626 value: true |
|
627 }, |
|
628 |
|
629 |
|
630 /** |
|
631 * An object, keyed by `fileId`, containing sets of key-value pairs |
|
632 * that should be passed as POST variables along with each corresponding |
|
633 * file. |
|
634 * |
|
635 * @attribute perFileParameters |
|
636 * @type {Object} |
|
637 * @default {} |
|
638 */ |
|
639 perFileParameters: { |
|
640 value: {} |
|
641 }, |
|
642 |
|
643 /** |
|
644 * The number of times to try re-uploading a file that failed to upload before |
|
645 * cancelling its upload. |
|
646 * |
|
647 * @attribute retryCount |
|
648 * @type {Number} |
|
649 * @default 3 |
|
650 */ |
|
651 retryCount: { |
|
652 value: 3 |
|
653 } |
|
654 |
|
655 } |
|
656 }); |
|
657 |
|
658 |
|
659 Y.namespace('Uploader'); |
|
660 Y.Uploader.Queue = UploaderQueue; |
|
661 |
|
662 |
|
663 }, '3.10.3', {"requires": ["base"]}); |