|
1 YUI.add('oop', function (Y, NAME) { |
|
2 |
|
3 /** |
|
4 Adds object inheritance and manipulation utilities to the YUI instance. This |
|
5 module is required by most YUI components. |
|
6 |
|
7 @module oop |
|
8 **/ |
|
9 |
|
10 var L = Y.Lang, |
|
11 A = Y.Array, |
|
12 OP = Object.prototype, |
|
13 CLONE_MARKER = '_~yuim~_', |
|
14 |
|
15 hasOwn = OP.hasOwnProperty, |
|
16 toString = OP.toString; |
|
17 |
|
18 /** |
|
19 Calls the specified _action_ method on _o_ if it exists. Otherwise, if _o_ is an |
|
20 array, calls the _action_ method on `Y.Array`, or if _o_ is an object, calls the |
|
21 _action_ method on `Y.Object`. |
|
22 |
|
23 If _o_ is an array-like object, it will be coerced to an array. |
|
24 |
|
25 This is intended to be used with array/object iteration methods that share |
|
26 signatures, such as `each()`, `some()`, etc. |
|
27 |
|
28 @method dispatch |
|
29 @param {Object} o Array or object to dispatch to. |
|
30 @param {Function} f Iteration callback. |
|
31 @param {Mixed} f.value Value being iterated. |
|
32 @param {Mixed} f.key Current object key or array index. |
|
33 @param {Mixed} f.object Object or array being iterated. |
|
34 @param {Object} c `this` object to bind the iteration callback to. |
|
35 @param {Boolean} proto If `true`, prototype properties of objects will be |
|
36 iterated. |
|
37 @param {String} action Function name to be dispatched on _o_. For example: |
|
38 'some', 'each', etc. |
|
39 @private |
|
40 @return {Mixed} Returns the value returned by the chosen iteration action, which |
|
41 varies. |
|
42 **/ |
|
43 function dispatch(o, f, c, proto, action) { |
|
44 if (o && o[action] && o !== Y) { |
|
45 return o[action].call(o, f, c); |
|
46 } else { |
|
47 switch (A.test(o)) { |
|
48 case 1: |
|
49 return A[action](o, f, c); |
|
50 case 2: |
|
51 return A[action](Y.Array(o, 0, true), f, c); |
|
52 default: |
|
53 return Y.Object[action](o, f, c, proto); |
|
54 } |
|
55 } |
|
56 } |
|
57 |
|
58 /** |
|
59 Augments the _receiver_ with prototype properties from the _supplier_. The |
|
60 receiver may be a constructor function or an object. The supplier must be a |
|
61 constructor function. |
|
62 |
|
63 If the _receiver_ is an object, then the _supplier_ constructor will be called |
|
64 immediately after _receiver_ is augmented, with _receiver_ as the `this` object. |
|
65 |
|
66 If the _receiver_ is a constructor function, then all prototype methods of |
|
67 _supplier_ that are copied to _receiver_ will be sequestered, and the |
|
68 _supplier_ constructor will not be called immediately. The first time any |
|
69 sequestered method is called on the _receiver_'s prototype, all sequestered |
|
70 methods will be immediately copied to the _receiver_'s prototype, the |
|
71 _supplier_'s constructor will be executed, and finally the newly unsequestered |
|
72 method that was called will be executed. |
|
73 |
|
74 This sequestering logic sounds like a bunch of complicated voodoo, but it makes |
|
75 it cheap to perform frequent augmentation by ensuring that suppliers' |
|
76 constructors are only called if a supplied method is actually used. If none of |
|
77 the supplied methods is ever used, then there's no need to take the performance |
|
78 hit of calling the _supplier_'s constructor. |
|
79 |
|
80 @method augment |
|
81 @param {Function|Object} receiver Object or function to be augmented. |
|
82 @param {Function} supplier Function that supplies the prototype properties with |
|
83 which to augment the _receiver_. |
|
84 @param {Boolean} [overwrite=false] If `true`, properties already on the receiver |
|
85 will be overwritten if found on the supplier's prototype. |
|
86 @param {String[]} [whitelist] An array of property names. If specified, |
|
87 only the whitelisted prototype properties will be applied to the receiver, and |
|
88 all others will be ignored. |
|
89 @param {Array|any} [args] Argument or array of arguments to pass to the |
|
90 supplier's constructor when initializing. |
|
91 @return {Function} Augmented object. |
|
92 @for YUI |
|
93 **/ |
|
94 Y.augment = function (receiver, supplier, overwrite, whitelist, args) { |
|
95 var rProto = receiver.prototype, |
|
96 sequester = rProto && supplier, |
|
97 sProto = supplier.prototype, |
|
98 to = rProto || receiver, |
|
99 |
|
100 copy, |
|
101 newPrototype, |
|
102 replacements, |
|
103 sequestered, |
|
104 unsequester; |
|
105 |
|
106 args = args ? Y.Array(args) : []; |
|
107 |
|
108 if (sequester) { |
|
109 newPrototype = {}; |
|
110 replacements = {}; |
|
111 sequestered = {}; |
|
112 |
|
113 copy = function (value, key) { |
|
114 if (overwrite || !(key in rProto)) { |
|
115 if (toString.call(value) === '[object Function]') { |
|
116 sequestered[key] = value; |
|
117 |
|
118 newPrototype[key] = replacements[key] = function () { |
|
119 return unsequester(this, value, arguments); |
|
120 }; |
|
121 } else { |
|
122 newPrototype[key] = value; |
|
123 } |
|
124 } |
|
125 }; |
|
126 |
|
127 unsequester = function (instance, fn, fnArgs) { |
|
128 // Unsequester all sequestered functions. |
|
129 for (var key in sequestered) { |
|
130 if (hasOwn.call(sequestered, key) |
|
131 && instance[key] === replacements[key]) { |
|
132 |
|
133 instance[key] = sequestered[key]; |
|
134 } |
|
135 } |
|
136 |
|
137 // Execute the supplier constructor. |
|
138 supplier.apply(instance, args); |
|
139 |
|
140 // Finally, execute the original sequestered function. |
|
141 return fn.apply(instance, fnArgs); |
|
142 }; |
|
143 |
|
144 if (whitelist) { |
|
145 Y.Array.each(whitelist, function (name) { |
|
146 if (name in sProto) { |
|
147 copy(sProto[name], name); |
|
148 } |
|
149 }); |
|
150 } else { |
|
151 Y.Object.each(sProto, copy, null, true); |
|
152 } |
|
153 } |
|
154 |
|
155 Y.mix(to, newPrototype || sProto, overwrite, whitelist); |
|
156 |
|
157 if (!sequester) { |
|
158 supplier.apply(to, args); |
|
159 } |
|
160 |
|
161 return receiver; |
|
162 }; |
|
163 |
|
164 /** |
|
165 * Copies object properties from the supplier to the receiver. If the target has |
|
166 * the property, and the property is an object, the target object will be |
|
167 * augmented with the supplier's value. |
|
168 * |
|
169 * @method aggregate |
|
170 * @param {Object} receiver Object to receive the augmentation. |
|
171 * @param {Object} supplier Object that supplies the properties with which to |
|
172 * augment the receiver. |
|
173 * @param {Boolean} [overwrite=false] If `true`, properties already on the receiver |
|
174 * will be overwritten if found on the supplier. |
|
175 * @param {String[]} [whitelist] Whitelist. If supplied, only properties in this |
|
176 * list will be applied to the receiver. |
|
177 * @return {Object} Augmented object. |
|
178 */ |
|
179 Y.aggregate = function(r, s, ov, wl) { |
|
180 return Y.mix(r, s, ov, wl, 0, true); |
|
181 }; |
|
182 |
|
183 /** |
|
184 * Utility to set up the prototype, constructor and superclass properties to |
|
185 * support an inheritance strategy that can chain constructors and methods. |
|
186 * Static members will not be inherited. |
|
187 * |
|
188 * @method extend |
|
189 * @param {function} r the object to modify. |
|
190 * @param {function} s the object to inherit. |
|
191 * @param {object} px prototype properties to add/override. |
|
192 * @param {object} sx static properties to add/override. |
|
193 * @return {object} the extended object. |
|
194 */ |
|
195 Y.extend = function(r, s, px, sx) { |
|
196 if (!s || !r) { |
|
197 Y.error('extend failed, verify dependencies'); |
|
198 } |
|
199 |
|
200 var sp = s.prototype, rp = Y.Object(sp); |
|
201 r.prototype = rp; |
|
202 |
|
203 rp.constructor = r; |
|
204 r.superclass = sp; |
|
205 |
|
206 // assign constructor property |
|
207 if (s != Object && sp.constructor == OP.constructor) { |
|
208 sp.constructor = s; |
|
209 } |
|
210 |
|
211 // add prototype overrides |
|
212 if (px) { |
|
213 Y.mix(rp, px, true); |
|
214 } |
|
215 |
|
216 // add object overrides |
|
217 if (sx) { |
|
218 Y.mix(r, sx, true); |
|
219 } |
|
220 |
|
221 return r; |
|
222 }; |
|
223 |
|
224 /** |
|
225 * Executes the supplied function for each item in |
|
226 * a collection. Supports arrays, objects, and |
|
227 * NodeLists |
|
228 * @method each |
|
229 * @param {object} o the object to iterate. |
|
230 * @param {function} f the function to execute. This function |
|
231 * receives the value, key, and object as parameters. |
|
232 * @param {object} c the execution context for the function. |
|
233 * @param {boolean} proto if true, prototype properties are |
|
234 * iterated on objects. |
|
235 * @return {YUI} the YUI instance. |
|
236 */ |
|
237 Y.each = function(o, f, c, proto) { |
|
238 return dispatch(o, f, c, proto, 'each'); |
|
239 }; |
|
240 |
|
241 /** |
|
242 * Executes the supplied function for each item in |
|
243 * a collection. The operation stops if the function |
|
244 * returns true. Supports arrays, objects, and |
|
245 * NodeLists. |
|
246 * @method some |
|
247 * @param {object} o the object to iterate. |
|
248 * @param {function} f the function to execute. This function |
|
249 * receives the value, key, and object as parameters. |
|
250 * @param {object} c the execution context for the function. |
|
251 * @param {boolean} proto if true, prototype properties are |
|
252 * iterated on objects. |
|
253 * @return {boolean} true if the function ever returns true, |
|
254 * false otherwise. |
|
255 */ |
|
256 Y.some = function(o, f, c, proto) { |
|
257 return dispatch(o, f, c, proto, 'some'); |
|
258 }; |
|
259 |
|
260 /** |
|
261 Deep object/array copy. Function clones are actually wrappers around the |
|
262 original function. Array-like objects are treated as arrays. Primitives are |
|
263 returned untouched. Optionally, a function can be provided to handle other data |
|
264 types, filter keys, validate values, etc. |
|
265 |
|
266 **Note:** Cloning a non-trivial object is a reasonably heavy operation, due to |
|
267 the need to recursively iterate down non-primitive properties. Clone should be |
|
268 used only when a deep clone down to leaf level properties is explicitly |
|
269 required. This method will also |
|
270 |
|
271 In many cases (for example, when trying to isolate objects used as hashes for |
|
272 configuration properties), a shallow copy, using `Y.merge()` is normally |
|
273 sufficient. If more than one level of isolation is required, `Y.merge()` can be |
|
274 used selectively at each level which needs to be isolated from the original |
|
275 without going all the way to leaf properties. |
|
276 |
|
277 @method clone |
|
278 @param {object} o what to clone. |
|
279 @param {boolean} safe if true, objects will not have prototype items from the |
|
280 source. If false, they will. In this case, the original is initially |
|
281 protected, but the clone is not completely immune from changes to the source |
|
282 object prototype. Also, cloned prototype items that are deleted from the |
|
283 clone will result in the value of the source prototype being exposed. If |
|
284 operating on a non-safe clone, items should be nulled out rather than |
|
285 deleted. |
|
286 @param {function} f optional function to apply to each item in a collection; it |
|
287 will be executed prior to applying the value to the new object. |
|
288 Return false to prevent the copy. |
|
289 @param {object} c optional execution context for f. |
|
290 @param {object} owner Owner object passed when clone is iterating an object. |
|
291 Used to set up context for cloned functions. |
|
292 @param {object} cloned hash of previously cloned objects to avoid multiple |
|
293 clones. |
|
294 @return {Array|Object} the cloned object. |
|
295 **/ |
|
296 Y.clone = function(o, safe, f, c, owner, cloned) { |
|
297 var o2, marked, stamp; |
|
298 |
|
299 // Does not attempt to clone: |
|
300 // |
|
301 // * Non-typeof-object values, "primitive" values don't need cloning. |
|
302 // |
|
303 // * YUI instances, cloning complex object like YUI instances is not |
|
304 // advised, this is like cloning the world. |
|
305 // |
|
306 // * DOM nodes (#2528250), common host objects like DOM nodes cannot be |
|
307 // "subclassed" in Firefox and old versions of IE. Trying to use |
|
308 // `Object.create()` or `Y.extend()` on a DOM node will throw an error in |
|
309 // these browsers. |
|
310 // |
|
311 // Instad, the passed-in `o` will be return as-is when it matches one of the |
|
312 // above criteria. |
|
313 if (!L.isObject(o) || |
|
314 Y.instanceOf(o, YUI) || |
|
315 (o.addEventListener || o.attachEvent)) { |
|
316 |
|
317 return o; |
|
318 } |
|
319 |
|
320 marked = cloned || {}; |
|
321 |
|
322 switch (L.type(o)) { |
|
323 case 'date': |
|
324 return new Date(o); |
|
325 case 'regexp': |
|
326 // if we do this we need to set the flags too |
|
327 // return new RegExp(o.source); |
|
328 return o; |
|
329 case 'function': |
|
330 // o2 = Y.bind(o, owner); |
|
331 // break; |
|
332 return o; |
|
333 case 'array': |
|
334 o2 = []; |
|
335 break; |
|
336 default: |
|
337 |
|
338 // #2528250 only one clone of a given object should be created. |
|
339 if (o[CLONE_MARKER]) { |
|
340 return marked[o[CLONE_MARKER]]; |
|
341 } |
|
342 |
|
343 stamp = Y.guid(); |
|
344 |
|
345 o2 = (safe) ? {} : Y.Object(o); |
|
346 |
|
347 o[CLONE_MARKER] = stamp; |
|
348 marked[stamp] = o; |
|
349 } |
|
350 |
|
351 Y.each(o, function(v, k) { |
|
352 if ((k || k === 0) && (!f || (f.call(c || this, v, k, this, o) !== false))) { |
|
353 if (k !== CLONE_MARKER) { |
|
354 if (k == 'prototype') { |
|
355 // skip the prototype |
|
356 // } else if (o[k] === o) { |
|
357 // this[k] = this; |
|
358 } else { |
|
359 this[k] = |
|
360 Y.clone(v, safe, f, c, owner || o, marked); |
|
361 } |
|
362 } |
|
363 } |
|
364 }, o2); |
|
365 |
|
366 if (!cloned) { |
|
367 Y.Object.each(marked, function(v, k) { |
|
368 if (v[CLONE_MARKER]) { |
|
369 try { |
|
370 delete v[CLONE_MARKER]; |
|
371 } catch (e) { |
|
372 v[CLONE_MARKER] = null; |
|
373 } |
|
374 } |
|
375 }, this); |
|
376 marked = null; |
|
377 } |
|
378 |
|
379 return o2; |
|
380 }; |
|
381 |
|
382 /** |
|
383 * Returns a function that will execute the supplied function in the |
|
384 * supplied object's context, optionally adding any additional |
|
385 * supplied parameters to the beginning of the arguments collection the |
|
386 * supplied to the function. |
|
387 * |
|
388 * @method bind |
|
389 * @param {Function|String} f the function to bind, or a function name |
|
390 * to execute on the context object. |
|
391 * @param {object} c the execution context. |
|
392 * @param {any} args* 0..n arguments to include before the arguments the |
|
393 * function is executed with. |
|
394 * @return {function} the wrapped function. |
|
395 */ |
|
396 Y.bind = function(f, c) { |
|
397 var xargs = arguments.length > 2 ? |
|
398 Y.Array(arguments, 2, true) : null; |
|
399 return function() { |
|
400 var fn = L.isString(f) ? c[f] : f, |
|
401 args = (xargs) ? |
|
402 xargs.concat(Y.Array(arguments, 0, true)) : arguments; |
|
403 return fn.apply(c || fn, args); |
|
404 }; |
|
405 }; |
|
406 |
|
407 /** |
|
408 * Returns a function that will execute the supplied function in the |
|
409 * supplied object's context, optionally adding any additional |
|
410 * supplied parameters to the end of the arguments the function |
|
411 * is executed with. |
|
412 * |
|
413 * @method rbind |
|
414 * @param {Function|String} f the function to bind, or a function name |
|
415 * to execute on the context object. |
|
416 * @param {object} c the execution context. |
|
417 * @param {any} args* 0..n arguments to append to the end of |
|
418 * arguments collection supplied to the function. |
|
419 * @return {function} the wrapped function. |
|
420 */ |
|
421 Y.rbind = function(f, c) { |
|
422 var xargs = arguments.length > 2 ? Y.Array(arguments, 2, true) : null; |
|
423 return function() { |
|
424 var fn = L.isString(f) ? c[f] : f, |
|
425 args = (xargs) ? |
|
426 Y.Array(arguments, 0, true).concat(xargs) : arguments; |
|
427 return fn.apply(c || fn, args); |
|
428 }; |
|
429 }; |
|
430 |
|
431 |
|
432 }, '@VERSION@', {"requires": ["yui-base"]}); |