2
Copyright (c) 2008, Yahoo! Inc. All rights reserved.
3
Code licensed under the BSD License:
4
http://developer.yahoo.net/yui/license.txt
7
YUI.add('base', function(Y) {
10
* Base class support for objects requiring
11
* managed attributes and acting as event targets
22
INITIALIZED = "initialized",
23
DESTROYED = "destroyed",
24
INITIALIZER = "initializer",
25
DESTRUCTOR = "destructor";
27
var ETP = Y.Event.Target.prototype;
31
* Provides a base class for managed attribute based
32
* objects, which handles the chaining of initializer and destructor methods
33
* across the hierarchy during init and destroy lifecycle methods and
34
* handles automatic configuration of registered Attributes, through
35
* the static <a href="#property_ATTRS">ATTRS</a> property.
38
* <p>The Base class also handles prefixing of event types with the static <a href="#property_NAME">NAME</a>
39
* property for all events fired from instances of classes derived from Base.</p>
45
* @param {Object} config Object literal of configuration property name/value pairs
47
var Base = function() {
48
Y.log('constructor called', 'life', 'base');
49
Y.Attribute.call(this);
50
this.init.apply(this, arguments);
55
* Name string to be used to identify instances of
56
* this class, for example in prefixing events.
59
* Classes extending Base, should define their own
60
* static NAME property.
69
* Object literal defining the set of attributes which
70
* will be available for instances of this class, and
71
* how they are configured. See Attributes addAtt method
72
* for a description of configuration options available
81
* Flag indicating whether or not this object
82
* has been through the init lifecycle phase.
84
* @attribute initialized
95
* Flag indicating whether or not this object
96
* has been through the destroy lifecycle phase.
98
* @attribute destroyed
113
* Builds a constructor function (class) from the
114
* main function, and array of extension functions (classes)
118
* The cfg object literal supports the following properties
121
* <dt>dynamic <boolean></dt>
123
* <p>If true, a completely new class
124
* is created which extends the main class, and acts as the
125
* host on which the extension classes are augmented.</p>
126
* <p>If false, the extensions classes are augmented directly to
127
* the main class, modifying the main classes prototype.</p>
129
* <dt>aggregates <String[]></dt>
130
* <dd>An array of static property names, which will get aggregated
131
* on to the built class in addition to the default properties build
132
* will always aggregate - "ATTRS" and "PLUGINS", as defined by
133
* Base.build.AGGREGATES</dd>
138
* @param {Function} main The main class on which to base the built class
139
* @param {Function[]} extensions The set of extension classes which will be
140
* augmented/aggregated to the built class.
141
* @param {Object} cfg
142
* @return {Function} A custom class, created from the provided main and extension classes
144
Base.build = function(main, extensions, cfg) {
146
var build = Base.build,
154
aggregates = cfg.aggregates;
155
dynamic = cfg.dynamic;
158
// Create dynamic class or just modify main class
159
builtClass = (dynamic) ? build._template(main) : main;
161
builtClass._yuibuild = {
167
aggregates = (aggregates) ? build.AGGREGATES.concat(aggregates) : build.AGGREGATES;
169
var el = extensions.length,
170
al = aggregates.length,
173
// Shallow isolate aggregates
174
if (dynamic && aggregates) {
175
for (i = 0; i < al; i++) {
176
var val = aggregates[i];
177
if (O.owns(main, val)) {
178
builtClass[val] = L.isArray(main[val]) ? [] : {};
181
Y.aggregate(builtClass, main, true, aggregates);
185
for (i = 0; i < el; i++) {
186
extClass = extensions[i];
189
Y.aggregate(builtClass, extClass, true, aggregates);
193
Y.mix(builtClass, extClass, true, null, 1);
195
builtClass._yuibuild.exts.push(extClass);
196
key = key + ":" + Y.stamp(extClass);
199
builtClass._yuibuild.id = key;
200
builtClass.prototype.hasImpl = build._hasImpl;
203
builtClass.NAME = main.NAME;
204
builtClass.prototype.constructor = builtClass;
212
AGGREGATES : ["ATTRS", "PLUGINS"],
214
_template: function(main) {
216
function BuiltClass() {
217
BuiltClass.superclass.constructor.apply(this, arguments);
219
var f = BuiltClass._yuibuild.exts,
222
for (var i = 0; i < l; i++) {
223
f[i].apply(this, arguments);
228
Y.extend(BuiltClass, main);
233
_hasImpl : function(extClass) {
234
if (this.constructor._yuibuild) {
235
var f = this.constructor._yuibuild.exts,
239
for (i = 0; i < l; i++) {
240
if (f[i] === extClass) {
252
* Creates a new object instance, based on a dynamically created custom class.
253
* The custom class is created from the main class passed in as the first parameter
254
* along with the list of extension classes passed in
255
* as the second parameter using <a href="#method_build">Base.build</a>
256
* with "dynamic" set to true. See the documentation for this method
257
* to see how the main class and extension classes are used.
260
* <p>Any arguments following the 2nd argument are passed as arguments to the
261
* constructor of the newly created class used to create the instance.</p>
266
* @param {Function} main The main class on which the instance it to be
267
* based. This class will be extended to create the class for the custom instance
268
* @param {Array} extensions The list of extension classes used to augment the
270
* @param {Any*} args* 0..n arguments to pass to the constructor of the
271
* newly created class, when creating the instance.
272
* @return {Object} An instance of the custom class
274
Base.create = function(main, extensions, args) {
275
var c = Base.build(main, extensions, {dynamic:true}),
276
cArgs = Y.Array(arguments, 2, true);
279
F.prototype = c.prototype;
281
return c.apply(new F(), cArgs);
287
* Init lifecycle method, invoked during construction.
288
* Fires the init event prior to invoking initializers on
289
* the class hierarchy.
294
* @param {Object} config Object literal of configuration property name/value pairs
295
* @return {Base} A reference to this object
297
init: function(config) {
298
Y.log('init called', 'life', 'base');
301
* The name string to be used to identify
302
* this instance of object.
306
this.name = this.constructor.NAME;
310
* Init event, fired prior to initialization. Invoking
311
* the preventDefault method on the event object provided
312
* to subscribers will prevent initialization from occuring.
315
* Subscribers to the "after" momemt of this event, will be notified
316
* after initialization of the object is complete (and therefore
317
* cannot prevent initialization).
321
* @preventable _defInitFn
322
* @param {Event.Facade} e Event object
323
* @param config Object literal of configuration name/value pairs
327
defaultFn:this._defInitFn
329
this.fire(INIT, config);
336
* Destroy lifecycle method. Fires the destroy
337
* event, prior to invoking destructors for the
341
* Subscribers to the destroy
342
* event can invoke preventDefault on the event object, to prevent destruction
346
* @return {Base} A reference to this object
350
destroy: function() {
351
Y.log('destroy called', 'life', 'base');
355
* Destroy event, fired prior to destruction. Invoking
356
* the preventDefault method on the event object provided
357
* to subscribers will prevent destruction from proceeding.
360
* Subscribers to the "after" moment of this event, will be notified
361
* after destruction is complete (and as a result cannot prevent
365
* @preventable _defDestroyFn
366
* @param {Event.Facade} e Event object
368
this.publish(DESTROY, {
370
defaultFn: this._defDestroyFn
377
* Default init event handler
380
* @param {Object} config Object literal of configuration property name/value pairs
383
_defInitFn : function(config) {
384
_instances[Y.stamp(this)] = this;
385
this._initHierarchy(config);
387
this._conf.remove(INITIALIZED, VALUE);
388
this.set(INITIALIZED, true);
392
* Default destroy event handler
394
* @method _defDestroyFn
397
_defDestroyFn : function() {
398
this._destroyHierarchy();
399
delete _instances[this._yuid];
401
this._conf.remove(DESTROYED, VALUE);
402
this.set(DESTROYED, true);
406
* Returns the top down class hierarchy for this object,
407
* with Base being the first class in the array.
410
* @return {Function[]} An Array of classes (constructor functions), making up the class heirarchy for this object
412
_getClasses : function() {
413
if (!this._classes) {
414
var c = this.constructor,
417
while (c && c.prototype) {
419
c = c.superclass ? c.superclass.constructor : null;
421
this._classes = classes;
423
return this._classes.concat();
427
* Initializes the class hierarchy rooted at this base class,
428
* which includes initializing attributes for each class defined
429
* in the class's static <a href="#property_ATTRS">ATTRS</a> property and invoking the initializer
430
* method on the prototype of each class in the hierarchy.
432
* @method _initHierarchy
433
* @param {Object} userConf Object literal containing attribute name/value pairs
436
_initHierarchy : function(userConf) {
438
classes = this._getClasses();
440
for (var ci = 0, cl = classes.length; ci < cl; ci++) {
441
constr = classes[ci];
443
if (constr._yuibuild && constr._yuibuild.exts && !constr._yuibuild.dynamic) {
444
for (var ei = 0, el = constr._yuibuild.exts.length; ei < el; ei++) {
445
constr._yuibuild.exts[ei].apply(this, arguments);
449
this._initAtts(constr.ATTRS, userConf);
451
if (O.owns(constr.prototype, INITIALIZER)) {
452
constr.prototype[INITIALIZER].apply(this, arguments);
458
* Destroys the class hierarchy rooted at this base class by invoking
459
* the descructor method on the prototype of each class in the hierarchy.
461
* @method _destroyHierarchy
464
_destroyHierarchy : function() {
466
classes = this._getClasses();
468
for (var ci = classes.length-1; ci >= 0; ci--) {
469
constr = classes[ci];
470
if (O.owns(constr.prototype, DESTRUCTOR)) {
471
constr.prototype[DESTRUCTOR].apply(this, arguments);
477
* Default toString implementation. Provides the constructor NAME
478
* and the instance ID.
481
* @return {String} String representation for this object
483
toString: function() {
484
return this.constructor.NAME + "[" + Y.stamp(this) + "]";
489
* Subscribe to a custom event hosted by this object.
492
* Overrides Event.Target's <a href="Event.Target.html#method_subscribe">subscribe</a> method, to add the name prefix
493
* of the instance to the event type, if absent.
497
* @param {String} type The type of event to subscribe to. If
498
* the type string does not contain a prefix ("prefix:eventType"),
499
* the name property of the instance will be used as the default prefix.
500
* @param {Function} fn The subscribed callback function, invoked when the event is fired.
501
* @param {Object} context Optional execution context for the callback.
502
* @param {Any*} args* 0..n params to supply to the callback
504
* @return {Event.Handle} An event handle which can be used to unsubscribe the subscribed callback.
506
subscribe : function() {
508
a[0] = this._prefixEvtType(a[0]);
509
return ETP.subscribe.apply(this, a);
514
* Fire a custom event by name. The callback functions will be executed
515
* from the context specified when the event was created, and with the
516
* following parameters.
519
* Overrides Event.Target's <a href="Event.Target.html#method_fire">fire</a> method, to add the name prefix
520
* of the instance to the event type, if absent.
524
* @param {String|Object} type The type of the event, or an object that contains
525
* a 'type' property. If the type does not contain a prefix ("prefix:eventType"),
526
* the name property of the instance will be used as the default prefix.
527
* @param {Any*} args* 0..n Additional arguments to pass to subscribers.
528
* @return {boolean} The return value from Event Target's <a href="Event.Target.html#method_fire">fire</a> method.
533
if (L.isString(a[0])) {
534
a[0] = this._prefixEvtType(a[0]);
535
} else if (a[0].type){
536
a[0].type = this._prefixEvtType(a[0].type);
538
return ETP.fire.apply(this, a);
543
* Creates a new custom event of the specified type. If a custom event
544
* by that name already exists, it will not be re-created. In either
545
* case the custom event is returned.
548
* Overrides Event.Target's <a href="Event.Target.html#method_publish">publish</a> method, to add the name prefix
549
* of the instance to the event type, if absent.
553
* @param {String} type The type, or name of the event. If the type does not
554
* contain a prefix ("prefix:eventType"), the name property of the instance will
555
* be used as the default prefix.
556
* @param {Object} opts Optional config params (see Event.Target <a href="Event.Target.html#method_publish">publish</a> for details)
557
* @return {Event.Custom} The published custom event object
559
publish : function() {
561
a[0] = this._prefixEvtType(a[0]);
562
return ETP.publish.apply(this, a);
567
* Subscribe to a custom event hosted by this object. The
568
* supplied callback will execute <em>after</em> any listeners added
569
* via the subscribe method, and after the default function,
570
* if configured for the event, has executed.
573
* Overrides Event.Target's <a href="Event.Target.html#method_after">after</a> method, to add the name prefix
574
* of the instance to the event type, if absent.
577
* @param {String} type The type of event to subscribe to. If
578
* the type string does not contain a prefix ("prefix:eventType"),
579
* the name property of the instance will be used as the default prefix.
580
* @param {Function} fn The subscribed callback function
581
* @param {Object} context Optional execution context for the callback
582
* @param {Any*} args* 0..n params to supply to the callback
583
* @return {Event.Handle} Event handle which can be used to unsubscribe the subscribed callback.
587
a[0] = this._prefixEvtType(a[0]);
588
return ETP.after.apply(this, a);
593
* Unsubscribes one or more listeners the from the specified event.
596
* Overrides Event.Target's <a href="Event.Target.html#method_unsubscribe">unsubscribe</a> method, to add the name prefix
597
* of the instance to the event type, if absent.
599
* @method unsubscribe
600
* @param {String|Object} type Either the handle to the subscriber or the
601
* type of event. If the type
602
* is not specified, it will attempt to remove
603
* the listener from all hosted events. If
604
* the type string does not contain a prefix
605
* ("prefix:eventType"), the name property of the
606
* instance will be used as the default prefix.
607
* @param {Function} fn The subscribed function to unsubscribe, if not
608
* supplied, all subscribers will be removed.
609
* @param {Object} context The custom object passed to subscribe. This is
610
* optional, but if supplied will be used to
611
* disambiguate multiple listeners that are the same
612
* (e.g., you subscribe many object using a function
613
* that lives on the prototype)
614
* @return {boolean} true if the subscriber was found and detached.
616
unsubscribe: function(type, fn, context) {
618
if (L.isString(a[0])) {
619
a[0] = this._prefixEvtType(a[0]);
621
return ETP.unsubscribe.apply(this, a);
626
* Removes all listeners from the specified event. If the event type
627
* is not specified, all listeners from all hosted custom events will
631
* Overrides Event.Target's <a href="Event.Target.html#method_unsubscribeAll">unsubscribeAll</a> method, to add the name prefix
632
* of the instance to the event type, if absent.
634
* @method unsubscribeAll
635
* @param {String} type The type, or name of the event. If
636
* the type string does not contain a prefix ("prefix:eventType"),
637
* the name property of the instance will be used as the default prefix
638
* @return {int} The number of listeners unsubscribed
640
unsubscribeAll: function(type) {
642
a[0] = this._prefixEvtType(a[0]);
643
return ETP.unsubscribeAll.apply(this, a);
647
* Utility method to prefix the event name with the
648
* name property of the instance, if absent
650
* @method _prefixEvtType
652
* @param {String} type The event name
653
* @return {String} The prefixed event name
655
_prefixEvtType: function(type) {
656
if (type.indexOf(SEP) === -1 && this.name) {
657
type = this.name + ":" + type;
663
Y.mix(Base, Y.Attribute, false, null, 1);
669
}, '3.0.0pr1' ,{requires:['attribute']});