]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - jssource/src_files/include/javascript/yui3/build/async-queue/async-queue.js
Release 6.2.0beta4
[Github/sugarcrm.git] / jssource / src_files / include / javascript / yui3 / build / async-queue / async-queue.js
1 /*
2 Copyright (c) 2009, Yahoo! Inc. All rights reserved.
3 Code licensed under the BSD License:
4 http://developer.yahoo.net/yui/license.txt
5 version: 3.0.0
6 build: 1549
7 */
8 YUI.add('async-queue', function(Y) {
9
10 /**
11  * <p>AsyncQueue allows you create a chain of function callbacks executed
12  * via setTimeout (or synchronously) that are guaranteed to run in order.
13  * Items in the queue can be promoted or removed.  Start or resume the
14  * execution chain with run().  pause() to temporarily delay execution, or
15  * stop() to halt and clear the queue.</p>
16  *
17  * @module async-queue
18  */
19
20 /**
21  * <p>A specialized queue class that supports scheduling callbacks to execute
22  * sequentially, iteratively, even asynchronously.</p>
23  *
24  * <p>Callbacks can be function refs or objects with the following keys.  Only
25  * the <code>fn</code> key is required.</p>
26  *
27  * <ul>
28  * <li><code>fn</code> -- The callback function</li>
29  * <li><code>context</code> -- The execution context for the callbackFn.</li>
30  * <li><code>args</code> -- Arguments to pass to callbackFn.</li>
31  * <li><code>timeout</code> -- Millisecond delay before executing callbackFn.
32  *                     (Applies to each iterative execution of callback)</li>
33  * <li><code>iterations</code> -- Number of times to repeat the callback.
34  * <li><code>until</code> -- Repeat the callback until this function returns
35  *                         true.  This setting trumps iterations.</li>
36  * <li><code>autoContinue</code> -- Set to false to prevent the AsyncQueue from
37  *                        executing the next callback in the Queue after
38  *                        the callback completes.</li>
39  * <li><code>id</code> -- Name that can be used to get, promote, get the
40  *                        indexOf, or delete this callback.</li>
41  * </ul>
42  *
43  * @class AsyncQueue
44  * @extends EventTarget
45  * @constructor
46  * @param callback* {Function|Object} 0..n callbacks to seed the queue
47  */
48 Y.AsyncQueue = function() {
49     this._init();
50     this.add.apply(this, arguments);
51 };
52
53 var Queue   = Y.AsyncQueue,
54     EXECUTE = 'execute',
55     SHIFT   = 'shift',
56     PROMOTE = 'promote',
57     REMOVE  = 'remove',
58
59     isObject   = Y.Lang.isObject,
60     isFunction = Y.Lang.isFunction;
61
62 /**
63  * <p>Static default values used to populate callback configuration properties.
64  * Preconfigured defaults include:</p>
65  *
66  * <ul>
67  *  <li><code>autoContinue</code>: <code>true</code></li>
68  *  <li><code>iterations</code>: 1</li>
69  *  <li><code>timeout</code>: 10 (10ms between callbacks)</li>
70  *  <li><code>until</code>: (function to run until iterations &lt;= 0)</li>
71  * </ul>
72  *
73  * @property AsyncQueue.defaults
74  * @type {Object}
75  * @static
76  */
77 Queue.defaults = Y.mix({
78     autoContinue : true,
79     iterations   : 1,
80     timeout      : 10,
81     until        : function () {
82         this.iterations |= 0;
83         return this.iterations <= 0;
84     }
85 }, Y.config.queueDefaults || {});
86
87 Y.extend(Queue, Y.EventTarget, {
88     /**
89      * Used to indicate the queue is currently executing a callback.
90      *
91      * @property _running
92      * @type {Boolean|Object} true for synchronous callback execution, the
93      *                        return handle from Y.later for async callbacks.
94      *                        Otherwise false.
95      * @protected
96      */
97     _running : false,
98
99     /**
100      * Initializes the AsyncQueue instance properties and events.
101      *
102      * @method _init
103      * @protected
104      */
105     _init : function () {
106         Y.EventTarget.call(this, { emitFacade: true });
107
108         this._q = [];
109
110         /** 
111          * Callback defaults for this instance.  Static defaults that are not
112          * overridden are also included.
113          *
114          * @property defaults
115          * @type {Object}
116          */
117         this.defaults = {};
118
119         this._initEvents();
120     },
121
122     /**
123      * Initializes the instance events.
124      *
125      * @method _initEvents
126      * @protected
127      */
128     _initEvents : function () {
129         /*
130         this.publish({
131             'execute' : { defaultFn : this._defExecFn },
132             'shift'   : { defaultFn : this._defShiftFn },
133             'add'     : { defaultFn : this._defAddFn },
134             'promote' : { defaultFn : this._defPromoteFn },
135             'remove'  : { defaultFn : this._defRemoveFn }
136         });
137         */
138         this.publish('execute' , { defaultFn : this._defExecFn, emitFacade: true });
139         this.publish('shift'   , { defaultFn : this._defShiftFn, emitFacade: true });
140         this.publish('add'     , { defaultFn : this._defAddFn, emitFacade: true });
141         this.publish('promote' , { defaultFn : this._defPromoteFn, emitFacade: true });
142         this.publish('remove'  , { defaultFn : this._defRemoveFn, emitFacade: true });
143     },
144
145     /**
146      * Returns the next callback needing execution.  If a callback is
147      * configured to repeat via iterations or until, it will be returned until
148      * the completion criteria is met.
149      *
150      * When the queue is empty, null is returned.
151      *
152      * @method next
153      * @return {Function} the callback to execute
154      */
155     next : function () {
156         var callback;
157
158         while (this._q.length) {
159             callback = this._q[0] = this._prepare(this._q[0]);
160             if (callback && callback.until()) {
161                 this.fire(SHIFT, { callback: callback });
162                 callback = null;
163             } else {
164                 break;
165             }
166         }
167
168         return callback || null;
169     },
170
171     /**
172      * Default functionality for the &quot;shift&quot; event.  Shifts the
173      * callback stored in the event object's <em>callback</em> property from
174      * the queue if it is the first item.
175      *
176      * @method _defShiftFn
177      * @param e {Event} The event object
178      * @protected
179      */
180     _defShiftFn : function (e) {
181         if (this.indexOf(e.callback) === 0) {
182             this._q.shift();
183         }
184     },
185
186     /**
187      * Creates a wrapper function to execute the callback using the aggregated 
188      * configuration generated by combining the static AsyncQueue.defaults, the
189      * instance defaults, and the specified callback settings.
190      *
191      * The wrapper function is decorated with the callback configuration as
192      * properties for runtime modification.
193      *
194      * @method _prepare
195      * @param callback {Object|Function} the raw callback
196      * @return {Function} a decorated function wrapper to execute the callback
197      * @protected
198      */
199     _prepare: function (callback) {
200         if (isFunction(callback) && callback._prepared) {
201             return callback;
202         }
203
204         var config = Y.merge(
205             Queue.defaults,
206             { context : this, args: [], _prepared: true },
207             this.defaults,
208             (isFunction(callback) ? { fn: callback } : callback)),
209             
210             wrapper = Y.bind(function () {
211                 if (!wrapper._running) {
212                     wrapper.iterations--;
213                 }
214                 if (isFunction(wrapper.fn)) {
215                     wrapper.fn.apply(wrapper.context || Y,
216                                      Y.Array(wrapper.args));
217                 }
218             }, this);
219             
220         return Y.mix(wrapper, config);
221     },
222
223     /**
224      * Sets the queue in motion.  All queued callbacks will be executed in
225      * order unless pause() or stop() is called or if one of the callbacks is
226      * configured with autoContinue: false.
227      *
228      * @method run
229      * @return {AsyncQueue} the AsyncQueue instance
230      * @chainable
231      */
232     run : function () {
233         var callback,
234             cont = true;
235
236         for (callback = this.next();
237             cont && callback && !this.isRunning();
238             callback = this.next())
239         {
240             cont = (callback.timeout < 0) ?
241                 this._execute(callback) :
242                 this._schedule(callback);
243         }
244
245         if (!callback) {
246             /**
247              * Event fired after the last queued callback is executed.
248              * @event complete
249              */
250             this.fire('complete');
251         }
252
253         return this;
254     },
255
256     /**
257      * Handles the execution of callbacks. Returns a boolean indicating
258      * whether it is appropriate to continue running.
259      *
260      * @method _execute
261      * @param callback {Object} the callback object to execute
262      * @return {Boolean} whether the run loop should continue
263      * @protected
264      */
265     _execute : function (callback) {
266         this._running = callback._running = true;
267
268         callback.iterations--;
269         this.fire(EXECUTE, { callback: callback });
270
271         var cont = this._running && callback.autoContinue;
272
273         this._running = callback._running = false;
274
275         return cont;
276     },
277
278     /**
279      * Schedules the execution of asynchronous callbacks.
280      *
281      * @method _schedule
282      * @param callback {Object} the callback object to execute
283      * @return {Boolean} whether the run loop should continue
284      * @protected
285      */
286     _schedule : function (callback) {
287         this._running = Y.later(callback.timeout, this, function () {
288             if (this._execute(callback)) {
289                 this.run();
290             }
291         });
292
293         return false;
294     },
295
296     /**
297      * Determines if the queue is waiting for a callback to complete execution.
298      *
299      * @method isRunning
300      * @return {Boolean} true if queue is waiting for a 
301      *                   from any initiated transactions
302      */
303     isRunning : function () {
304         return !!this._running;
305     },
306
307     /**
308      * Default functionality for the &quot;execute&quot; event.  Executes the
309      * callback function
310      *
311      * @method _defExecFn
312      * @param e {Event} the event object
313      * @protected
314      */
315     _defExecFn : function (e) {
316         e.callback();
317     },
318
319     /**
320      * Add any number of callbacks to the end of the queue. Callbacks may be
321      * provided as functions or objects.
322      *
323      * @method add
324      * @param callback* {Function|Object} 0..n callbacks
325      * @return {AsyncQueue} the AsyncQueue instance
326      * @chainable
327      */
328     add : function () {
329         this.fire('add', { callbacks: Y.Array(arguments,0,true) });
330
331         return this;
332     },
333
334     /**
335      * Default functionality for the &quot;add&quot; event.  Adds the callbacks
336      * in the event facade to the queue. Callbacks successfully added to the
337      * queue are present in the event's <code>added</code> property in the
338      * after phase.
339      *
340      * @method _defAddFn
341      * @param e {Event} the event object
342      * @protected
343      */
344     _defAddFn : function(e) {
345         var _q = this._q,
346             added = [];
347
348         Y.Array.each(e.callbacks, function (c) {
349             if (isObject(c)) {
350                 _q.push(c);
351                 added.push(c);
352             }
353         });
354
355         e.added = added;
356     },
357
358     /**
359      * Pause the execution of the queue after the execution of the current
360      * callback completes.  If called from code outside of a queued callback,
361      * clears the timeout for the pending callback. Paused queue can be
362      * restarted with q.run()
363      *
364      * @method pause
365      * @return {AsyncQueue} the AsyncQueue instance
366      * @chainable
367      */
368     pause: function () {
369         if (isObject(this._running)) {
370             this._running.cancel();
371         }
372
373         this._running = false;
374
375         return this;
376     },
377
378     /**
379      * Stop and clear the queue after the current execution of the
380      * current callback completes.
381      *
382      * @method stop
383      * @return {AsyncQueue} the AsyncQueue instance
384      * @chainable
385      */
386     stop : function () { 
387         this._q = [];
388
389         return this.pause();
390     },
391
392     /** 
393      * Returns the current index of a callback.  Pass in either the id or
394      * callback function from getCallback.
395      *
396      * @method indexOf
397      * @param callback {String|Function} the callback or its specified id
398      * @return {Number} index of the callback or -1 if not found
399      */
400     indexOf : function (callback) {
401         var i = 0, len = this._q.length, c;
402
403         for (; i < len; ++i) {
404             c = this._q[i];
405             if (c === callback || c.id === callback) {
406                 return i;
407             }
408         }
409
410         return -1;
411     },
412
413     /**
414      * Retrieve a callback by its id.  Useful to modify the configuration
415      * while the queue is running.
416      *
417      * @method getCallback
418      * @param id {String} the id assigned to the callback
419      * @return {Object} the callback object
420      */
421     getCallback : function (id) {
422         var i = this.indexOf(id);
423
424         return (i > -1) ? this._q[i] : null;
425     },
426
427     /**
428      * Promotes the named callback to the top of the queue. If a callback is
429      * currently executing or looping (via until or iterations), the promotion
430      * is scheduled to occur after the current callback has completed.
431      *
432      * @method promote
433      * @param callback {String|Object} the callback object or a callback's id
434      * @return {AsyncQueue} the AsyncQueue instance
435      * @chainable
436      */
437     promote : function (callback) {
438         var payload = { callback : callback },e;
439
440         if (this.isRunning()) {
441             e = this.after(SHIFT, function () {
442                     this.fire(PROMOTE, payload);
443                     e.detach();
444                 }, this);
445         } else {
446             this.fire(PROMOTE, payload);
447         }
448
449         return this;
450     },
451
452     /**
453      * <p>Default functionality for the &quot;promote&quot; event.  Promotes the
454      * named callback to the head of the queue.</p>
455      *
456      * <p>The event object will contain a property &quot;callback&quot;, which
457      * holds the id of a callback or the callback object itself.</p>
458      *
459      * @method _defPromoteFn
460      * @param e {Event} the custom event
461      * @protected
462      */
463     _defPromoteFn : function (e) {
464         var i = this.indexOf(e.callback),
465             promoted = (i > -1) ? this._q.splice(i,1)[0] : null;
466
467         e.promoted = promoted;
468
469         if (promoted) {
470             this._q.unshift(promoted);
471         }
472     },
473
474     /**
475      * Removes the callback from the queue.  If the queue is active, the
476      * removal is scheduled to occur after the current callback has completed.
477      *
478      * @method remove
479      * @param callback {String|Object} the callback object or a callback's id
480      * @return {AsyncQueue} the AsyncQueue instance
481      * @chainable
482      */
483     remove : function (callback) {
484         var payload = { callback : callback },e;
485
486         // Can't return the removed callback because of the deferral until
487         // current callback is complete
488         if (this.isRunning()) {
489             e = this.after(SHIFT, function () {
490                     this.fire(REMOVE, payload);
491                     e.detach();
492                 },this);
493         } else {
494             this.fire(REMOVE, payload);
495         }
496
497         return this;
498     },
499
500     /**
501      * <p>Default functionality for the &quot;remove&quot; event.  Removes the
502      * callback from the queue.</p>
503      *
504      * <p>The event object will contain a property &quot;callback&quot;, which
505      * holds the id of a callback or the callback object itself.</p>
506      *
507      * @method _defRemoveFn
508      * @param e {Event} the custom event
509      * @protected
510      */
511     _defRemoveFn : function (e) {
512         var i = this.indexOf(e.callback);
513
514         e.removed = (i > -1) ? this._q.splice(i,1)[0] : null;
515     },
516
517     /**
518      * Returns the number of callbacks in the queue.
519      *
520      * @method size
521      * @return {Number}
522      */
523     size : function () {
524         // next() flushes callbacks that have met their until() criteria and
525         // therefore shouldn't count since they wouldn't execute anyway.
526         if (!this.isRunning()) {
527             this.next();
528         }
529
530         return this._q.length;
531     }
532 });
533
534
535
536 }, '3.0.0' ,{requires:['event-custom']});