diff options
Diffstat (limited to 'node_modules/montage/core/extras/object.js')
-rw-r--r-- | node_modules/montage/core/extras/object.js | 256 |
1 files changed, 256 insertions, 0 deletions
diff --git a/node_modules/montage/core/extras/object.js b/node_modules/montage/core/extras/object.js new file mode 100644 index 00000000..0281240f --- /dev/null +++ b/node_modules/montage/core/extras/object.js | |||
@@ -0,0 +1,256 @@ | |||
1 | |||
2 | var M = require("core/core"); // lazy bound because of dependency cycle | ||
3 | |||
4 | var MODIFY = "modify"; | ||
5 | |||
6 | /** | ||
7 | @function external:Object#getProperty | ||
8 | @param {Object} aPropertyPath | ||
9 | @param {Property} unique | ||
10 | @param {Property} preserve | ||
11 | @param {Function} visitedComponentCallback | ||
12 | @param {Array} currentIndex | ||
13 | @returns result | ||
14 | */ | ||
15 | Object.defineProperty(Object.prototype, "getProperty", { | ||
16 | value: function(aPropertyPath, unique, preserve, visitedComponentCallback, currentIndex) { | ||
17 | var dotIndex, | ||
18 | result, | ||
19 | currentPathComponent, | ||
20 | nextDotIndex, | ||
21 | remainingPath = null; | ||
22 | |||
23 | if (aPropertyPath == null) { | ||
24 | return; | ||
25 | } | ||
26 | |||
27 | dotIndex = aPropertyPath.indexOf(".", currentIndex); | ||
28 | currentIndex = currentIndex || 0; | ||
29 | currentPathComponent = aPropertyPath.substring(currentIndex, (dotIndex === -1 ? aPropertyPath.length : dotIndex)); | ||
30 | |||
31 | if (currentPathComponent in this) { | ||
32 | result = this[currentPathComponent]; | ||
33 | } else { | ||
34 | result = typeof this.undefinedGet === "function" ? this.undefinedGet(currentPathComponent) : undefined; | ||
35 | } | ||
36 | |||
37 | if (visitedComponentCallback) { | ||
38 | nextDotIndex = aPropertyPath.indexOf(".", currentIndex); | ||
39 | if (nextDotIndex != -1) { | ||
40 | remainingPath = aPropertyPath.substr(nextDotIndex+1); | ||
41 | } | ||
42 | visitedComponentCallback(this, currentPathComponent, result, null, remainingPath); | ||
43 | } | ||
44 | |||
45 | if (visitedComponentCallback && result && -1 === dotIndex) { | ||
46 | |||
47 | // We resolved the last object on the propertyPath, be sure to give the visitor a chance to handle this one | ||
48 | //visitedComponentCallback(result, null, null, null, null); | ||
49 | |||
50 | } else if (result && dotIndex !== -1) { | ||
51 | // We resolved that component of the path, but there's more path components; go to the next | ||
52 | |||
53 | if (result.getProperty) { | ||
54 | result = result.getProperty(aPropertyPath, unique, preserve, visitedComponentCallback, dotIndex + 1); | ||
55 | } else { | ||
56 | // TODO track when this happens, right now it's only happening with CanvasPixelArray in WebKit | ||
57 | result = Object.prototype.getProperty.call(result, aPropertyPath, unique, preserve, visitedComponentCallback, dotIndex + 1); | ||
58 | } | ||
59 | } | ||
60 | // Otherwise, we reached the end of the propertyPath, or at least as far as we could; stop | ||
61 | return result; | ||
62 | }, | ||
63 | enumerable: false | ||
64 | }); | ||
65 | |||
66 | /** | ||
67 | @private | ||
68 | */ | ||
69 | Object.defineProperty(Object.prototype, "_propertySetterNamesByName", { | ||
70 | value: {} | ||
71 | }); | ||
72 | |||
73 | /** | ||
74 | @private | ||
75 | */ | ||
76 | Object.defineProperty(Object.prototype, "_propertySetterByName", { | ||
77 | value: {} | ||
78 | }); | ||
79 | |||
80 | /** | ||
81 | Description | ||
82 | @member external:Object#setProperty | ||
83 | @function | ||
84 | @param {Object} aPropertyPath | ||
85 | @param {Object} value | ||
86 | @returns itself | ||
87 | */ | ||
88 | Object.defineProperty(Object.prototype, "setProperty", { | ||
89 | value: function(aPropertyPath, value) { | ||
90 | var propertyIsNumber = !isNaN(aPropertyPath), | ||
91 | lastDotIndex = propertyIsNumber ? -1 : aPropertyPath.lastIndexOf("."), | ||
92 | setObject, | ||
93 | lastObjectAtPath, | ||
94 | propertyToSetOnArray; | ||
95 | |||
96 | if (lastDotIndex !== -1) { | ||
97 | //The propertyPath describes a property that is deeper inside this object | ||
98 | setObject = this.getProperty(aPropertyPath.substring(0, lastDotIndex)); | ||
99 | |||
100 | if (!setObject) { | ||
101 | this.undefinedSet(aPropertyPath); | ||
102 | return; | ||
103 | } | ||
104 | |||
105 | aPropertyPath = aPropertyPath.substring(lastDotIndex + 1); | ||
106 | } else { | ||
107 | // The property path describes a property on this object | ||
108 | setObject = this; | ||
109 | } | ||
110 | |||
111 | lastObjectAtPath = setObject.getProperty(aPropertyPath); | ||
112 | |||
113 | // TODO clean up some of the duplicated code here | ||
114 | |||
115 | if (lastObjectAtPath && Array.isArray(lastObjectAtPath)) { | ||
116 | if (lastObjectAtPath !== value) { | ||
117 | // if the value does not match the object described by this propertyPath; set it as the new value | ||
118 | |||
119 | if (Array.isArray(setObject)) { | ||
120 | // If the setObject is an array itself; splice (typically called by set) to trigger bindings, do it here to save time | ||
121 | propertyToSetOnArray = parseInt(aPropertyPath, 10); | ||
122 | if (!isNaN(propertyToSetOnArray)) { | ||
123 | if (setObject.length < propertyToSetOnArray) { | ||
124 | // TODO while I could set the value here I'm setting null and letting the splice, | ||
125 | // which we need to do anyway to trigger bindings, do the actual setting | ||
126 | setObject[propertyToSetOnArray] = null; | ||
127 | } | ||
128 | |||
129 | setObject.splice(propertyToSetOnArray, 1, value); | ||
130 | |||
131 | } else { | ||
132 | setObject[aPropertyPath] = value; | ||
133 | } | ||
134 | |||
135 | } else { | ||
136 | setObject[aPropertyPath] = value; | ||
137 | } | ||
138 | |||
139 | } else { | ||
140 | // Otherwise, they are the same object, a mutation event probably happened | ||
141 | |||
142 | // If the object at the property we're "setting" is itself an array, see if there was an event passed along | ||
143 | // as part of a change and whether we need to call the setObject's changeProperty method | ||
144 | var changeEvent = this.setProperty.changeEvent, modify; | ||
145 | |||
146 | // For these mutation/addition/removal events, use the 'modify' attribute of this property's descriptor | ||
147 | if (changeEvent && (changeEvent.currentTarget.getProperty(changeEvent.currentPropertyPath) === lastObjectAtPath) && | ||
148 | (modify = M.Montage.getPropertyAttribute(setObject, aPropertyPath, MODIFY))) { | ||
149 | modify.call(setObject, changeEvent.type, changeEvent.newValue, changeEvent.prevValue); | ||
150 | } | ||
151 | } | ||
152 | } else if (Array.isArray(setObject)) { | ||
153 | // If the setObject is an array itself; splice (typically called by set) to trigger bindings, do it here to save time | ||
154 | propertyToSetOnArray = parseInt(aPropertyPath, 10); | ||
155 | if (!isNaN(propertyToSetOnArray)) { | ||
156 | if (setObject.length < propertyToSetOnArray) { | ||
157 | // TODO while I could set the value here I'm setting null and letting the splice, | ||
158 | // which we need to do anyway to trigger bindings, do the actual setting | ||
159 | setObject[propertyToSetOnArray] = null; | ||
160 | } | ||
161 | } | ||
162 | setObject.splice(propertyToSetOnArray, 1, value); | ||
163 | } else { | ||
164 | setObject[aPropertyPath] = value; | ||
165 | } | ||
166 | }, | ||
167 | enumerable: false | ||
168 | }); | ||
169 | |||
170 | /** | ||
171 | @member external:Object#parentProperty | ||
172 | @default null | ||
173 | */ | ||
174 | Object.defineProperty(Object.prototype, "parentProperty", { | ||
175 | enumerable: false, | ||
176 | value: null, | ||
177 | writable: true | ||
178 | }); | ||
179 | |||
180 | /** | ||
181 | @function module:montage/core/core.Montage#undefinedGet | ||
182 | @param {Object} aPropertyName The object property name. | ||
183 | */ | ||
184 | Object.defineProperty(Object.prototype, "undefinedGet", { | ||
185 | value: function(aPropertyName) { | ||
186 | console.warn("get undefined property -" + aPropertyName + "-"); | ||
187 | }, | ||
188 | writable: true | ||
189 | }); | ||
190 | |||
191 | /** | ||
192 | @function module:montage/core/core.Montage#undefinedSet | ||
193 | @param {Object} aPropertyName The object property name. | ||
194 | */ | ||
195 | Object.defineProperty(Object.prototype, "undefinedSet", { | ||
196 | value: function(aPropertyName) { | ||
197 | console.warn("set undefined property -" + aPropertyName + "-"); | ||
198 | }, | ||
199 | writable: true | ||
200 | }); | ||
201 | |||
202 | /** | ||
203 | Returns the descriptor object for an object's property. | ||
204 | @function external:Object#getPropertyDescriptor | ||
205 | @param {Object} anObject The object containing the property. | ||
206 | @param {String} propertyName The name of the property. | ||
207 | @returns {Object} The object's property descriptor. | ||
208 | */ | ||
209 | Object.getPropertyDescriptor = function(anObject, propertyName) { | ||
210 | var current = anObject, | ||
211 | currentDescriptor; | ||
212 | |||
213 | do { | ||
214 | currentDescriptor = Object.getOwnPropertyDescriptor(current, propertyName); | ||
215 | } while (!currentDescriptor && (current = current.__proto__ || Object.getPrototypeOf(current))); | ||
216 | |||
217 | return currentDescriptor; | ||
218 | }; | ||
219 | |||
220 | /** | ||
221 | Returns the prototype object and property descriptor for a property belonging to an object. | ||
222 | @function external:Object#getPrototypeAndDescriptorDefiningProperty | ||
223 | @param {Object} anObject The object to return the prototype for. | ||
224 | @param {String} propertyName The name of the property. | ||
225 | @returns {Object} An object containing two properties named <code>prototype</code> and <code>propertyDescriptor</code> |