|
1 /* |
|
2 Copyright (c) 2009, Yahoo! Inc. All rights reserved. |
|
3 Code licensed under the BSD License: |
|
4 http://developer.yahoo.net/yui/license.txt |
|
5 version: 3.0.0 |
|
6 build: 1549 |
|
7 */ |
|
8 YUI.add('base-base', function(Y) { |
|
9 |
|
10 /** |
|
11 * The base module provides the Base class, which objects requiring attribute and custom event support can extend. |
|
12 * The module also provides two ways to reuse code - An augmentable Plugin.Host interface which provides plugin support |
|
13 * (which is augmented to the Base class) and Base.build which provides a way to |
|
14 * build custom classes using extensions. |
|
15 * |
|
16 * @module base |
|
17 */ |
|
18 |
|
19 /** |
|
20 * The base-base submodule provides the Base class without the Plugin support, provided by Plugin.Host, |
|
21 * and without the extension support provided by Base.build. |
|
22 * |
|
23 * @module base |
|
24 * @submodule base-base |
|
25 */ |
|
26 var O = Y.Object, |
|
27 L = Y.Lang, |
|
28 DOT = ".", |
|
29 DESTROY = "destroy", |
|
30 INIT = "init", |
|
31 INITIALIZED = "initialized", |
|
32 DESTROYED = "destroyed", |
|
33 INITIALIZER = "initializer", |
|
34 OBJECT_CONSTRUCTOR = Object.prototype.constructor, |
|
35 DEEP = "deep", |
|
36 SHALLOW = "shallow", |
|
37 DESTRUCTOR = "destructor", |
|
38 |
|
39 Attribute = Y.Attribute; |
|
40 |
|
41 /** |
|
42 * <p> |
|
43 * A base class which objects requiring attributes and custom event support can |
|
44 * extend. Base also handles the chaining of initializer and destructor methods across |
|
45 * the hierarchy as part of object construction and destruction. Additionally, attributes configured |
|
46 * through the static <a href="#property_Base.ATTRS">ATTRS</a> property for each class |
|
47 * in the hierarchy will be initialized by Base. |
|
48 * </p> |
|
49 * |
|
50 * <p> |
|
51 * The static <a href="#property_Base.NAME">NAME</a> property of each class extending |
|
52 * from Base will be used as the identifier for the class, and is used by Base to prefix |
|
53 * all events fired by instances of that class. |
|
54 * </p> |
|
55 * @class Base |
|
56 * @constructor |
|
57 * @uses Attribute |
|
58 * @uses Plugin.Host |
|
59 * |
|
60 * @param {Object} config Object with configuration property name/value pairs |
|
61 */ |
|
62 function Base() { |
|
63 Y.log('constructor called', 'life', 'base'); |
|
64 |
|
65 Attribute.call(this); |
|
66 |
|
67 // If Plugin.Host has been augmented [ through base-pluginhost ], setup it's |
|
68 // initial state, but don't initialize Plugins yet. That's done after initialization. |
|
69 var PluginHost = Y.Plugin && Y.Plugin.Host; |
|
70 if (this._initPlugins && PluginHost) { |
|
71 PluginHost.call(this); |
|
72 } |
|
73 |
|
74 if (this._lazyAddAttrs !== false) { this._lazyAddAttrs = true; } |
|
75 |
|
76 this.init.apply(this, arguments); |
|
77 } |
|
78 |
|
79 /** |
|
80 * The list of properties which can be configured for |
|
81 * each attribute (e.g. setter, getter, writeOnce, readOnly etc.) |
|
82 * |
|
83 * @property Base._ATTR_CFG |
|
84 * @type Array |
|
85 * @static |
|
86 * @private |
|
87 */ |
|
88 Base._ATTR_CFG = Attribute._ATTR_CFG.concat("cloneDefaultValue"); |
|
89 |
|
90 /** |
|
91 * <p> |
|
92 * The string to be used to identify instances of |
|
93 * this class, for example in prefixing events. |
|
94 * </p> |
|
95 * <p> |
|
96 * Classes extending Base, should define their own |
|
97 * static NAME property, which should be camelCase by |
|
98 * convention (e.g. MyClass.NAME = "myClass";). |
|
99 * </p> |
|
100 * @property Base.NAME |
|
101 * @type String |
|
102 * @static |
|
103 */ |
|
104 Base.NAME = "base"; |
|
105 |
|
106 /** |
|
107 * The default set of attributes which will be available for instances of this class, and |
|
108 * their configuration. In addition to the configuration properties listed by |
|
109 * Attribute's <a href="Attribute.html#method_addAttr">addAttr</a> method, the attribute |
|
110 * can also be configured with a "cloneDefaultValue" property, which defines how the statically |
|
111 * defined value field should be protected ("shallow", "deep" and false are supported values). |
|
112 * |
|
113 * By default if the value is an object literal or an array it will be "shallow" cloned, to |
|
114 * protect the default value. |
|
115 * |
|
116 * @property Base.ATTRS |
|
117 * @type Object |
|
118 * @static |
|
119 */ |
|
120 Base.ATTRS = { |
|
121 /** |
|
122 * Flag indicating whether or not this object |
|
123 * has been through the init lifecycle phase. |
|
124 * |
|
125 * @attribute initialized |
|
126 * @readonly |
|
127 * @default false |
|
128 * @type boolean |
|
129 */ |
|
130 initialized: { |
|
131 readOnly:true, |
|
132 value:false |
|
133 }, |
|
134 |
|
135 /** |
|
136 * Flag indicating whether or not this object |
|
137 * has been through the destroy lifecycle phase. |
|
138 * |
|
139 * @attribute destroyed |
|
140 * @readonly |
|
141 * @default false |
|
142 * @type boolean |
|
143 */ |
|
144 destroyed: { |
|
145 readOnly:true, |
|
146 value:false |
|
147 } |
|
148 }; |
|
149 |
|
150 Base.prototype = { |
|
151 |
|
152 /** |
|
153 * Init lifecycle method, invoked during construction. |
|
154 * Fires the init event prior to setting up attributes and |
|
155 * invoking initializers for the class hierarchy. |
|
156 * |
|
157 * @method init |
|
158 * @final |
|
159 * @chainable |
|
160 * @param {Object} config Object with configuration property name/value pairs |
|
161 * @return {Base} A reference to this object |
|
162 */ |
|
163 init: function(config) { |
|
164 Y.log('init called', 'life', 'base'); |
|
165 |
|
166 /** |
|
167 * The string used to identify the class of this object. |
|
168 * |
|
169 * @deprecated Use this.constructor.NAME |
|
170 * @property name |
|
171 * @type String |
|
172 */ |
|
173 this._yuievt.config.prefix = this.name = this.constructor.NAME; |
|
174 |
|
175 /** |
|
176 * <p> |
|
177 * Lifecycle event for the init phase, fired prior to initialization. |
|
178 * Invoking the preventDefault() method on the event object provided |
|
179 * to subscribers will prevent initialization from occuring. |
|
180 * </p> |
|
181 * <p> |
|
182 * Subscribers to the "after" momemt of this event, will be notified |
|
183 * after initialization of the object is complete (and therefore |
|
184 * cannot prevent initialization). |
|
185 * </p> |
|
186 * |
|
187 * @event init |
|
188 * @preventable _defInitFn |
|
189 * @param {EventFacade} e Event object, with a cfg property which |
|
190 * refers to the configuration object passed to the constructor. |
|
191 */ |
|
192 this.publish(INIT, { |
|
193 queuable:false, |
|
194 defaultFn:this._defInitFn |
|
195 }); |
|
196 |
|
197 if (config) { |
|
198 if (config.on) { |
|
199 this.on(config.on); |
|
200 } |
|
201 if (config.after) { |
|
202 this.after(config.after); |
|
203 } |
|
204 } |
|
205 |
|
206 this.fire(INIT, {cfg: config}); |
|
207 |
|
208 return this; |
|
209 }, |
|
210 |
|
211 /** |
|
212 * <p> |
|
213 * Destroy lifecycle method. Fires the destroy |
|
214 * event, prior to invoking destructors for the |
|
215 * class hierarchy. |
|
216 * </p> |
|
217 * <p> |
|
218 * Subscribers to the destroy |
|
219 * event can invoke preventDefault on the event object, to prevent destruction |
|
220 * from proceeding. |
|
221 * </p> |
|
222 * @method destroy |
|
223 * @return {Base} A reference to this object |
|
224 * @final |
|
225 * @chainable |
|
226 */ |
|
227 destroy: function() { |
|
228 Y.log('destroy called', 'life', 'base'); |
|
229 |
|
230 /** |
|
231 * <p> |
|
232 * Lifecycle event for the destroy phase, |
|
233 * fired prior to destruction. Invoking the preventDefault |
|
234 * method on the event object provided to subscribers will |
|
235 * prevent destruction from proceeding. |
|
236 * </p> |
|
237 * <p> |
|
238 * Subscribers to the "after" moment of this event, will be notified |
|
239 * after destruction is complete (and as a result cannot prevent |
|
240 * destruction). |
|
241 * </p> |
|
242 * @event destroy |
|
243 * @preventable _defDestroyFn |
|
244 * @param {EventFacade} e Event object |
|
245 */ |
|
246 this.publish(DESTROY, { |
|
247 queuable:false, |
|
248 defaultFn: this._defDestroyFn |
|
249 }); |
|
250 this.fire(DESTROY); |
|
251 return this; |
|
252 }, |
|
253 |
|
254 /** |
|
255 * Default init event handler |
|
256 * |
|
257 * @method _defInitFn |
|
258 * @param {EventFacade} e Event object, with a cfg property which |
|
259 * refers to the configuration object passed to the constructor. |
|
260 * @protected |
|
261 */ |
|
262 _defInitFn : function(e) { |
|
263 this._initHierarchy(e.cfg); |
|
264 if (this._initPlugins) { |
|
265 // Need to initPlugins manually, to handle constructor parsing, static Plug parsing |
|
266 this._initPlugins(e.cfg); |
|
267 } |
|
268 this._set(INITIALIZED, true); |
|
269 }, |
|
270 |
|
271 /** |
|
272 * Default destroy event handler |
|
273 * |
|
274 * @method _defDestroyFn |
|
275 * @param {EventFacade} e Event object |
|
276 * @protected |
|
277 */ |
|
278 _defDestroyFn : function(e) { |
|
279 this._destroyHierarchy(); |
|
280 if (this._destroyPlugins) { |
|
281 this._destroyPlugins(); |
|
282 } |
|
283 this._set(DESTROYED, true); |
|
284 }, |
|
285 |
|
286 /** |
|
287 * Returns the class hierarchy for this object, with Base being the last class in the array. |
|
288 * |
|
289 * @method _getClasses |
|
290 * @protected |
|
291 * @return {Function[]} An array of classes (constructor functions), making up the class hierarchy for this object. |
|
292 * This value is cached the first time the method, or _getAttrCfgs, is invoked. Subsequent invocations return the |
|
293 * cached value. |
|
294 */ |
|
295 _getClasses : function() { |
|
296 if (!this._classes) { |
|
297 this._initHierarchyData(); |
|
298 } |
|
299 return this._classes; |
|
300 }, |
|
301 |
|
302 /** |
|
303 * Returns an aggregated set of attribute configurations, by traversing the class hierarchy. |
|
304 * |
|
305 * @method _getAttrCfgs |
|
306 * @protected |
|
307 * @return {Object} The hash of attribute configurations, aggregated across classes in the hierarchy |
|
308 * This value is cached the first time the method, or _getClasses, is invoked. Subsequent invocations return |
|
309 * the cached value. |
|
310 */ |
|
311 _getAttrCfgs : function() { |
|
312 if (!this._attrs) { |
|
313 this._initHierarchyData(); |
|
314 } |
|
315 return this._attrs; |
|
316 }, |
|
317 |
|
318 /** |
|
319 * A helper method used when processing ATTRS across the class hierarchy during |
|
320 * initialization. Returns a disposable object with the attributes defined for |
|
321 * the provided class, extracted from the set of all attributes passed in . |
|
322 * |
|
323 * @method _filterAttrCfs |
|
324 * @private |
|
325 * |
|
326 * @param {Function} clazz The class for which the desired attributes are required. |
|
327 * @param {Object} allCfgs The set of all attribute configurations for this instance. |
|
328 * Attributes will be removed from this set, if they belong to the filtered class, so |
|
329 * that by the time all classes are processed, allCfgs will be empty. |
|
330 * |
|
331 * @return {Object} The set of attributes belonging to the class passed in, in the form |
|
332 * of an object with attribute name/configuration pairs. |
|
333 */ |
|
334 _filterAttrCfgs : function(clazz, allCfgs) { |
|
335 var cfgs = null, attr, attrs = clazz.ATTRS; |
|
336 |
|
337 if (attrs) { |
|
338 for (attr in attrs) { |
|
339 if (attrs.hasOwnProperty(attr) && allCfgs[attr]) { |
|
340 cfgs = cfgs || {}; |
|
341 cfgs[attr] = allCfgs[attr]; |
|
342 delete allCfgs[attr]; |
|
343 } |
|
344 } |
|
345 } |
|
346 |
|
347 return cfgs; |
|
348 }, |
|
349 |
|
350 /** |
|
351 * A helper method used by _getClasses and _getAttrCfgs, which determines both |
|
352 * the array of classes and aggregate set of attribute configurations |
|
353 * across the class hierarchy for the instance. |
|
354 * |
|
355 * @method _initHierarchyData |
|
356 * @private |
|
357 */ |
|
358 _initHierarchyData : function() { |
|
359 var c = this.constructor, |
|
360 classes = [], |
|
361 attrs = []; |
|
362 |
|
363 while (c) { |
|
364 // Add to classes |
|
365 classes[classes.length] = c; |
|
366 |
|
367 // Add to attributes |
|
368 if (c.ATTRS) { |
|
369 attrs[attrs.length] = c.ATTRS; |
|
370 } |
|
371 c = c.superclass ? c.superclass.constructor : null; |
|
372 } |
|
373 |
|
374 this._classes = classes; |
|
375 this._attrs = this._aggregateAttrs(attrs); |
|
376 }, |
|
377 |
|
378 /** |
|
379 * A helper method, used by _initHierarchyData to aggregate |
|
380 * attribute configuration across the instances class hierarchy. |
|
381 * |
|
382 * The method will potect the attribute configuration value to protect the statically defined |
|
383 * default value in ATTRS if required (if the value is an object literal, array or the |
|
384 * attribute configuration has cloneDefaultValue set to shallow or deep). |
|
385 * |
|
386 * @method _aggregateAttrs |
|
387 * @private |
|
388 * @param {Array} allAttrs An array of ATTRS definitions across classes in the hierarchy |
|
389 * (subclass first, Base last) |
|
390 * @return {Object} The aggregate set of ATTRS definitions for the instance |
|
391 */ |
|
392 _aggregateAttrs : function(allAttrs) { |
|
393 var attr, |
|
394 attrs, |
|
395 cfg, |
|
396 val, |
|
397 path, |
|
398 i, |
|
399 clone, |
|
400 cfgProps = Base._ATTR_CFG, |
|
401 aggAttrs = {}; |
|
402 |
|
403 if (allAttrs) { |
|
404 for (i = allAttrs.length-1; i >= 0; --i) { |
|
405 attrs = allAttrs[i]; |
|
406 |
|
407 for (attr in attrs) { |
|
408 if (attrs.hasOwnProperty(attr)) { |
|
409 |
|
410 // Protect config passed in |
|
411 cfg = Y.mix({}, attrs[attr], true, cfgProps); |
|
412 |
|
413 val = cfg.value; |
|
414 clone = cfg.cloneDefaultValue; |
|
415 |
|
416 if (val) { |
|
417 if ( (clone === undefined && (OBJECT_CONSTRUCTOR === val.constructor || L.isArray(val))) || clone === DEEP || clone === true) { |
|
418 Y.log('Cloning default value for attribute:' + attr, 'info', 'base'); |
|
419 cfg.value = Y.clone(val); |
|
420 } else if (clone === SHALLOW) { |
|
421 Y.log('Merging default value for attribute:' + attr, 'info', 'base'); |
|
422 cfg.value = Y.merge(val); |
|
423 } |
|
424 // else if (clone === false), don't clone the static default value. |
|
425 // It's intended to be used by reference. |
|
426 } |
|
427 |
|
428 path = null; |
|
429 if (attr.indexOf(DOT) !== -1) { |
|
430 path = attr.split(DOT); |
|
431 attr = path.shift(); |
|
432 } |
|
433 |
|
434 if (path && aggAttrs[attr] && aggAttrs[attr].value) { |
|
435 O.setValue(aggAttrs[attr].value, path, val); |
|
436 } else if (!path){ |
|
437 if (!aggAttrs[attr]) { |
|
438 aggAttrs[attr] = cfg; |
|
439 } else { |
|
440 Y.mix(aggAttrs[attr], cfg, true, cfgProps); |
|
441 } |
|
442 } |
|
443 } |
|
444 } |
|
445 } |
|
446 } |
|
447 |
|
448 return aggAttrs; |
|
449 }, |
|
450 |
|
451 /** |
|
452 * Initializes the class hierarchy for the instance, which includes |
|
453 * initializing attributes for each class defined in the class's |
|
454 * static <a href="#property_Base.ATTRS">ATTRS</a> property and |
|
455 * invoking the initializer method on the prototype of each class in the hierarchy. |
|
456 * |
|
457 * @method _initHierarchy |
|
458 * @param {Object} userVals Object with configuration property name/value pairs |
|
459 * @private |
|
460 */ |
|
461 _initHierarchy : function(userVals) { |
|
462 var lazy = this._lazyAddAttrs, |
|
463 constr, |
|
464 constrProto, |
|
465 ci, |
|
466 ei, |
|
467 el, |
|
468 classes = this._getClasses(), |
|
469 attrCfgs = this._getAttrCfgs(); |
|
470 |
|
471 for (ci = classes.length-1; ci >= 0; ci--) { |
|
472 |
|
473 constr = classes[ci]; |
|
474 constrProto = constr.prototype; |
|
475 |
|
476 if (constr._yuibuild && constr._yuibuild.exts && !constr._yuibuild.dynamic) { |
|
477 for (ei = 0, el = constr._yuibuild.exts.length; ei < el; ei++) { |
|
478 constr._yuibuild.exts[ei].apply(this, arguments); |
|
479 } |
|
480 } |
|
481 |
|
482 this.addAttrs(this._filterAttrCfgs(constr, attrCfgs), userVals, lazy); |
|
483 |
|
484 if (constrProto.hasOwnProperty(INITIALIZER)) { |
|
485 constrProto.initializer.apply(this, arguments); |
|
486 } |
|
487 } |
|
488 }, |
|
489 |
|
490 /** |
|
491 * Destroys the class hierarchy for this instance by invoking |
|
492 * the descructor method on the prototype of each class in the hierarchy. |
|
493 * |
|
494 * @method _destroyHierarchy |
|
495 * @private |
|
496 */ |
|
497 _destroyHierarchy : function() { |
|
498 var constr, |
|
499 constrProto, |
|
500 ci, cl, |
|
501 classes = this._getClasses(); |
|
502 |
|
503 for (ci = 0, cl = classes.length; ci < cl; ci++) { |
|
504 constr = classes[ci]; |
|
505 constrProto = constr.prototype; |
|
506 if (constrProto.hasOwnProperty(DESTRUCTOR)) { |
|
507 constrProto.destructor.apply(this, arguments); |
|
508 } |
|
509 } |
|
510 }, |
|
511 |
|
512 /** |
|
513 * Default toString implementation. Provides the constructor NAME |
|
514 * and the instance ID. |
|
515 * |
|
516 * @method toString |
|
517 * @return {String} String representation for this object |
|
518 */ |
|
519 toString: function() { |
|
520 return this.constructor.NAME + "[" + Y.stamp(this) + "]"; |
|
521 } |
|
522 }; |
|
523 |
|
524 // Straightup augment, no wrapper functions |
|
525 Y.mix(Base, Attribute, false, null, 1); |
|
526 |
|
527 // Fix constructor |
|
528 Base.prototype.constructor = Base; |
|
529 |
|
530 Y.Base = Base; |
|
531 |
|
532 // Fix constructor |
|
533 Base.prototype.constructor = Base; |
|
534 |
|
535 |
|
536 }, '3.0.0' ,{requires:['attribute-base']}); |