/**
* Surrogate Class Feature
*/
/*global dessert, troop */
(function () {
"use strict";
var hOP = Object.prototype.hasOwnProperty;
/**
* @class troop.Surrogate
* @ignore
*/
troop.Surrogate = {
/**
* Adds surrogates buffer to class.
* @this {troop.Base}
*/
initSurrogates: function () {
this.addConstants(/** @lends troop.Base */{
/**
* Container for surrogate info. Added to class via .initSurrogates().
* @type {object}
*/
surrogateInfo: {
/**
* @type {function}
*/
preparationHandler: undefined,
/**
* @type {object[]}
*/
descriptors: []
}
});
},
/**
* Retrieves first surrogate fitting constructor arguments.
* @this {troop.Base}
* @returns {troop.Base}
*/
getSurrogate: function () {
var args = arguments,
preparationHandler,
descriptors,
i, descriptor;
/**
* Surrogate info property must be the class' own property
* otherwise surrogates would be checked on instantiating
* every descendant of the current class, too.
* This would be wasteful, unnecessary, and confusing.
*/
if (hOP.call(this, 'surrogateInfo')) {
// dealing with preparation handler
preparationHandler = this.surrogateInfo.preparationHandler;
if (preparationHandler) {
args = preparationHandler.apply(this, arguments) || arguments;
}
// going through descriptors and determining surrogate
descriptors = this.surrogateInfo.descriptors;
for (i = 0; i < descriptors.length; i++) {
descriptor = descriptors[i];
// determining whether arguments fit next filter
if (descriptor.filter.apply(this, args)) {
return descriptor.namespace[descriptor.className];
}
}
}
// returning caller as fallback
return this;
}
};
troop.Base.addMethods(/** @lends troop.Base */{
/**
* Adds a handler to be called before evaluating any of the surrogate filters.
* The specified handler receives the original constructor arguments and is expected to
* return a modified argument list (array) that will be passed to the surrogate filters.
* @param {function} handler
* @returns {troop.Base}
* @see troop.Base.addSurrogate
*/
prepareSurrogates: function (handler) {
dessert.isFunction(handler, "Invalid handler");
if (!hOP.call(this, 'surrogateInfo')) {
troop.Surrogate.initSurrogates.call(this);
}
this.surrogateInfo.preparationHandler = handler;
return this;
},
/**
* Adds a surrogate class to the current class. Instantiation is forwarded to the first surrogate where
* the filter returns true.
* @param {object} namespace Namespace in which the surrogate class resides.
* @param {string} className Surrogate class name. The class the namespace / class name point to does not
* have to exist (or be resolved when postponed) at the time of adding the filter.
* @param {function} filter Function evaluating whether the surrogate class specified by the namespace
* and class name fits the arguments.
* @example
* var ns = {}; // namespace
* ns.Horse = troop.Base.extend()
* .prepareSurrogates(function (height) {
* return [height < 5]; // isPony
* })
* .addSurrogate(ns, 'Pony', function (isPony) {
* return isPony;
* })
* .addMethods({ init: function () {} });
* ns.Pony = ns.Horse.extend()
* .addMethods({ init: function () {} });
* var myHorse = ns.Horse.create(10), // instance of ns.Horse
* myPony = ns.Horse.create(3); // instance of ns.Pony
* @returns {troop.Base}
*/
addSurrogate: function (namespace, className, filter) {
dessert
.isObject(namespace, "Invalid namespace object")
.isString(className, "Invalid class name")
.isFunction(filter, "Invalid filter function");
if (!hOP.call(this, 'surrogateInfo')) {
troop.Surrogate.initSurrogates.call(this);
}
this.surrogateInfo.descriptors.push({
namespace: namespace,
className: className,
filter : filter
});
return this;
}
});
}());