/**
* The `proact-properties` module provides stateful reactive values attached to normal JavaScript
* object's fields.
*
* @module proact-properties
* @main proact-properties
*/
/**
* <p>
* Constructs a `ProAct.Property`.
* The properties are simple {{#crossLink "ProAct.Actor"}}{{/crossLink}}s with state.
* The basic property has a state of a simple value - number/string/boolean.
* </p>
* <p>
* Every property could represent a field in a plain JavaScript object.
* It makes it reactive, on reading the property value,
* if {{#crossLink "ProAct/currentCaller:property"}}{{/crossLink}} is set,
* it is added as a listener to the property changes.
* </p>
* <p>
* Every property has a type. The default property has a type of a simple value.
* </p>
* <p>
* All the properties of an object are managed by its {{#crossLink "ProAct.ObjectCore"}}{{/crossLink}},
* which is set to a hidden field of the object - '__pro__'.
* </p>
* <p>
* When created every property is in {{#crossLink "ProAct.States/init:property"}}{{/crossLink}}, state,
* when it is functional, the state is changed to {{#crossLink "ProAct.States/ready:property"}}{{/crossLink}}.
* If the property is not in {{#crossLink "ProAct.States/ready:property"}}{{/crossLink}} state, it is not useable.
* </p>
* <p>
* {{#crossLink "ProAct.Actor/init:method"}}{{/crossLink}} is called by this constructor for the property initialization.
* It should initialize the property and set its state to {{#crossLink "ProAct.States/ready:property"}}{{/crossLink}}.
* </p>
* <p>
* ProAct.Property is part of the `proact-properties` module of `ProAct.js`.
* </p>
*
* Examples:
* ```
* var property = new Property({v: 5}, 'v');
* property.get(); // This is 5
* property.set(4);
* property.get(); // This is 4
* ```
*
* @class ProAct.Property
* @extends ProAct.Actor
* @constructor
* @param {String} queueName
* The name of the queue all the updates should be pushed to.
* <p>
* If this parameter is null/undefined the default queue of
* {{#crossLink "ProAct/flow:property"}}{{/crossLink}} is used.
* </p>
* <p>
* If this parameter is not a string it is used as the
* <i>proObject</i>.
* </p>
* @param {Object} proObject
* A plain JavaScript object, holding a field, this property will represent.
* @param {String} property
* The name of the field of the object, this property should represent.
* @param {Function} getter
* An optional getter to be used when the property is read.
* If this parameter is empty, a new {{#crossLink "ProAct.Property/defaultGetter:method"}}{{/crossLink}} is build for <i>this</i>.
* @param {Function} setter
* An optional setter to be used when the property is written.
* If this parameter is empty, a new {{#crossLink "ProAct.Property/defaultSetter:method"}}{{/crossLink}} is build for <i>this</i>.
*/
function Property (queueName, proObject, property, getter, setter) {
if (queueName && !P.U.isString(queueName)) {
setter = getter;
getter = property;
property = proObject;
proObject = queueName;
queueName = null;
}
if (!(proObject || property)) {
property = 'v';
proObject = {v: null};
}
P.U.defValProp(this, 'proObject', false, false, true, proObject);
this.property = property;
if (!this.proObject.__pro__) {
P.U.defValProp(proObject, '__pro__', false, false, true, new ProAct.ObjectCore(proObject));
}
this.proObject.__pro__.properties[property] = this;
this.get = getter || P.P.defaultGetter(this);
this.set = setter || P.P.defaultSetter(this);
this.oldVal = null;
this.val = proObject[property];
this.g = this.get;
this.s = this.set;
P.Actor.call(this, queueName); // Super!
this.parent = this.proObject.__pro__;
var meta = this.parent.meta.p;
this.isStaticTyped = meta && meta.statics && meta.statics.indexOf(this.property) !== -1;
}
ProAct.Property = P.P = Property;
P.U.ex(ProAct.Property, {
/**
* Defines the possible types of the `ProAct.Property`.
*
* @class Types
* @namespace ProAct.Property
* @static
*/
Types: {
/**
* ProAct.Property for simple types - Numbers, Strings or Booleans.
*
* @property simple
* @type Number
* @final
* @for ProAct.Property.Types
*/
simple: 'simple', // strings, booleans and numbers
/**
* ProAct.Property for auto computed types - Functions.
*
* @property auto
* @type Number
* @final
* @for ProAct.Property.Types
*/
auto: {}, // functions - dependent
/**
* ProAct.Property for object types - fields containing objects.
*
* @property object
* @type Number
* @final
* @for ProAct.Property.Types
*/
object: {}, // references Pro objects
/**
* ProAct.Property for nil types - fields containing null or undefined.
*
* @property nil
* @type Number
* @final
* @for ProAct.Property.Types
*/
nil: {}, // nulls
/**
* Retrieves the right` ProAct.Property.Types` value from a value.
*
* @for ProAct.Property.Types
* @method type
* @param {Object} value
* The value to use to compute the `ProAct.Property.Types` member for.
* @return {Number}
* The type of the passed value.
*/
type: function (value) {
if (value === null) {
return P.P.Types.nil;
} else if (P.U.isFunction(value)) {
return P.P.Types.auto;
} else if (P.U.isObject(value)) {
return P.P.Types.object;
} else {
return P.P.Types.simple;
}
}
},
/**
* Generates a default getter function for a `ProAct.Property` instance.
* <p>
* Every `ProAct.Property` instance has a getter and a setter,
* they can be passed in the constructor, but if left blank,
* this method is used for creating the getter function.
* </p>
* <p>
* The default getter function uses {{#crossLink "ProAct.Property/addCaller:method"}}{{/crossLink}}
* method to track the {{#crossLink "ProAct/currentCaller:property"}}{{/crossLink}} listener if set.
* If it is set it is added as a listener to the passed <i>property</i>.
* </p>
*
* @for ProAct.Property
* @static
* @private
* @method defaultGetter
* @param {ProAct.Property} property
* The `ProAct.Property` instance to generate a getter function for.
* @return {Function}
* The generated getter function.
*/
defaultGetter: function (property) {
return function () {
property.addCaller();
return property.val;
};
},
/**
* Generates a default setter function for a ProAct.Property instance.
* <p>
* Every `ProAct.Property` instance has a setter and a getter,
* they can be passed in the constructor, but if left blank,
* this method is used for creating the setter function.
* </p>
* <p>
* The default setter function uses the {{#crossLink "ProAct.ActorUtil/update:method"}}{{/crossLink}}
* method to update all the listeners for <i>change</i>s for the passed <i>property</i>.
* </p>
* <p>
* It updates the listeners only if the new value of the property
* is different from the old one (using <i>===</i> for the comparison).
* </p>
*
* @for ProAct.Property
* @private
* @method defaultSetter
* @static
* @param {ProAct.Property} property
* The `ProAct.Property` instance to generate a setter function for.
* @param {Function} setter
* A setter function for the way of setting the value.
* It can be skipped if the value should be set using <i>=</i>.
* @return {Function}
* The generated setter function.
*/
defaultSetter: function (property, setter) {
return function (newVal) {
if (property.state != P.States.ready) {
return;
}
newVal = P.Actor.transform(property, newVal);
if (newVal === P.Actor.BadValue || property.val === newVal) {
return;
}
property.oldVal = property.val;
if (setter) {
property.val = setter.call(property.proObject, newVal);
} else {
property.val = newVal;
}
if (property.type() !== P.P.Types.auto && P.P.Types.type(property.val) !== property.type()) {
P.P.reProb(property).update();
return;
}
ActorUtil.update.call(property);
};
},
/**
* Used to define the managed by a `ProAct.Property` instance field of the passed <i>obj</i>.
* <p>
* The field is writable, enumerable and configurable.
* </p>
*
* @for ProAct.Property
* @method defineProp
* @private
* @static
* @param {Object} obj
* The object which field should be defined as a property.
* @param {String} prop
* The name of the property field to define.
* @param {Function} get
* The getter that should be used to read the new property to be defined.
* @param {Function} set
* The setter that should be used to update the new property to be defined.
*/
defineProp: function (obj, prop, get, set) {
Object.defineProperty(obj, prop, {
get: get,
set: set,
enumerable: true,
configurable: true
});
},
/**
* Recreates a property, using its current value.
* <p>
* The re-definition works by using {{#crossLink "ProAct.Property/destroy:method"}}{{/crossLink}}
* to destroy the passed <i>property</i> first, and then the
* {{#crossLink "ProAct.ObjectCore/makeProp:method"}}{{/crossLink}} method is called of the
* {{#crossLink "ProAct.ObjectCore"}}{{/crossLink}} of the object the <i>property</i> belongs to.
* </p>
* <p>
* This way a new `ProAct.Property` instance is created to replace the passed one.
* </p>
*
* @for ProAct.Property
* @private
* @method reProb
* @static
* @param {ProAct.Property} property
* The ProAct.Property instance to re-define.
* @return {ProAct.Property}
* The new re-defined property.
*/
reProb: function (property) {
if (property.isStaticTyped || property.state !== P.States.ready) {
return;
}
var po = property.proObject,
p = property.property,
l = property.listeners.change;
property.destroy();
return po.__pro__.makeProp(p, l);
},
/**
* Creates a constant property. It's value can not be changed.
*
* ```
* var property = ProAct.Property.constant(5);
*
* console.log(property.get()); // 5
*
* property.set(4);
* console.log(property.get()); // 5
* ```
*
* @for ProAct.Property
* @static
* @method constant
* @param {Object} val The value of the property. Can not be changed.
* @param {Object} meta Optional meta data for the property.
* @param {String} queueName The name of the queue all the updates should be pushed to. By default the default queue is used.
* @return {ProAct.Property} The new constant property.
*/
constant: function (val, meta, queueName) {
return P.P.value(val, meta, queueName).close();
},
/**
* Creates a value property. It's value can be updated any time and other properties may depend on it.
*
* This propety is eager - this means that it is initialized automatically even if it's not used.
*
* ```
* var property = ProAct.Property.value(5);
*
* console.log(property.get()); // 5
*
* property.set(4);
* console.log(property.get()); // 4
* ```
*
* @for ProAct.Property
* @static
* @method value
* @param {Object} val The value of the property.
* @param {Object} meta Optional meta data for the property.
* @param {String} queueName The name of the queue all the updates should be pushed to. By default the default queue is used.
* @return {ProAct.Property} The new value property.
*/
value: function (val, meta, queueName) {
var property = P.P.lazyValue(val, meta, queueName);
property.get();
return property;
},
/**
* Creates a lazy initialized value property. It's value can be updated any time and other properties may depend on it.
*
* Being lazy means, that the property won't be initialized until it is read (it's get() method is called).
*
* ```
* var property = ProAct.Property.lazyValue(5);
*
* console.log(property.get()); // 5
*
* property.set(4);
* console.log(property.get()); // 4
* ```
*
* @for ProAct.Property
* @static
* @method lazyValue
* @param {Object} val The value of the property.
* @param {Object} meta Optional meta data for the property.
* @param {String} queueName The name of the queue all the updates should be pushed to. By default the default queue is used.
* @return {ProAct.Property} The new lazily initialized value property.
*/
lazyValue: function (val, meta, queueName) {
if (meta && (P.U.isString(meta) || P.U.isArray(meta))) {
meta = {
v: meta
};
}
meta = meta || {};
meta.p = meta.p || {};
meta.p.statics = meta.p.statics || ['v'];
if (queueName) {
meta.p.queueName = queueName;
}
var object = {v: val},
core = new ObjectCore(object, meta);
P.U.defValProp(object, '__pro__', false, false, false, core);
core.prob();
return core.properties.v;
}
});
ProAct.Property.prototype = P.U.ex(Object.create(P.Actor.prototype), {
/**
* Reference to the constructor of this object.
*
* @property constructor
* @type ProAct.Property
* @final
* @for ProAct.Property
*/
constructor: ProAct.Property,
/**
* Retrieves the {{#crossLink "ProAct.Property.Types"}}{{/crossLink}} value of <i>this</i> property.
* <p>
* For instances of the base class - `ProAct.Property` it is
* {{#crossLink "ProAct.Property.Types/simple:property"}}{{/crossLink}}.
* </p>
*
* @for ProAct.Property
* @instance
* @method type
* @return {Number}
* The right type of the property.
*/
type: function () {
return P.P.Types.simple;
},
/**
* Creates the <i>event</i> to be send to the listeners of this `ProAct.Property` on update.
* <p>
* The <i>event</i> should be an instance of {{#crossLink "ProAct.Event"}}{{/crossLink}}.
* </p>
* <p>
* By default this method returns {{#crossLink "ProAct.Event.Types/value:property"}}{{/crossLink}} event with target the property name and arguments:
* <ul>
* <li>The object this `ProAct.Property` manages a field for.</li>
* <li>The old value of this property.</li>
* <li>The new value of this property.</li>
* </ul>
* </p>
*
* @for ProAct.Property
* @instance
* @protected
* @method makeEvent
* @default {ProAct.Event} with type {{#crossLink "ProAct.Event.Types/value:property"}}{{/crossLink}}.
* @param {ProAct.Event} source
* The source event of the event. It can be null
* @return {ProAct.ValueEvent}
* The event, created.
*/
makeEvent: function (source) {
return new P.VE(source, this.property, this.proObject, this.oldVal, this.val);
},
/**
* Creates the <i>listener</i> of this `ProAct.Property`.
* <p>
* This listener turns the observable in a observer.
* </p>
* <p>
* The listener for ProAct.Property is an object defining the <i>call</i> method.
* </p>
* <p>
* It has a <i>property</i> field set to <i>this</i>.
* </p>
* <p>
* On value changes the <i><this</i> value is set to the new value using the {{#crossLink "ProAct.Actor/transform:method"}}{{/crossLink}} to transform it.
* </p>
*
* @for ProAct.Property
* @instance
* @protected
* @method makeListener
* @return {Object}
* The <i>listener of this ProAct.Property</i>.
*/
makeListener: function () {
if (!this.listener) {
var self = this;
this.listener = {
property: self,
queueName: self.queueName,
call: function (newVal) {
if (newVal && newVal.type !== undefined && newVal.type === P.E.Types.value && newVal.args.length === 3 && newVal.target) {
newVal = newVal.args[0][newVal.target];
}
self.set(newVal);
}
};
}
return this.listener;
},
/**
* Initializes this property.
* <p>
* First the property is defined as a field in its object,
* using {{#crossLink "ProAct.Property/defineProp:method"}}{{/crossLink}}.
* </p>
*
* @for ProAct.Property
* @instance
* @protected
* @method doInit
*/
doInit: function () {
P.P.defineProp(this.proObject, this.property, this.get, this.set);
P.P.defineProp(this, 'v', this.get, this.set);
},
/**
* Uses {{#crossLink "ProAct/currentCaller:property"}}{{/crossLink}} to
* automatically add a new listener to this property if the caller is set.
* <p>
* This method is used by the default getter to make every reader of the property a listener to it.
* </p>
*
* @for ProAct.Property
* @protected
* @instance
* @method addCaller
*/
addCaller: function () {
var caller = P.currentCaller;
if (caller && caller.property !== this) {
this.on(caller);
}
},
/**
* A hook that is called right before destruction, the extenders use it to clean up resources.
*
* The `ProAct.Property` deletes its state and is removed from its core container.
*
* Don't override it.
*
* @for ProAct.Property
* @protected
* @instance
* @method beforeDestroy
*/
beforeDestroy: function () {
delete this.proObject.__pro__.properties[this.property];
this.oldVal = undefined;
P.U.defValProp(this.proObject, this.property, true, true, true, this.val);
this.get = this.set = this.property = this.proObject = undefined;
this.g = this.s = undefined;
this.val = undefined;
this.isStaticTyped = undefined;
delete this.v;
},
/**
* Creates a new `ProAct.Property` instance with source <i>this</i> and mapping
* the passed <i>mapping function</i>.
*
* When the source is changed, the product of this operator is updated too.
*
* ```
* var val = ProAct.Property.value(5);
* var plusOne = val.map(function (v) {
* return v + 1;
* });
*
* plusOne.get(); // 6
*
* val.set(4);
* plusOne.get(); // 5
* ```
*
* or
*
* ```
* var positive = val.map('+');
*
* val.set(-4);
*
* positive.get(); // 4
* ```
*
* @for ProAct.Property
* @instance
* @method map
* @param {Object|Function|Strin} mappingFunction
* Function or object with a <i>call method</i> to use as map function.
* Can be string for predefined mapping functions.
* @return {ProAct.Property}
* A new `ProAct.Property` instance with the <i>mapping</i> applied.
*/
map: function (mappingFunction) {
var prop = P.P.value(this.val, {}, this.queueName).mapping(mappingFunction).into(this);
ActorUtil.update.call(this);
return prop;
},
/**
* Creates a new `ProAct.Property` instance with source <i>this</i> and filtering
* the passed <i>filtering function</i>.
*
* When the source changes, the product, may be updated.
*
* TODO On creation if the filter fails, the property keeps the original value.
* What to do? Also these kinds of properties shouldn't be set manually.
*
* ```
* var prop = ProAct.Property.value(4);
* var even = sourceActor.filter(function (el) {
* return el % 2 == 0;
* });
*
* even.get(); // 4
*
* prop.set(5);
* even.get(); // 4
*
* prop.set(6);
* even.get(); // 6
* ```
*
* or
*
* ```
* var actor = sourceActor.filter('odd');
*
* ```
*
* @for ProAct.Actor
* @instance
* @method filter
* @param {Object} filteringFunction
* The filtering function or object with a call method, should return boolean.
* @return {ProAct.Property}
* A new ProAct.Actor instance with the <i>filtering</i> applied.
*/
filter: function (filteringFunction) {
var prop = P.P.value(this.val, {}, this.queueName).filtering(filteringFunction).into(this);
ActorUtil.update.call(this);
return prop;
},
/**
* Creates a new `ProAct.Property` instance with source <i>this</i> and accumulation
* the passed <i>accumulation function</i>.
*
* Some examples:
*
* ```
* var prop = ProAct.Property.value(3);
* var acc = prop.accumulate(0, function (current, el) {
* return current + el;
* });
*
* acc.get(); // 3
*
* prop.set(5);
*
* acc.get(); // 8
*
* prop.set(2);
*
* acc.get(); // 10
* ```
*
* @for ProAct.Property
* @instance
* @method accumulate
* @param {Object} initVal
* Initial value for the accumulation. For example '0' for sum.
* @param {Object} accumulationFunction
* The function to accumulate.
* @return {ProAct.Property}
* A new `ProAct.Property` instance with the <i>accumulation</i> applied.
*/
accumulate: function (initVal, accumulationFunction) {
var prop = P.P.value(this.val, {}, this.queueName).accumulation(initVal, accumulationFunction).into(this);
ActorUtil.update.call(this);
return prop;
},
/**
* The <b>toString()</b> method returns a string representing this `ProAct.Property`.
* <p>
* The string representation is the value of <i>this</i> property.
* </p>
*
* @for ProAct.Property
* @instance
* @method toString
*/
toString: function () {
return this.val + '';
},
valueOf: function () {
return this.val;
}
});
P.U.ex(P.Actor.prototype, {
/**
* Generates a new {{#crossLink "ProAct.Property"}}{{/crossLink}} containing the state of an accumulations.
*
* <p>
* The value will be updated with every update coming to this actor.
* </p>
*
*
* @for ProAct.Actor
* @instance
* @method reduce
* @param {Object} initVal
* Initial value for the accumulation. For example '0' for sum.
* @param {Object} accumulationFunction
* The function to accumulate.
* @return {ProAct.Property}
* A {{#crossLink "ProAct.Property"}}{{/crossLink}} instance observing <i>this</i> with the accumulation applied.
*/
reduce: function (initVal, accumulationFunction) {
return P.P.value(initVal).into(this.accumulate(initVal, accumulationFunction));
},
/**
* Creates a {{{#crossLink "ProAct.Property"}}{{/crossLink}} instance,
* dependent on this.
* Comes from the `proact-properties` module.
*
* @for ProAct.Actor
* @instance
* @method toProperty
*/
toProperty: function () {
return P.P.value(this.val, {}, this.queueName).into(this);
}
});
function PropertyProbProvider () {
};
PropertyProbProvider.prototype = P.U.ex(Object.create(P.ProbProvider.prototype), {
constructor: PropertyProbProvider,
filter: function (data, meta) {
return data === null || (!P.U.isObject(data) && !P.U.isArray(data));
},
provide: function (data, meta) {
return P.P.lazyValue(data, meta);
}
});
P.ProbProvider.register(new PropertyProbProvider());