/*
No rights, expressed or implied, whatsoever to this software are provided by Motorola Mobility, Inc. hereunder.
(c) Copyright 2011 Motorola Mobility, Inc. All Rights Reserved.
change@independentProperty
event.
@function module:montage/core/core.Montage.addDependencyToProperty
@param {Object} obj The object containing the dependent and independent properties.
@param {String} independentProperty The name of the object's independent property.
@param {String} dependentProperty The name of the object's dependent property.
*/
M.defineProperty(M, "addDependencyToProperty", { value: function(obj, independentProperty, dependentProperty) {
// TODO optimize this so we don't keep checking over and over again
if (!obj._dependenciesForProperty) {
obj._dependenciesForProperty = {};
}
if (!obj._dependenciesForProperty[dependentProperty]) {
obj._dependenciesForProperty[dependentProperty] = [];
}
obj._dependenciesForProperty[dependentProperty].push(independentProperty);
}});
/**
Removes a dependent property from another property's collection of dependent properties.
When the value of a dependent property changes, it generates a change@independentProperty
event.
@function module:montage/core/core.Montage.removeDependencyFromProperty
@param {Object} obj The object containing the dependent and independent properties.
@param {String} independentProperty The name of the object's independent property.
@param {String} dependentProperty The name of the object's dependent property that you want to remove.
*/
M.defineProperty(M, "removeDependencyFromProperty", {value: function(obj, independentProperty, dependentProperty) {
if (!obj._dependenciesForProperty) {
return;
}
var dependencies = obj._dependenciesForProperty[dependentProperty];
if (dependencies) {
dependencies = dependencies.filter(function(element) {
return (element !== independentProperty);
});
}
}});
function getAttributeProperties(proto, attributeName) {
var attributePropertyName = "_" + attributeName + "AttributeProperties";
if (proto.hasOwnProperty(attributePropertyName)) {
return proto[attributePropertyName];
} else {
return Object.defineProperty(proto, attributePropertyName, {
enumerable: false, configurable: false, writable: false,
value: Object.create(getAttributeProperties(Object.getPrototypeOf(proto), attributeName))
})[attributePropertyName];
}
}
/**
Returns the descriptor object for an object's property.
@function external:Object#getPropertyDescriptor
@param {Object} anObject The object containing the property.
@param {String} propertyName The name of the property.
@returns {Object} The object's property descriptor.
*/
Object.getPropertyDescriptor = function(anObject, propertyName) {
var current = anObject,
currentDescriptor;
do {
currentDescriptor = Object.getOwnPropertyDescriptor(current, propertyName);
} while (!currentDescriptor && (current = current.__proto__ || Object.getPrototypeOf(current)));
return currentDescriptor;
};
/**
Returns the prototype object and property descriptor for a property belonging to an object.
@function external:Object#getPrototypeAndDescriptorDefiningProperty
@param {Object} anObject The object to return the prototype for.
@param {String} propertyName The name of the property.
@returns {Object} An object containing two properties named prototype
and propertyDescriptor
that contain the object's prototype object and property descriptor, respectively.
*/
Object.getPrototypeAndDescriptorDefiningProperty = function(anObject, propertyName) {
var current = anObject,
currentDescriptor;
if (propertyName) {
do {
currentDescriptor = Object.getOwnPropertyDescriptor(current, propertyName);
} while (!currentDescriptor && (current = current.__proto__ || Object.getPrototypeOf(current)));
return {
prototype: current,
propertyDescriptor: currentDescriptor
};
}
};
/**
Returns the names of serializable properties belonging to Montage object.
@function module:montage/core/core.Montage.getSerializablePropertyNames
@param {Object} anObject A Montage object.
@returns {Array} An array containing the names of the serializable properties belonging to anObject
.
*/
M.defineProperty(M, "getSerializablePropertyNames", {value: function(anObject) {
var propertyNames = [],
attributes = anObject._serializableAttributeProperties;
if (attributes) {
for (var name in attributes) {
if (attributes[name]) {
propertyNames.push(name);
}
}
}
return propertyNames;
}});
/**
Returns the attribute of a property belonging to an object.
@function module:montage/core/core.Montage.getPropertyAttribute
@param {Object} anObject A object.
@param {String} propertyName The name of a property belonging to anObject
.
@param {String} attributeName The name of a property's attribute.
@returns attributes array
*/
M.defineProperty(M, "getPropertyAttribute", {value: function(anObject, propertyName, attributeName) {
var attributePropertyName = "_" + attributeName + "AttributeProperties",
attributes = anObject[attributePropertyName];
if (attributes) {
return attributes[propertyName];
}
}});
/**
@function module:montage/core/core.Montage.getPropertyAttributes
@param {Object} anObject An object.
@param {String} attributeName The attribute name.
@returns {Object} TODO getPropertyAttributes returns description
*/
M.defineProperty(M, "getPropertyAttributes", {value: function(anObject, attributeName) {
var attributeValues = {},
attributePropertyName = "_" + attributeName + "AttributeProperties",
attributes = anObject[attributePropertyName];
if (!attributes) {
return;
}
for (var name in attributes) {
attributeValues[name] = attributes[name];
}
return attributeValues;
}});
var _instanceMetadataDescriptor = {
isInstance: {value: true}
};
var _functionInstanceMetadataDescriptor = {
objectName: {value: "Function"},
isInstance: {value: true}
};
/**
@function module:montage/core/core.Montage.getInfoForObject
@param {Object} object An object.
@returns {Object} object._montage_metadata
*/
M.defineProperty(M, "getInfoForObject", {
value: function(object) {
var metadata;
var instanceMetadataDescriptor;
if (object.hasOwnProperty("_montage_metadata")) {
return object._montage_metadata;
} else {
metadata = object._montage_metadata || (object.constructor && object.constructor._montage_metadata) || null;
if (object.constructor === Function) {
instanceMetadataDescriptor = _functionInstanceMetadataDescriptor;
} else {
instanceMetadataDescriptor = _instanceMetadataDescriptor;
}
try {
return Object.defineProperty(object, "_montage_metadata", {
enumerable: false,
// this object needs to be overriden by the SerializationCompiler because this particular code might be executed on an exported object before the Compiler takes action, for instance, if this function is called within the module definition itself (happens with __core__).
writable: true,
value: Object.create(metadata, instanceMetadataDescriptor)
})._montage_metadata;
} catch(e) {
// NOTE Safari (as of Version 5.0.2 (6533.18.5, r78685)
// doesn't seem to allow redefining an existing property on a DOM Element
return (object._montage_metadata = Object.create(metadata, instanceMetadataDescriptor));
}
}
}
});
/**
@function module:montage/core/core.Montage.doNothing
@default function
*/
Object.defineProperty(M, "doNothing", {
value: function() {
}
});
/**
@function module:montage/core/core.Montage#self
@default function
@returns itself
*/
Object.defineProperty(M, "self", {
value: function() {
return this;
}
});
/**
@private
*/
Object.defineProperty(M, "__OBJECT_COUNT", {
value: 0,
writable: true
});
var Uuid = require("core/uuid").Uuid;
/**
Generates and returns a unique identifier.
@function module:montage/core/core.Montage.generateUID
@returns {String} A unique ID.
@example
true
if the calling object and anObject
are identical and their uuid
properties are also equal. Otherwise, returns false
.
*/
Object.defineProperty(M, "equals", {
value: function(anObject) {
return this === anObject || this.uuid === anObject.uuid;
}
});
if (!Object.isSealed) {
Object.defineProperty(Object, "isSealed", {
value: function() {
return false;
}
});
}
if (!Object.seal) {
Object.defineProperty(Object, "seal", {
value: function(object) {
return object;
}
});
}
/*
* If it exists this method calls the method named with the identifier prefix.
* Example: If the name parameter is "shouldDoSomething" and the caller's identifier is "bob", then
* this method will try and call "bobShouldDoSomething"
*/
/**
* @function module:montage/core/core.Montage#callDelegateMethod
* @param {string} name
*/
Object.defineProperty(M, "callDelegateMethod", {
value: function(name) {
var delegate, delegateFunctionName, delegateFunction;
if (typeof this.identifier === "string") {
delegateFunctionName = this.identifier + name.toCapitalized();
} else {
delegateFunctionName = name;
}
if ((delegate = this.delegate) && typeof (delegateFunction = delegate[delegateFunctionName]) === "function") {
// remove first argument
Array.prototype.shift.call(arguments);
return delegateFunction.apply(delegate, arguments);
}
}
});
/**
@function external:Object#getProperty
@param {Object} aPropertyPath
@param {Property} unique
@param {Property} preserve
@param {Function} visitedComponentCallback
@param {Array} currentIndex
@returns result
*/
Object.defineProperty(Object.prototype, "getProperty", {
value: function(aPropertyPath, unique, preserve, visitedComponentCallback, currentIndex) {
var dotIndex,
result,
currentPathComponent;
dotIndex = aPropertyPath.indexOf(".", currentIndex);
currentIndex = currentIndex || 0;
currentPathComponent = aPropertyPath.substring(currentIndex, (dotIndex === -1 ? aPropertyPath.length : dotIndex));
if (currentPathComponent in this) {
result = this[currentPathComponent];
} else {
result = typeof this.undefinedGet === "function" ? this.undefinedGet(currentPathComponent) : undefined;
}
if (visitedComponentCallback) {
visitedComponentCallback(this, currentPathComponent, result);
}
if (visitedComponentCallback && result && -1 === dotIndex) {
// We resolved the last object on the propertyPath, be sure to give the visitor a chance to handle this one
visitedComponentCallback(result);
} else if (result && dotIndex !== -1) {
// We resolved that component of the path, but there's more path components; go to the next
if (result.getProperty) {
result = result.getProperty(aPropertyPath, unique, preserve, visitedComponentCallback, dotIndex + 1);
} else {
// TODO track when this happens, right now it's only happening with CanvasPixelArray in WebKit
result = Object.prototype.getProperty.call(result, aPropertyPath, unique, preserve, visitedComponentCallback, dotIndex + 1);
}
}
// Otherwise, we reached the end of the propertyPath, or at least as far as we could; stop
return result;
},
enumerable: false
});
/**
@private
*/
Object.defineProperty(M, "_propertySetterNamesByName", {
value: {}
});
/**
@private
*/
Object.defineProperty(M, "_propertySetterByName", {
value: {}
});
/**
Description
@member external:Object#setProperty
@function
@param {Object} aPropertyPath
@param {Object} value
@returns itself
*/
Object.defineProperty(Object.prototype, "setProperty", {
value: function(aPropertyPath, value) {
var propertyIsNumber = !isNaN(aPropertyPath),
lastDotIndex = propertyIsNumber ? -1 : aPropertyPath.lastIndexOf("."),
setObject,
lastObjectAtPath,
propertyToSetOnArray;
if (lastDotIndex !== -1) {
//The propertyPath describes a property that is deeper inside this object
setObject = this.getProperty(aPropertyPath.substring(0, lastDotIndex));
if (!setObject) {
this.undefinedSet(aPropertyPath);
return;
}
aPropertyPath = aPropertyPath.substring(lastDotIndex + 1);
} else {
// The property path describes a property on this object
setObject = this;
}
lastObjectAtPath = setObject.getProperty(aPropertyPath);
// TODO clean up some of the duplicated code here
if (lastObjectAtPath && Array.isArray(lastObjectAtPath)) {
if (lastObjectAtPath !== value) {
// if the value does not match the object described by this propertyPath; set it as the new value
if (Array.isArray(setObject)) {
// If the setObject is an array itself; splice (typically called by set) to trigger bindings, do it here to save time
propertyToSetOnArray = parseInt(aPropertyPath, 10);
if (!isNaN(propertyToSetOnArray)) {
if (setObject.length < propertyToSetOnArray) {
// TODO while I could set the value here I'm setting null and letting the splice,
// which we need to do anyway to trigger bindings, do the actual setting
setObject[propertyToSetOnArray] = null;
}
setObject.splice(propertyToSetOnArray, 1, value);
} else {
setObject[aPropertyPath] = value;
}
} else {
setObject[aPropertyPath] = value;
}
} else {
// Otherwise, they are the same object, a mutation event probably happened
// If the object at the property we're "setting" is itself an array, see if there was an event passed along
// as part of a change and whether we need to call the setObject's changeProperty method
var changeEvent = this.setProperty.changeEvent, modify;
// For these mutation/addition/removal events, use the 'modify' attribute of this property's descriptor
if (changeEvent && (changeEvent.currentTarget === lastObjectAtPath) &&
(modify = M.getPropertyAttribute(setObject, aPropertyPath, "modify"))) {
modify.call(setObject, changeEvent.type, changeEvent.newValue, changeEvent.prevValue);
}
}
} else if (Array.isArray(setObject)) {
// If the setObject is an array itself; splice (typically called by set) to trigger bindings, do it here to save time
propertyToSetOnArray = parseInt(aPropertyPath, 10);
if (!isNaN(propertyToSetOnArray)) {
if (setObject.length < propertyToSetOnArray) {
// TODO while I could set the value here I'm setting null and letting the splice,
// which we need to do anyway to trigger bindings, do the actual setting
setObject[propertyToSetOnArray] = null;
}
}
setObject.splice(propertyToSetOnArray, 1, value);
} else {
setObject[aPropertyPath] = value;
}
},
enumerable: false
});
/**
Description
@member external:Array#getProperty
@function
@param {Object} aPropertyPath
@param {Property} unique
@param {Property} preserve
@param {Function} visitedComponentCallback
@param {Array} currentIndex
*/
Object.defineProperty(Array.prototype, "getProperty", {
value: function(aPropertyPath, unique, preserve, visitedComponentCallback, currentIndex) {
currentIndex = currentIndex || 0;
var result,
propertyIsNumber = !isNaN(aPropertyPath),
parenthesisStartIndex = propertyIsNumber ? -1 : aPropertyPath.indexOf("(", currentIndex),
parenthesisEndIndex = propertyIsNumber ? -1 : aPropertyPath.lastIndexOf(")"),
currentPathComponentEndIndex = propertyIsNumber ? -1 : aPropertyPath.indexOf(".", currentIndex),
nextDelimiterIndex = -1,
itemResult,
index,
currentPathComponent,
functionName,
functionArgPropertyPath;
// PARSE: Determine the indices of the currentPathComponent we're concerned with
if (currentPathComponentEndIndex > -1 && parenthesisStartIndex > -1) {
// We have both a dot and an open parenthesis somewhere in the path, figure out the next path component
if (currentPathComponentEndIndex > parenthesisStartIndex) {
// The next dot was actually inside the function call; use the entire function call: foo.bar(a.b.c) -> bar(a.b.c)
nextDelimiterIndex = parenthesisEndIndex + 1;
functionName = aPropertyPath.substring(currentIndex, parenthesisStartIndex);
} else {
// The next dot comes before the start of the function parenthesis; use the dot: foo.bar(a.b.c) -> foo
nextDelimiterIndex = currentPathComponentEndIndex;
}
} else {
// We have either a dot, parenthesis, or neither
if (parenthesisStartIndex > -1) {
// we had a starting parenthesis; use the END parenthesis to include the entire function call
nextDelimiterIndex = parenthesisEndIndex + 1;
functionName = aPropertyPath.substring(currentIndex, parenthesisStartIndex);
} else {
nextDelimiterIndex = currentPathComponentEndIndex;
}
}
// Find the component of the propertyPath we want to deal with during this particular invocation of this function
currentPathComponent = propertyIsNumber ? aPropertyPath : aPropertyPath.substring(currentIndex, (nextDelimiterIndex === -1 ? aPropertyPath.length : nextDelimiterIndex));
// EVALUATE: Determine the value of the currentPathComponent
if (functionName) {
// We have a function to execute as part of this propertyPath; execute it assuming that it will know
// how to handle the property path being passed along
// TODO do we call this before or after finding the result (probably before to maintain the chain
// of one invocation's discovered value being the context of the next invocation
if (visitedComponentCallback) {
visitedComponentCallback(this, functionName + "()");
}
functionArgPropertyPath = aPropertyPath.substring(parenthesisStartIndex + 1, parenthesisEndIndex);
result = this[functionName](functionArgPropertyPath, visitedComponentCallback);
} else {
// TODO we don't provide any way to access properties that are actually accessible on the array itself
// we assume that by default, any property in a propertyPath after an array refers to a property
// you are interested in getting on each member of the array
if (isNaN(currentPathComponent)) {
if (visitedComponentCallback) {
visitedComponentCallback(this, null, this[currentPathComponent]);
}
// The currentPathComponent is some property not directly on this array, and not an index in the array
// so we'll be getting an array of resolving this currentPathComponent on each member in the array
if (!preserve) {
result = this.map(function(value, index) {
itemResult = value.getProperty(aPropertyPath, unique, preserve, null, currentIndex);
if (visitedComponentCallback) {
visitedComponentCallback(value, currentPathComponent, itemResult, index);
}
return itemResult;
});
} else {
result = new Array(this.length);
index = 0;
while((itemResult = this[index])) {
result[index] = itemResult.getProperty(aPropertyPath, unique, preserve, null, currentIndex);
if (visitedComponentCallback) {
visitedComponentCallback(itemResult, currentPathComponent, result[index], index);
}
index++;
}
}
} else {
// The currentPathComponent is an index into this array
result = this[currentPathComponent];
if (visitedComponentCallback) {
visitedComponentCallback(this, currentPathComponent, result);
}
if (currentPathComponentEndIndex > 0) {
result = result ? result.getProperty(aPropertyPath, unique, preserve, visitedComponentCallback, currentPathComponentEndIndex + 1) : undefined;
} else if (visitedComponentCallback && currentPathComponentEndIndex === -1 && result) {
// If we're at the end of the path, but have a result, visit it
visitedComponentCallback(result);
}
}
}
return result;
},
enumerable: false
});
/**
Description
@function external:Array#sum
@param {Object} propertyPath
@param {Function} visitedCallback
@returns sum
*/
Object.defineProperty(Array.prototype, "sum", {
value: function(propertyPath, visitedCallBack) {
var sum = 0, result, resultSum;
if (propertyPath) {
this.map(function(value) {
result = value.getProperty(propertyPath, null, null, visitedCallBack);
if (Array.isArray(result)) {
resultSum = 0;
result.map(function(value) {
return (resultSum += Number(value));
});
result = resultSum;
}
return (sum += Number(result));
});
}
else {
this.map(function(value) {
return (sum += Number(value));
});
}
return sum;
}
});
/**
Description
@member external:Array#any
@function
@param {Object} propertyPath
@param {Function} visitedCallback
@returns result
*/
Object.defineProperty(Array.prototype, "any", {
value: function(propertyPath, visitedCallback) {
var result;
if (propertyPath) {
result = this.some(function(value) {
return !!value.getProperty(propertyPath, null, null, visitedCallback);
});
} else {
result = this.some(function(value) {
return !!value;
});
}
return result;
}
});
/**
@member external:Array#count
@function
@returns this.length
*/
Object.defineProperty(Array.prototype, "count", {
value: function() {
return this.length;
}
});
/**
@function module:montage/core/core.Montage#undefinedGet
@param {Object} aPropertyName The object property name.
*/
Object.defineProperty(M, "undefinedGet", {
value: function(aPropertyName) {
console.warn("get undefined property -" + aPropertyName + "-");
},
writable: true
});
/**
@member external:Array#undefinedGet
*/
Object.defineProperty(Array.prototype, "undefinedGet", {
value: M.undefinedGet,
writable: true
});
/**
@function module:montage/core/core.Montage#undefinedSet
@param {Object} aPropertyName The object property name.
*/
Object.defineProperty(M, "undefinedSet", {
value: function(aPropertyName) {
console.warn("set undefined property -" + aPropertyName + "-");
},
writable: true
});
/**
@member external:Array#undefinedSet
*/
Object.defineProperty(Array.prototype, "undefinedSet", {
value: M.undefinedSet,
writable: true
});
/**
@member external:Object#parentProperty
@default null
*/
Object.defineProperty(Object.prototype, "parentProperty", {
enumerable: false,
value: null,
writable: true
});
// XXX Does not presently function server-side
if (typeof window !== "undefined") {
var EventManager = require("core/event/event-manager").EventManager;
EventManager.create().initWithWindow(window);
// Now that we have a defaultEventManager we can setup the bindings system
require("core/event/binding");
}