/**
* @module proact-flow
*/
/**
* <p>
* Creates a queue of {{#crossLink "ProAct.Queue"}}{{/crossLink}}s. The order of these sub-queues is used
* to determine the order in which they will be dequed.
* </p>
* <p>
* The idea of this class is to have different queues for the different layers
* of an application. That way lower level actions will always execuded before higher level.
* </p>
* <p>
* If a higher level queue enques actions in lower level one, the action flow returns stops and returns
* from the lower level one.
* </p>
* <p>
* The {{#crossLink "ProAct.Queues/go:method"}}{{/crossLink}} method deques all the actions from all the queues and executes them in the right
* order, using their priorities and queue order.
* </p>
* <p>
* A `ProAct.Queues` can be used to setup very complex the action flow.
* ProAct.js uses it with only one queue - 'proq' to create an action flow if something changes.
* </p>
*
* TODO We need to pass before, after and error callbacks here too. ~meddle@2014-07-10
*
* @class ProAct.Queues
* @constructor
* @param {Array} queueNames
* Array with the names of the sub-queues. The size of this array determines
* the number of the sub-queues.
* @param {Object} options
* Various options for the ProAct.Queues.
* <p>Available options:</p>
* <ul>
* <li>queue - An options object containing options to be passed to all the sub-queues. For more information see {{#crossLink "ProAct.Queue"}}{{/crossLink}}.</li>
* </ul>
*/
ProAct.Queues = P.QQ = function (queueNames, options) {
if (!queueNames) {
queueNames = ['proq'];
}
this.queueNames = queueNames;
this.options = options || {};
this._queues = {};
var i, ln = this.queueNames.length;
for (i = 0; i < ln; i++) {
this._queues[this.queueNames[i]] = new P.Q(this.queueNames[i], this.options.queue);
}
};
P.QQ.prototype = {
/**
* Reference to the constructor of this object.
*
* @property constructor
* @type ProAct.Queues
* @final
* @for ProAct.Queues
*/
constructor: ProAct.Queues,
/**
* Checks if this `ProAct.Queues` is empty.
*
* @for ProAct.Queues
* @instance
* @method isEmpty
* @return {Boolean}
* True if there are no actions in any of the sub-queues.
*/
isEmpty: function () {
var queues = this._queues,
names = this.queueNames,
length = names.length,
i, currentQueueName, currentQueue;
for (i = 0; i < length; i++) {
currentQueueName = names[i];
currentQueue = queues[currentQueueName];
if (!currentQueue.isEmpty()) {
return false;
}
}
return true;
},
/**
* Pushes an action to a sub-queue.
* This method can enque the same action multiple times and always with priority of '1'.
* <p>
* `defer`, `enque` and `add` are aliases of this method.
* </p>
*
* @for ProAct.Queues
* @instance
* @method push
* @param {String} queueName
* The name of the queue to enque the action in.
* <p>
* On the place of this argument the context can be passed and the queue to push in
* becomes the first queue of the sub-queues.
* </p>
* @param {Object} context
* The context of the action.
* It can be null.
* <p>
* If the method is called with a Function context, the context becomes the action.
* This way the method can be called with only one parameter for actions without context.
* </p>
* @param {Function} action
* The action to enque.
* <p>
* If there is no context and the action is passed in place of the context,
* this parameter can hold the arguments of the action.
* </p>
* @param {Array} args
* Arguments to be passed to the action when it is executed.
*/
push: function (queueName, context, action, args) {
if (queueName && !(typeof(queueName) === 'string')) {
args = action;
action = context;
context = queueName;
queueName = this.queueNames[0];
}
if (!queueName) {
queueName = this.queueNames[0];
}
var queue = this._queues[queueName];
if (queue) {
queue.push(context, action, args);
}
},
/**
* Pushes an action to a sub-queue only once.
* <p>
* If the action is pushed for the second time using this method, instead of
* adding it to the sub-queue, its priority goes up and its arguments are updated.
* This means that this action will be executed after all the other actions, pushed only once.
* </p>
* <p>
* `deferOnce`, `enqueOnce` and `addOnce` are aliases of this method.
* </p>
*
* @for ProAct.Queues
* @instance
* @method pushOnce
* @param {String} queueName
* The name of the queue to enque the action in.
* <p>
* On the place of this argument the context can be passed and the queue to push in
* becomes the first queue of the sub-queues.
* </p>
* @param {Object} context
* The context of the action.
* It can be null.
* <p>
* If the method is called with a Function context, the context becomes the action.
* This way the method can be called with only one parameter for actions without context.
* </p>
* @param {Function} action
* The action to enque.
* <p>
* If there is no context and the action is passed in place of the context,
* this parameter can hold the arguments of the action.
* </p>
* @param {Array} args
* Arguments to be passed to the action when it is executed.
*/
pushOnce: function (queueName, context, action, args) {
if (queueName && !(typeof(queueName) === 'string')) {
args = action;
action = context;
context = queueName;
queueName = this.queueNames[0];
}
if (!queueName) {
queueName = this.queueNames[0];
}
var queue = this._queues[queueName];
if (queue) {
queue.pushOnce(context, action, args);
}
},
/**
* Starts the action flow.
* <p>
* Executes the actions in all the sub-queues in the order they were enqued, but also uses the priorities
* to execute these with numerically higher priority after these with numerically lower priority.
* </p>
* <p>
* If some of the actions in the third queue pushes new actions to the second queue, the action flow returns
* to the second queue again and then continues through all the queues.
* </p>
* <p>
* `run` and `flush` are aliases of this method.
* </p>
*
* @for ProAct.Queues
* @instance
* @method go
* @param {String} queueName
* The name of the queue to begin from. Can be null and defaults to the first sub-queue.
*/
go: function (queueName) {
var currentQueueIndex = 0,
queues = this._queues,
names = this.queueNames,
i, length = this.queueNames.length,
currentQueueName, currentQueue,
prevQueueIndex;
if (queueName) {
for (i = 0; i < length; i++) {
if (names[i] === queueName) {
currentQueueIndex = i;
}
}
}
goloop:
while (currentQueueIndex < length) {
currentQueueName = names[currentQueueIndex];
currentQueue = queues[currentQueueName];
currentQueue.go(true);
if ((prevQueueIndex = this._probePrevIndex(currentQueueIndex)) !== -1) {
currentQueueIndex = prevQueueIndex;
continue goloop;
}
currentQueueIndex = currentQueueIndex + 1;
}
},
_probePrevIndex: function (startIndex) {
var queues = this._queues,
names = this.queueNames,
i, currentQueueName, currentQueue;
for (i = 0; i <= startIndex; i++) {
currentQueueName = names[i];
currentQueue = queues[currentQueueName];
if (!currentQueue.isEmpty()) {
return i;
}
}
return -1;
}
};
P.QQ.prototype.defer = P.QQ.prototype.enque = P.QQ.prototype.add = P.QQ.prototype.push;
P.QQ.prototype.deferOnce = P.QQ.prototype.enqueOnce = P.QQ.prototype.addOnce = P.QQ.prototype.pushOnce;
P.QQ.prototype.flush = P.QQ.prototype.run = P.QQ.prototype.go;