Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

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