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.Attribute.call(this);
49
this.init.apply(this, arguments);
54
* Name string to be used to identify instances of
55
* this class, for example in prefixing events.
58
* Classes extending Base, should define their own
59
* static NAME property.
68
* Object literal defining the set of attributes which
69
* will be available for instances of this class, and
70
* how they are configured. See Attributes addAtt method
71
* for a description of configuration options available
80
* Flag indicating whether or not this object
81
* has been through the init lifecycle phase.
83
* @attribute initialized
94
* Flag indicating whether or not this object
95
* has been through the destroy lifecycle phase.
97
* @attribute destroyed
112
* Builds a constructor function (class) from the
113
* main function, and array of extension functions (classes)
117
* The cfg object literal supports the following properties
120
* <dt>dynamic <boolean></dt>
122
* <p>If true, a completely new class
123
* is created which extends the main class, and acts as the
124
* host on which the extension classes are augmented.</p>
125
* <p>If false, the extensions classes are augmented directly to
126
* the main class, modifying the main classes prototype.</p>
128
* <dt>aggregates <String[]></dt>
129
* <dd>An array of static property names, which will get aggregated
130
* on to the built class in addition to the default properties build
131
* will always aggregate - "ATTRS" and "PLUGINS", as defined by
132
* Base.build.AGGREGATES</dd>
137
* @param {Function} main The main class on which to base the built class
138
* @param {Function[]} extensions The set of extension classes which will be
139
* augmented/aggregated to the built class.
140
* @param {Object} cfg
141
* @return {Function} A custom class, created from the provided main and extension classes
143
Base.build = function(main, extensions, cfg) {
145
var build = Base.build,
153
aggregates = cfg.aggregates;
154
dynamic = cfg.dynamic;
157
// Create dynamic class or just modify main class
158
builtClass = (dynamic) ? build._template(main) : main;
160
builtClass._yuibuild = {
166
aggregates = (aggregates) ? build.AGGREGATES.concat(aggregates) : build.AGGREGATES;
168
var el = extensions.length,
169
al = aggregates.length,
172
// Shallow isolate aggregates
173
if (dynamic && aggregates) {
174
for (i = 0; i < al; i++) {
175
var val = aggregates[i];
176
if (O.owns(main, val)) {
177
builtClass[val] = L.isArray(main[val]) ? [] : {};
180
Y.aggregate(builtClass, main, true, aggregates);
184
for (i = 0; i < el; i++) {
185
extClass = extensions[i];
188
Y.aggregate(builtClass, extClass, true, aggregates);
192
Y.mix(builtClass, extClass, true, null, 1);
194
builtClass._yuibuild.exts.push(extClass);
195
key = key + ":" + Y.stamp(extClass);
198
builtClass._yuibuild.id = key;
199
builtClass.prototype.hasImpl = build._hasImpl;
202
builtClass.NAME = main.NAME;
203
builtClass.prototype.constructor = builtClass;
211
AGGREGATES : ["ATTRS", "PLUGINS"],
213
_template: function(main) {
215
function BuiltClass() {
216
BuiltClass.superclass.constructor.apply(this, arguments);
218
var f = BuiltClass._yuibuild.exts,
221
for (var i = 0; i < l; i++) {
222
f[i].apply(this, arguments);
227
Y.extend(BuiltClass, main);
232
_hasImpl : function(extClass) {
233
if (this.constructor._yuibuild) {
234
var f = this.constructor._yuibuild.exts,
238
for (i = 0; i < l; i++) {
239
if (f[i] === extClass) {
251
* Creates a new object instance, based on a dynamically created custom class.
252
* The custom class is created from the main class passed in as the first parameter
253
* along with the list of extension classes passed in
254
* as the second parameter using <a href="#method_build">Base.build</a>
255
* with "dynamic" set to true. See the documentation for this method
256
* to see how the main class and extension classes are used.
259
* <p>Any arguments following the 2nd argument are passed as arguments to the
260
* constructor of the newly created class used to create the instance.</p>
265
* @param {Function} main The main class on which the instance it to be
266
* based. This class will be extended to create the class for the custom instance
267
* @param {Array} extensions The list of extension classes used to augment the
269
* @param {Any*} args* 0..n arguments to pass to the constructor of the
270
* newly created class, when creating the instance.
271
* @return {Object} An instance of the custom class
273
Base.create = function(main, extensions, args) {
274
var c = Base.build(main, extensions, {dynamic:true}),
275
cArgs = Y.Array(arguments, 2, true);
278
F.prototype = c.prototype;
280
return c.apply(new F(), cArgs);
286
* Init lifecycle method, invoked during construction.
287
* Fires the init event prior to invoking initializers on
288
* the class hierarchy.
293
* @param {Object} config Object literal of configuration property name/value pairs
294
* @return {Base} A reference to this object
296
init: function(config) {
299
* The name string to be used to identify
300
* this instance of object.
304
this.name = this.constructor.NAME;
308
* Init event, fired prior to initialization. Invoking
309
* the preventDefault method on the event object provided
310
* to subscribers will prevent initialization from occuring.
313
* Subscribers to the "after" momemt of this event, will be notified
314
* after initialization of the object is complete (and therefore
315
* cannot prevent initialization).
319
* @preventable _defInitFn
320
* @param {Event.Facade} e Event object
321
* @param config Object literal of configuration name/value pairs
325
defaultFn:this._defInitFn
327
this.fire(INIT, config);
334
* Destroy lifecycle method. Fires the destroy
335
* event, prior to invoking destructors for the
339
* Subscribers to the destroy
340
* event can invoke preventDefault on the event object, to prevent destruction
344
* @return {Base} A reference to this object
348
destroy: function() {
352
* Destroy event, fired prior to destruction. Invoking
353
* the preventDefault method on the event object provided
354
* to subscribers will prevent destruction from proceeding.
357
* Subscribers to the "after" moment of this event, will be notified
358
* after destruction is complete (and as a result cannot prevent
362
* @preventable _defDestroyFn
363
* @param {Event.Facade} e Event object
365
this.publish(DESTROY, {
367
defaultFn: this._defDestroyFn
374
* Default init event handler
377
* @param {Object} config Object literal of configuration property name/value pairs
380
_defInitFn : function(config) {
381
_instances[Y.stamp(this)] = this;
382
this._initHierarchy(config);
384
this._conf.remove(INITIALIZED, VALUE);
385
this.set(INITIALIZED, true);
389
* Default destroy event handler
391
* @method _defDestroyFn
394
_defDestroyFn : function() {
395
this._destroyHierarchy();
396
delete _instances[this._yuid];
398
this._conf.remove(DESTROYED, VALUE);
399
this.set(DESTROYED, true);
403
* Returns the top down class hierarchy for this object,
404
* with Base being the first class in the array.
407
* @return {Function[]} An Array of classes (constructor functions), making up the class heirarchy for this object
409
_getClasses : function() {
410
if (!this._classes) {
411
var c = this.constructor,
414
while (c && c.prototype) {
416
c = c.superclass ? c.superclass.constructor : null;
418
this._classes = classes;
420
return this._classes.concat();
424
* Initializes the class hierarchy rooted at this base class,
425
* which includes initializing attributes for each class defined
426
* in the class's static <a href="#property_ATTRS">ATTRS</a> property and invoking the initializer
427
* method on the prototype of each class in the hierarchy.
429
* @method _initHierarchy
430
* @param {Object} userConf Object literal containing attribute name/value pairs
433
_initHierarchy : function(userConf) {
435
classes = this._getClasses();
437
for (var ci = 0, cl = classes.length; ci < cl; ci++) {
438
constr = classes[ci];
440
if (constr._yuibuild && constr._yuibuild.exts && !constr._yuibuild.dynamic) {
441
for (var ei = 0, el = constr._yuibuild.exts.length; ei < el; ei++) {
442
constr._yuibuild.exts[ei].apply(this, arguments);
446
this._initAtts(constr.ATTRS, userConf);
448
if (O.owns(constr.prototype, INITIALIZER)) {
449
constr.prototype[INITIALIZER].apply(this, arguments);
455
* Destroys the class hierarchy rooted at this base class by invoking
456
* the descructor method on the prototype of each class in the hierarchy.
458
* @method _destroyHierarchy
461
_destroyHierarchy : function() {
463
classes = this._getClasses();
465
for (var ci = classes.length-1; ci >= 0; ci--) {
466
constr = classes[ci];
467
if (O.owns(constr.prototype, DESTRUCTOR)) {
468
constr.prototype[DESTRUCTOR].apply(this, arguments);
474
* Default toString implementation. Provides the constructor NAME
475
* and the instance ID.
478
* @return {String} String representation for this object
480
toString: function() {
481
return this.constructor.NAME + "[" + Y.stamp(this) + "]";
486
* Subscribe to a custom event hosted by this object.
489
* Overrides Event.Target's <a href="Event.Target.html#method_subscribe">subscribe</a> method, to add the name prefix
490
* of the instance to the event type, if absent.
494
* @param {String} type The type of event to subscribe to. If
495
* the type string does not contain a prefix ("prefix:eventType"),
496
* the name property of the instance will be used as the default prefix.
497
* @param {Function} fn The subscribed callback function, invoked when the event is fired.
498
* @param {Object} context Optional execution context for the callback.
499
* @param {Any*} args* 0..n params to supply to the callback
501
* @return {Event.Handle} An event handle which can be used to unsubscribe the subscribed callback.
503
subscribe : function() {
505
a[0] = this._prefixEvtType(a[0]);
506
return ETP.subscribe.apply(this, a);
511
* Fire a custom event by name. The callback functions will be executed
512
* from the context specified when the event was created, and with the
513
* following parameters.
516
* Overrides Event.Target's <a href="Event.Target.html#method_fire">fire</a> method, to add the name prefix
517
* of the instance to the event type, if absent.
521
* @param {String|Object} type The type of the event, or an object that contains
522
* a 'type' property. If the type does not contain a prefix ("prefix:eventType"),
523
* the name property of the instance will be used as the default prefix.
524
* @param {Any*} args* 0..n Additional arguments to pass to subscribers.
525
* @return {boolean} The return value from Event Target's <a href="Event.Target.html#method_fire">fire</a> method.
530
if (L.isString(a[0])) {
531
a[0] = this._prefixEvtType(a[0]);
532
} else if (a[0].type){
533
a[0].type = this._prefixEvtType(a[0].type);
535
return ETP.fire.apply(this, a);
540
* Creates a new custom event of the specified type. If a custom event
541
* by that name already exists, it will not be re-created. In either
542
* case the custom event is returned.
545
* Overrides Event.Target's <a href="Event.Target.html#method_publish">publish</a> method, to add the name prefix
546
* of the instance to the event type, if absent.
550
* @param {String} type The type, or name of the event. If the type does not
551
* contain a prefix ("prefix:eventType"), the name property of the instance will
552
* be used as the default prefix.
553
* @param {Object} opts Optional config params (see Event.Target <a href="Event.Target.html#method_publish">publish</a> for details)
554
* @return {Event.Custom} The published custom event object
556
publish : function() {
558
a[0] = this._prefixEvtType(a[0]);
559
return ETP.publish.apply(this, a);
564
* Subscribe to a custom event hosted by this object. The
565
* supplied callback will execute <em>after</em> any listeners added
566
* via the subscribe method, and after the default function,
567
* if configured for the event, has executed.
570
* Overrides Event.Target's <a href="Event.Target.html#method_after">after</a> method, to add the name prefix
571
* of the instance to the event type, if absent.
574
* @param {String} type The type of event to subscribe to. If
575
* the type string does not contain a prefix ("prefix:eventType"),
576
* the name property of the instance will be used as the default prefix.
577
* @param {Function} fn The subscribed callback function
578
* @param {Object} context Optional execution context for the callback
579
* @param {Any*} args* 0..n params to supply to the callback
580
* @return {Event.Handle} Event handle which can be used to unsubscribe the subscribed callback.
584
a[0] = this._prefixEvtType(a[0]);
585
return ETP.after.apply(this, a);
590
* Unsubscribes one or more listeners the from the specified event.
593
* Overrides Event.Target's <a href="Event.Target.html#method_unsubscribe">unsubscribe</a> method, to add the name prefix
594
* of the instance to the event type, if absent.
596
* @method unsubscribe
597
* @param {String|Object} type Either the handle to the subscriber or the
598
* type of event. If the type
599
* is not specified, it will attempt to remove
600
* the listener from all hosted events. If
601
* the type string does not contain a prefix
602
* ("prefix:eventType"), the name property of the
603
* instance will be used as the default prefix.
604
* @param {Function} fn The subscribed function to unsubscribe, if not
605
* supplied, all subscribers will be removed.
606
* @param {Object} context The custom object passed to subscribe. This is
607
* optional, but if supplied will be used to
608
* disambiguate multiple listeners that are the same
609
* (e.g., you subscribe many object using a function
610
* that lives on the prototype)
611
* @return {boolean} true if the subscriber was found and detached.
613
unsubscribe: function(type, fn, context) {
615
if (L.isString(a[0])) {
616
a[0] = this._prefixEvtType(a[0]);
618
return ETP.unsubscribe.apply(this, a);
623
* Removes all listeners from the specified event. If the event type
624
* is not specified, all listeners from all hosted custom events will
628
* Overrides Event.Target's <a href="Event.Target.html#method_unsubscribeAll">unsubscribeAll</a> method, to add the name prefix
629
* of the instance to the event type, if absent.
631
* @method unsubscribeAll
632
* @param {String} type The type, or name of the event. If
633
* the type string does not contain a prefix ("prefix:eventType"),
634
* the name property of the instance will be used as the default prefix
635
* @return {int} The number of listeners unsubscribed
637
unsubscribeAll: function(type) {
639
a[0] = this._prefixEvtType(a[0]);
640
return ETP.unsubscribeAll.apply(this, a);
644
* Utility method to prefix the event name with the
645
* name property of the instance, if absent
647
* @method _prefixEvtType
649
* @param {String} type The event name
650
* @return {String} The prefixed event name
652
_prefixEvtType: function(type) {
653
if (type.indexOf(SEP) === -1 && this.name) {
654
type = this.name + ":" + type;
660
Y.mix(Base, Y.Attribute, false, null, 1);
666
}, '3.0.0pr1' ,{requires:['attribute']});