objects/property.js

var aeq = (function (aeq) {
/**
 * Converts a Property into an aeq.Property object
 * @memberof aeq
 * @class
 * @param  {Property} property Property to convert
 * @return {aeq.Property}      aeq.Property object
 */
aeq.Property = function (property) {
	if (property instanceof aeq.Property) {
		return property;
	}
	if (this instanceof aeq.Property) {
		this.property = property;
	} else {
		return new aeq.Property(property);
	}
};

aeq.Property.prototype = {
	isAeq: true,

	toString: function() {
		return "[object aeq.Property]";
	},

	// Function for extending the prototype using objects
	extend: aeq.extend,

	/**
	 * Get the original object
	 * @method
	 * @instance
	 * @return {Property}
	 */
	get: function() {
		return this.property;
	},

	/**
	 * Gets or sets expression on property
	 * @method
	 * @instance
	 * @param  {string} [newValue] Expression to set
	 * @return {string|boolean}    Returns current expression, current expression error, or `true` if expression was set
	 */
	expression: function(newValue) {
		if (!this.property.canSetExpression) {
			return false;
		}
		if (arguments.length === 0) {
			return this.property.expression;
		}
		this.property.expression = newValue;
		if (this.property.expressionError === ""
				&& (this.property.expressionEnabled
				|| newValue === "")) {
			return true;
		}
		return this.property.expressionError;
	},

	/**
	 * Gets array of selected keys
	 * @method
	 * @instance
	 * @return {Key[]} Array of keys to return
	 */
	selectedKeys: function() {
		var selectedKeys = [];
		// Return key objects for selected keys
		for (var i = 1; i <= this.property.selectedKeys.length; i++) {
			selectedKeys.push(this.key(i));
		}
		return selectedKeys;
	},

	/**
	 * Adds & returns a new key at time
	 * @method
	 * @instance
	 * @param  {number} time The time in seconds; a floating-point value. The beginning of the composition is 0.
	 * @return {Key}         Newly-created key
	 */
	addKey: function(time) {
		var keyIndex = this.property.addKey(time);
		return this.key(keyIndex);
	},

	/**
	 * Retrieves property following passed dimension
	 * @method
	 * @instance
	 * @param  {number} dim The dimension number (starting at 0).
	 * @return {Property}   Property following passed dimension
	 */
	separationFollower: function(dim) {
		return this.property.getSeparationFollower(dim);
	},

	/**
	 * Returns the index of the keyframe nearest to the specified time.
	 * @method
	 * @instance
	 * @param  {number} time The time in seconds; a floating-point value. The beginning of the composition is 0.
	 * @return {Key}         Nearest key
	 */
	nearestKeyIndex: function(time) {
		var keyIndex = this.property.nearestKeyIndex(time);
		return this.key(keyIndex);
	},

	/**
	 * Removes key by index or key object
	 * @method
	 * @instance
	 * @param  {number|Key} keyIndex Index of target key, or key itself
	 */
	removeKey: function(keyIndex) {
		if (aeq.isNumber(keyIndex)) {
			this.property.removeKey(keyIndex);
		} else if (keyIndex.toString() === "[object aeq.Key]") {
			keyIndex.remove();
		}
	},

	/**
	 * Returns the original multidimensional property for this separated follower
	 * Can only be accessed if the property is one of the separated properties
	 * 	(e.g Y Position), otherwise AE throws an error
	 * @method
	 * @instance
	 * @return {Property|null} Original multidimensional property, or null
	 */
	separationLeader: function() {
		if (this.property.isSeparationFollower) {
			return this.property.separationLeader;
		}
		return null;
	},

	/**
	 * Returns the dimension number it represents in the multidimensional leader
	 * Can only be accessed if the property is one of the separated properties
	 * 	(e.g Y Position), otherwise AE throws an error
	 * @method
	 * @instance
	 * @return {number|null} Dimension number, or null
	 */
	separationDimension: function() {
		if (this.property.isSeparationFollower) {
			return this.property.separationDimension;
		}
		return null;
	},

	/**
	 * Returns maximum permitted value of property
	 * @method
	 * @instance
	 * @return {number|null} Max value, or null if there isn't one
	 */
	maxValue: function() {
		if (this.property.hasMax) {
			return this.property.maxValue;
		}
		return null;
	},

	/**
	 * Returns minimum permitted value of property
	 * @method
	 * @instance
	 * @return {number|null} Max value, or null if there isn't one
	 */
	minValue: function() {
		if (this.property.hasMin) {
			return this.property.minValue;
		}
		return null;
	},

	/**
	 * Gets or sets property value
	 * 	If expressionEnabled is true, returns the evaluated expression value.
	 * 	If there are keyframes, returns the keyframed value at the current time.
	 * 	Otherwise, returns the static value.
	 * @method
	 * @instance
	 * @param  {any} [newValue] New value to try to set
	 * @return {any}            Current value
	 */
	value: function(newValue) {
		if (arguments.length === 0) {
			return this.property.value;
		}
		this.property.setValue(newValue);
	},

	/**
	 * Get or set the value of the current property as evaluated at the specified time
	 * @method
	 * @instance
	 * @param  {number} time    The time in seconds; a floating-point value. The beginning of the composition is 0.
	 * @param  {any}    [value] Property value at time
	 * @return {any|number}     Set value, or index of nearest key to `time`
	 */
	valueAtTime: function(time, value) {
		// TODO: Both setValueAtTime and valueAtTime require two arguments
		// How should this be handled?
		if (arguments.length === 1) {
			return this.property.valueAtTime(time);
		}
		this.property.setValueAtTime(time, value);

		// TODO: should returning key object be optional?
		return this.nearestKeyIndex(time);
	},

	/**
	 * Get or sets values for a set of keyframes at specified times
	 * @method
	 * @instance
	 * @param  {number[]} time    Array of times
	 * @param  {any[]}    [value] Array of values
	 * @return {any[]|number[]}   Array of set values, or array of indices of nearest key to `time`
	 */
	valuesAtTimes: function(times, values) {
		var result = [], i = 0, il = times.length;
		if (arguments.length === 1) {
			for ( ; i < il; i++ ) {
				// TODO: valueAtTime require two arguments How should this be handled?
				result.push(this.property.valueAtTime(times[i]));
			}
			return result;
		}
		this.property.setValuesAtTimes(times, values);

		// TODO: should returning key objects be optional?
		for ( ; i < il; i++ ) {
			result.push(this.nearestKeyIndex(times[i]));
		}
		return result;
	},

	/**
	 * Runs a function on each key in current property
	 * @method
	 * @instance
	 * @param  {Function} callback Function to execute on each key
	 */
	forEachKey: function(callback) {
		var length = this.property.numKeys, i = 1;

		for ( ; i <= length; i++ ) {
			callback(this.key(i), i, this.property);
		}
	},

	/**
	 * Returns a aeq.Key object for specific key index
	 * @method
	 * @instance
	 * @param  {number} keyIndex Index of target key
	 * @return {aeq.Key}         aeq.Key object for target key
	 */
	key: function(keyIndex) {
		return new aeq.Key(this.property, keyIndex);
	}
};

// Create functions for read-only attributes
aeq.forEach([
	"expressionError",
	"isTimeVarying",
	"numKeys",
	"canSetExpression",
	"canVaryOverTime",
	"isSpatial",
	"isSeparationFollower",
	"isSeparationLeader",
	"propertyIndex",
	"propertyValueType",
	"unitsText"
], function(attribute) {
	aeq.Property.prototype[attribute] = function() {
		return this.property[attribute];
	};
});

return aeq;
}(aeq || {}));