Jump to Table of Contents

Base

Base is designed to be a low-level foundation class from which other attribute- and event target-based classes in the YUI library can be derived. It provides a standard template for creating attribute-based objects across the library and provides a consistent init() and destroy() sequence that chains initialization (initializer) and destruction (destructor) methods for the class hierarchy.

It also provides a way for classes to reuse implementation code through plugins, or through extensions.

3.11.0 Upgrade Note:

Prior to 3.11.0, Base would invoke Extension constructors, add attributes, and invoke initializers one class at a time, which means Extension constructors would have been called after superclass attributes were set up.

As of 3.11.0, Base adds all attributes in one shot, after all Extension constructors are called, and before any initializers are called.

As a result, if you've created custom Extensions which have initialization code in the constructor which accesses attributes, you'll need to move this code to the Extension's initializer, to make sure attributes are available when this code is hit.

Base's HISTORY.md, and the originating pull request, have more details about the need for this change.

Getting Started

To include the source files for Base and its dependencies, first load the YUI seed file if you haven't already loaded it.

<script src="http://yui.yahooapis.com/3.18.1/build/yui/yui-min.js"></script>

Next, create a new YUI instance for your application and populate it with the modules you need by specifying them as arguments to the YUI().use() method. YUI will automatically load any dependencies required by the modules you specify.

<script>
// Create a new YUI instance and populate it with the required modules.
YUI().use('base', function (Y) {
    // Base is available and ready for use. Add implementation
    // code here.
});
</script>

For more information on creating YUI instances and on the use() method, see the documentation for the YUI Global Object.

Extending Base

Although Base can be instantiated, it's really designed to be a root class, which you extend when creating your own Attribute and EventTarget based classes as shown below:

YUI().use("base", function(Y) {

    function MyClass(config) {

        // Invoke Base constructor, passing through arguments
        MyClass.superclass.constructor.apply(this, arguments);
    }

    Y.extend(MyClass, Y.Base, {
        // Prototype methods for your new class
    });
});

Base itself augments Attribute, which in turn augments EventTarget; Base is therefore both an Attribute provider and an Event Target.

Base's constructor expects a configuration object literal, which is used to set up initial values for attributes during construction (discussed below).

Static Properties

Base looks for two "static" properties which it requires you to add to your class constructor — NAME and ATTRS. Base uses these properties when setting up events and attributes for the class:

function MyClass(config) {
    MyClass.superclass.constructor.apply(this, arguments);
}

// Used to identify instances of this class
// For example, to prefix event names

MyClass.NAME = "myClass";

// "Associative Array", used to define the set of attributes
// added by this class. The name of the attribute is the key,
// and the object literal value acts as the configuration
// object passed to addAttrs

MyClass.ATTRS = {
    A : {
        // Attribute "A" configuration
    },

    B : {
        // Attribute "B" configuration
    }
}

Y.extend(MyClass, Y.Base, {
    // Prototype methods for your new class
});

NAME

The NAME property is a string which is used to identify the class.

One core area where it is currently used is to prefix all events that are published by instances of your class. For example, any events published by the class MyClass in the above code snippet will have the myClass prefix. By convention the name string is a camelCase version of the class name.

Event prefixes allow events with the same name fired from instances of different classes to be uniquely identified, when bubbled or broadcast. For instance, a click event on any Menu widget can be discerned by subscribing to the menu:click event; an Editor click would be distinguished as editor:click. Because YUI 3.x Custom Events bubble, this prefixing allows you to subscribe to events from specific classes at a higher level in your application — that is, you can listen from common event target, much the way you would when using event delegation when working with DOM events. For example:

// NAME is used to prefix the provided event type, if not already prefixed,
// when publishing, firing and subscribing to events.

MyClass.prototype.doSomething = function() {
    // Actually fires the event type "myclass:enabled"
    this.fire("enabled");
}

...

var o = new MyClass(cfg);

o.on("enabled", function() {
    // Actually listening for "myclass:enabled".
});

o.on("myclass:enabled", function() {
    // Also listening for "myclass:enabled"
});

The NAME property is also used in the default toString implementation for Base.

ATTRS

The ATTRS property is an associative array (an object with attribute name/configuration pairs) which is used to define the default set of attributes that your class adds to each instance. The instance will contain attributes defined by each class in the class hierarchy from which it is created, with each class adding the set of attributes and supporting code that it requires.

For example, this is the (partial) set of attributes which the Drag class defines:

Drag.ATTRS = {

    node: {
        setter: function(node) {
            var n = Y.one(node);
            if (!n) {
                Y.fail('DD.Drag: Invalid Node Given: ' + node);
            }
            return n;
        }
    },

    dragNode: {
        setter: function(node) {
            var n = Y.one(node);
            if (!n) {
                Y.fail('DD.Drag: Invalid dragNode Given: ' + node);
            }
            return n;
        }
    },

    offsetNode: {
        value: true
    },

    clickPixelThresh: {
        value: DDM.get('clickPixelThresh')
    },

    ...

}; // End of Drag.ATTRS associative array (object literal)

Each property in the object literal (e.g. "dragNode"), defines the name of the attribute to be added, and the corresponding value defines the attribute's configuration. See Attribute's discussion of configuration properties for more details about how configuration objects should be structured.

When instantiating a class derived from Base, Base's init() method will initialize the set of attributes defined by the ATTRS property for each class in the class hierarchy. This helps avoid replication of attribute initialization code in the constructor/initializer of each class.

It also defines a specific order in which attributes are initialized — starting from the Base class first and ending with the specific subclass being instantiated. Within a class, the order in which attributes are defined in the ATTRS property does not matter. If an attribute defined in the ATTRS configuration for the class, requests the value of another attribute defined after it in the ATTRS configuration (in its valueFn or getter for example), the later attribute will be initialized on demand, when the first attribute attempts to get the value of the later attribute.

It is worth noting that Base adds or initializes attributes lazily for performance reasons, meaning the attribute will not be initialized until the first call to get or set it is made. This behavior can be overridden if desired for specific attributes by setting the lazyAdd configuration property to false (for example if the setter for the attribute is responsible for setting some other non attribute state in the object).

Note: If the ATTRS of a class need to be statically updated after the class has been created or defined, then to do so safely use the static modifyAttrs() method.

Initialization and Destruction

Base implements final versions of its init and destroy methods used to establish the initialization and destruction lifecycle phases. Classes extending Base can perform operations during initialization or destruction by defining prototype-level initializer and destructor methods:

Y.extend(MyClass, Y.Base, {

    // Prototype methods for your new class

    // Tasks MyClass needs to perform during
    // the init() lifecycle phase
    initializer : function(cfg) {
        this._wrapper = Y.Node.create('<div class="yui-wrapper"></div>');
    },

    // Tasks MyClass needs to perform during
    // the destroy() lifecycle phase
    destructor : function() {
        Y.Event.purgeElement(this._wrapper);
        this._wrapper.get("parentNode").removeChild(this._wrapper);
        this._wrapper = null;
    }

});

Base's init and destroy methods take care of invoking initializer and destructor methods for each class in the hierarchy. The implementations for each class do not need to call superclass versions of the method. Base ensures that initialization and destruction occur in a fixed order, following the class hierarchy.

initializer()
Base's init method, which is invoked by Base's constructor, will invoke the initializer method for each class in the hierarchy — starting from the Base class first and ending with the subclass being instantiated. The initializer method for each class is invoked after its attributes have been initialized (as discussed above) and will receive the configuration object literal passed to the init method.
destructor()
Base's destroy method, when called, will invoke the destructor method for each class in the hierarchy — starting from the subclass instantiated to create the instance and ending with the Base class (the opposite of initialization).

If your class does not require any code to be executed during init or destroy, you do not need to define the corresponding initializer or destructor method on its prototype.

The "MyComponent" template file provides a starting point for you to create your own components derived from Base.

Plugins

Plugins can be used to add atomic pieces of functionality or features to instances of objects derived from Base, without having to bake support, or even knowledge of the feature, into the core object class. This allows features to be mixed and matched per instance, without having to build all features into a monolithic class or having to ship multiple classes with varying permutations of features.

Plugin.Host adds the following key methods to the Base class:

plug(pluginClass, pluginConfig)

Adds a plugin to the instance with the configuration specified. The plug method adds a new instance of the plugin and attaches it to the instance on the namespace (property) defined by the plugin class' NS property.

The plug method also allows multiple plugins to be added in a single call by passing in an array of plugins with optional configurations as defined in the API documentation.

unplug(pluginClass) or unplug(namespace)
Removes the provided plugin or the plugin at the attached namespace from the instance and destroys it.

The above 2 methods are designed to be used after an instance of the component has already been created. Plugins can also be added using the constructor configuration object, using the plugins configuration key. For example:

var overlay = new Y.Overlay({
    srcNode: "#module",
    plugins : [{fn:AnimPlugin, cfg:{duration:2}}]
});

Additionally, if the component developer wants a certain set of plugins added to his or her component by default, static Base.plug and Base.unplug methods are provided, allowing the developer to define the list of plugins to be added as part of the class definition.

The Plugin landing page discusses plugin development in detail. The Widget IO Plugin, Overlay IO Plugin and Overlay Animation Plugin examples also provide a concrete look at plugin development.

The "MyPlugin" template file provides a starting point for you to create your own plugins derived from Plugin.Base.

Extensions

The Base class provides a static build method used to create custom classes, by mixing a main class, with one or more extension classes.

Extension classes are similar to plugins, in that they encapsulate or bundle specific feature implementations. However extensions are used to mix and match code at the class level, to create new classes, whereas plugins are used to mix and match code at the instance level.

In addition to build, Base also provides the static create and mix methods, which are sugar methods on top of Base.build.

Base.create
The create sugar method makes the task of creating a completely new class, which mixes in extensions, a lot more succint, by providing a way for the caller to pass in additional prototype and static properties which will exist on the newly created class.
Base.mix
The mix sugar method on the other hand, can be used to add extensions into an already existing class.

Create

The Base.create method can be used to dynamically create new classes that are derived from an existing main class and mix in additional "extension" classes to add methods, attributes, events and properties to the main class. Base.create leaves the original main and mixed-in classes untouched so that the main class can still be used without the additional features mixed in. Base.mix, on the other hand, can be used if modifying an existing class is the goal.

/* Main Class */
function Panel(cfg) {
    Panel.superclass.constructor.apply(this, arguments);
}

Panel.ATTRS = {
    // Panel attributes
    close : { ... },
    minimize : { ... },
    shadow : { ... },
    ...
};

Y.extend(Panel, Y.Base, {
    // Panel methods
    show : function() { ... },
    hide : function() { ... },
    minimize : function() { ... }
};

/* Additional Resizable Feature */
function Resizable() {
    this._initResizable();
}

Resizable.ATTRS = {
    handles : { ... },
    constrain : { ... }
};

Resizable.prototype = {
    _initResizable : function() { ... }
    lock : function() { ... }
};

/* Additional Modality Feature */
function Modal() {
    this._initModality();
}

Modal.ATTRS = {
    modal : { ... },
    region : { ... }
};

Modal.prototype = {
    _initModality : function() { ... },
    showMask() : function() { ... },
    hideMask() : function() { ... }
};

// Create a new class WindowPanel, which extends Panel, and
// combines methods/attributes from Resizable and Modal

var WindowPanel = Y.Base.create("windowPanel", Panel, [Resizable, Modal]);

var wp = new WindowPanel({
    shadow: true,
    modal: true,
    handles:["e", "s", "se"]
});

wp.show();
wp.lock();

Under the hood, Base.create:

  • Creates a new 'built' class by extending the main class passed in as the first argument.
  • Augments the list of feature classes, or extensions, to the built class, so that it now has their prototype methods.
  • Aggregates or copies any known static properties on the built class. Static properties to setup on the built class are defined by the component or class author, through a _buildCfg static property on the main class, or on the extension. For example Base defines the ATTRS property as a property which needs to be custom aggregated when mixing in extensions, and Widget adds the HTML_PARSER property for aggregation.

    The Base.create API Documention provides more details about the structure of _buildCfg and how it can be used by component authors.

The new class constructor created by Base.create will invoke the constructors for the main and feature classes when the new class is instantiated, during the init part of the lifecycle.

See Base's API documentation for more details on the create and mix methods.

The "MyExtension" template file provides a starting point for you to create your own extensions.