3 Copyright 2012 Yahoo! Inc. All rights reserved.
4 Licensed under the BSD License.
5 http://yuilibrary.com/license/
7 YUI.add('uploader-queue', function (Y, NAME) {
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
22 currentUploadedByteValues,
28 * This class manages a queue of files to be uploaded to the server.
29 * @class Uploader.Queue
32 * @param {Object} config Configuration object
34 var UploaderQueue = function(o) {
35 this.queuedFiles = [];
36 this.uploadRetries = {};
37 this.numberOfUploads = 0;
38 this.currentUploadedByteValues = {};
39 this.currentFiles = {};
40 this.totalBytesUploaded = 0;
43 UploaderQueue.superclass.constructor.apply(this, arguments);
47 Y.extend(UploaderQueue, Y.Base, {
50 * Stored value of the current queue state
51 * @property _currentState
54 * @default UploaderQueue.STOPPED
56 _currentState: UploaderQueue.STOPPED,
59 * Construction logic executed during UploaderQueue instantiation.
64 initializer : function (cfg) {
69 * Handles and retransmits upload start event.
71 * @method _uploadStartHandler
72 * @param event The event dispatched during the upload process.
75 _uploadStartHandler : function (event) {
76 var updatedEvent = event;
77 updatedEvent.file = event.target;
78 updatedEvent.originEvent = event;
80 this.fire("uploadstart", updatedEvent);
84 * Handles and retransmits upload error event.
86 * @method _uploadErrorHandler
87 * @param event The event dispatched during the upload process.
90 _uploadErrorHandler : function (event) {
91 var errorAction = this.get("errorAction");
92 var updatedEvent = event;
93 updatedEvent.file = event.target;
94 updatedEvent.originEvent = event;
96 this.numberOfUploads-=1;
97 delete this.currentFiles[event.target.get("id")];
98 this._detachFileEvents(event.target);
100 event.target.cancelUpload();
102 if (errorAction === UploaderQueue.STOP) {
106 else if (errorAction === UploaderQueue.RESTART_ASAP) {
107 var fileid = event.target.get("id"),
108 retries = this.uploadRetries[fileid] || 0;
109 if (retries < this.get("retryCount")) {
110 this.uploadRetries[fileid] = retries + 1;
111 this.addToQueueTop(event.target);
113 this._startNextFile();
115 else if (errorAction === UploaderQueue.RESTART_AFTER) {
116 var fileid = event.target.get("id"),
117 retries = this.uploadRetries[fileid] || 0;
118 if (retries < this.get("retryCount")) {
119 this.uploadRetries[fileid] = retries + 1;
120 this.addToQueueBottom(event.target);
122 this._startNextFile();
125 this.fire("uploaderror", updatedEvent);
129 * Launches the upload of the next file in the queue.
131 * @method _startNextFile
134 _startNextFile : function () {
135 if (this.queuedFiles.length > 0) {
136 var currentFile = this.queuedFiles.shift(),
137 fileId = currentFile.get("id"),
138 parameters = this.get("perFileParameters"),
139 fileParameters = parameters.hasOwnProperty(fileId) ? parameters[fileId] : parameters;
141 this.currentUploadedByteValues[fileId] = 0;
143 currentFile.on("uploadstart", this._uploadStartHandler, this);
144 currentFile.on("uploadprogress", this._uploadProgressHandler, this);
145 currentFile.on("uploadcomplete", this._uploadCompleteHandler, this);
146 currentFile.on("uploaderror", this._uploadErrorHandler, this);
147 currentFile.on("uploadcancel", this._uploadCancelHandler, this);
149 currentFile.set("xhrHeaders", this.get("uploadHeaders"));
150 currentFile.set("xhrWithCredentials", this.get("withCredentials"));
152 currentFile.startUpload(this.get("uploadURL"), fileParameters, this.get("fileFieldName"));
154 this._registerUpload(currentFile);
159 * Register a new upload process.
161 * @method _registerUpload
164 _registerUpload : function (file) {
165 this.numberOfUploads += 1;
166 this.currentFiles[file.get("id")] = file;
170 * Unregisters a new upload process.
172 * @method _unregisterUpload
175 _unregisterUpload : function (file) {
176 if (this.numberOfUploads > 0) {
177 this.numberOfUploads -=1;
179 delete this.currentFiles[file.get("id")];
180 delete this.uploadRetries[file.get("id")];
182 this._detachFileEvents(file);
185 _detachFileEvents : function (file) {
186 file.detach("uploadstart", this._uploadStartHandler);
187 file.detach("uploadprogress", this._uploadProgressHandler);
188 file.detach("uploadcomplete", this._uploadCompleteHandler);
189 file.detach("uploaderror", this._uploadErrorHandler);
190 file.detach("uploadcancel", this._uploadCancelHandler);
194 * Handles and retransmits upload complete event.
196 * @method _uploadCompleteHandler
197 * @param event The event dispatched during the upload process.
200 _uploadCompleteHandler : function (event) {
202 this._unregisterUpload(event.target);
204 this.totalBytesUploaded += event.target.get("size");
205 delete this.currentUploadedByteValues[event.target.get("id")];
208 if (this.queuedFiles.length > 0 && this._currentState === UploaderQueue.UPLOADING) {
209 this._startNextFile();
212 var updatedEvent = event;
213 updatedEvent.file = event.target;
214 updatedEvent.originEvent = event;
216 var uploadedTotal = this.totalBytesUploaded;
218 Y.each(this.currentUploadedByteValues, function (value) {
219 uploadedTotal += value;
222 var percentLoaded = Math.min(100, Math.round(10000*uploadedTotal/this.totalBytes) / 100);
224 this.fire("totaluploadprogress", {bytesLoaded: uploadedTotal,
225 bytesTotal: this.totalBytes,
226 percentLoaded: percentLoaded});
228 this.fire("uploadcomplete", updatedEvent);
230 if (this.queuedFiles.length === 0 && this.numberOfUploads <= 0) {
231 this.fire("alluploadscomplete");
232 this._currentState = UploaderQueue.STOPPED;
239 * Handles and retransmits upload cancel event.
241 * @method _uploadCancelHandler
242 * @param event The event dispatched during the upload process.
245 _uploadCancelHandler : function (event) {
247 var updatedEvent = event;
248 updatedEvent.originEvent = event;
249 updatedEvent.file = event.target;
251 this.fire("uploadcacel", updatedEvent);
257 * Handles and retransmits upload progress event.
259 * @method _uploadProgressHandler
260 * @param event The event dispatched during the upload process.
263 _uploadProgressHandler : function (event) {
265 this.currentUploadedByteValues[event.target.get("id")] = event.bytesLoaded;
267 var updatedEvent = event;
268 updatedEvent.originEvent = event;
269 updatedEvent.file = event.target;
271 this.fire("uploadprogress", updatedEvent);
273 var uploadedTotal = this.totalBytesUploaded;
275 Y.each(this.currentUploadedByteValues, function (value) {
276 uploadedTotal += value;
279 var percentLoaded = Math.min(100, Math.round(10000*uploadedTotal/this.totalBytes) / 100);
281 this.fire("totaluploadprogress", {bytesLoaded: uploadedTotal,
282 bytesTotal: this.totalBytes,
283 percentLoaded: percentLoaded});
287 * Starts uploading the queued up file list.
289 * @method startUpload
291 startUpload: function() {
293 this.queuedFiles = this.get("fileList").slice(0);
294 this.numberOfUploads = 0;
295 this.currentUploadedByteValues = {};
296 this.currentFiles = {};
297 this.totalBytesUploaded = 0;
299 this._currentState = UploaderQueue.UPLOADING;
301 while (this.numberOfUploads < this.get("simUploads") && this.queuedFiles.length > 0) {
302 this._startNextFile();
307 * Pauses the upload process. The ongoing file uploads
308 * will complete after this method is called, but no
309 * new ones will be launched.
311 * @method pauseUpload
313 pauseUpload: function () {
314 this._currentState = UploaderQueue.STOPPED;
318 * Restarts a paused upload process.
320 * @method restartUpload
322 restartUpload: function () {
323 this._currentState = UploaderQueue.UPLOADING;
324 while (this.numberOfUploads < this.get("simUploads")) {
325 this._startNextFile();
330 * If a particular file is stuck in an ongoing upload without
331 * any progress events, this method allows to force its reupload
332 * by cancelling its upload and immediately relaunching it.
334 * @method forceReupload
335 * @param file {Y.File} The file to force reupload on.
337 forceReupload : function (file) {
338 var id = file.get("id");
339 if (this.currentFiles.hasOwnProperty(id)) {
341 this._unregisterUpload(file);
342 this.addToQueueTop(file);
343 this._startNextFile();
348 * Add a new file to the top of the queue (the upload will be
349 * launched as soon as the current number of uploading files
350 * drops below the maximum permissible value).
352 * @method addToQueueTop
353 * @param file {Y.File} The file to add to the top of the queue.
355 addToQueueTop: function (file) {
356 this.queuedFiles.unshift(file);
360 * Add a new file to the bottom of the queue (the upload will be
361 * launched after all the other queued files are uploaded.)
363 * @method addToQueueBottom
364 * @param file {Y.File} The file to add to the bottom of the queue.
366 addToQueueBottom: function (file) {
367 this.queuedFiles.push(file);
371 * Cancels a specific file's upload. If no argument is passed,
372 * all ongoing uploads are cancelled and the upload process is
375 * @method cancelUpload
376 * @param file {Y.File} An optional parameter - the file whose upload
377 * should be cancelled.
379 cancelUpload: function (file) {
382 var id = file.get("id");
383 if (this.currentFiles[id]) {
384 this.currentFiles[id].cancelUpload();
385 this._unregisterUpload(this.currentFiles[id]);
386 if (this._currentState === UploaderQueue.UPLOADING) {
387 this._startNextFile();
391 for (var i = 0, len = this.queuedFiles.length; i < len; i++) {
392 if (this.queuedFiles[i].get("id") === id) {
393 this.queuedFiles.splice(i, 1);
400 for (var fid in this.currentFiles) {
401 this.currentFiles[fid].cancelUpload();
402 this._unregisterUpload(this.currentFiles[fid]);
405 this.currentUploadedByteValues = {};
406 this.currentFiles = {};
407 this.totalBytesUploaded = 0;
408 this.fire("alluploadscancelled");
409 this._currentState = UploaderQueue.STOPPED;
416 * Static constant for the value of the `errorAction` attribute:
417 * prescribes the queue to continue uploading files in case of
424 CONTINUE: "continue",
427 * Static constant for the value of the `errorAction` attribute:
428 * prescribes the queue to stop uploading files in case of
438 * Static constant for the value of the `errorAction` attribute:
439 * prescribes the queue to restart a file upload immediately in case of
441 * @property RESTART_ASAP
446 RESTART_ASAP: "restartasap",
449 * Static constant for the value of the `errorAction` attribute:
450 * prescribes the queue to restart an errored out file upload after
451 * other files have finished uploading.
452 * @property RESTART_AFTER
457 RESTART_AFTER: "restartafter",
460 * Static constant for the value of the `_currentState` property:
461 * implies that the queue is currently not uploading files.
470 * Static constant for the value of the `_currentState` property:
471 * implies that the queue is currently uploading files.
472 * @property UPLOADING
477 UPLOADING: "uploading",
480 * The identity of the class.
484 * @default 'uploaderqueue'
489 NAME: 'uploaderqueue',
492 * Static property used to define the default attribute configuration of
503 * Maximum number of simultaneous uploads; must be in the
504 * range between 1 and 5. The value of `2` is default. It
505 * is recommended that this value does not exceed 3.
506 * @property simUploads
512 validator: function (val, name) {
513 return (val >= 1 && val <= 5);
518 * The action to take in case of error. The valid values for this attribute are:
519 * `Y.Uploader.Queue.CONTINUE` (the upload process should continue on other files,
520 * ignoring the error), `Y.Uploader.Queue.STOP` (the upload process
521 * should stop completely), `Y.Uploader.Queue.RESTART_ASAP` (the upload
522 * should restart immediately on the errored out file and continue as planned), or
523 * Y.Uploader.Queue.RESTART_AFTER (the upload of the errored out file should restart
524 * after all other files have uploaded)
525 * @property errorAction
527 * @default Y.Uploader.Queue.CONTINUE
531 validator: function (val, name) {
532 return (val === UploaderQueue.CONTINUE || val === UploaderQueue.STOP || val === UploaderQueue.RESTART_ASAP || val === UploaderQueue.RESTART_AFTER);
537 * The total number of bytes that has been uploaded.
538 * @property bytesUploaded
547 * The total number of bytes in the queue.
548 * @property bytesTotal
557 * The queue file list. This file list should only be modified
558 * before the upload has been started; modifying it after starting
559 * the upload has no effect, and `addToQueueTop` or `addToQueueBottom` methods
560 * should be used instead.
567 setter: function (val) {
569 Y.Array.each(newValue, function (value) {
570 this.totalBytes += value.get("size");
578 * A String specifying what should be the POST field name for the file
579 * content in the upload request.
581 * @attribute fileFieldName
590 * The URL to POST the file upload requests to.
592 * @attribute uploadURL
601 * Additional HTTP headers that should be included
602 * in the upload request. Due to Flash Player security
603 * restrictions, this attribute is only honored in the
606 * @attribute uploadHeaders
615 * A Boolean that specifies whether the file should be
616 * uploaded with the appropriate user credentials for the
617 * domain. Due to Flash Player security restrictions, this
618 * attribute is only honored in the HTML5 Uploader.
620 * @attribute withCredentials
630 * An object, keyed by `fileId`, containing sets of key-value pairs
631 * that should be passed as POST variables along with each corresponding
634 * @attribute perFileParameters
643 * The number of times to try re-uploading a file that failed to upload before
644 * cancelling its upload.
646 * @attribute retryCount
658 Y.namespace('Uploader');
659 Y.Uploader.Queue = UploaderQueue;
661 }, '3.7.1', {"requires": ["base"]});