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