~landscape/lazr-js/trunk

« back to all changes in this revision

Viewing changes to src-js/lazrjs/yui/3.0.0/build/async-queue/async-queue.js

  • Committer: Sidnei da Silva
  • Date: 2009-10-21 21:43:07 UTC
  • mfrom: (120.2.15 yui-3.0.0)
  • mto: (124.5.1 trunk)
  • mto: This revision was merged to the branch mainline in revision 126.
  • Revision ID: sidnei.da.silva@canonical.com-20091021214307-mpul9404n317puk5
- Merge from yui-3.0.0, resolving conflicts

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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']});