Creating Custom Modules with YUI Using the YUI Module Control

About the Author:Cyril Doussin is a web developer currently working for Yahoo! London where he has contributed, along with Stuart Colville, Ed Eliot and Ben Ward, to the new front-end code for the price comparison site Kelkoo. He enjoys playing with the YUI, microformats, Django, an old classical guitar and coffee machines.

Introduction

The Yahoo! User Interface Library ships with a few controls such as Calendar or Panel which allow a Javascript programmer to quickly add highly interactive functionality to a web site. As stated in the documentation:

The Container family of components is designed to enable developers to create different kinds of content-containing modules on the web. Module and Overlay are the most basic containers, and they can be used directly or extended to build custom containers. Also part of the Container family are four UI controls that extend Module and Overlay: Tooltip, Panel, Dialog, and SimpleDialog.

These four UI controls actually all extend Overlay, which itself extends Module. The YAHOO.widget.Module object provides a basic framework that you can use to create custom UI controls which follow the common header/body/footer paradigm. This isn't the only pattern you can use in building YUI-based widgets, but it's one I've used to good effect in my own projects.

If you would like to build a similar content-containing Control, YAHOO.widget.Module provides you with a good base for several reasons:

the logic of your Control will have a flexible, event-driven, execution flow

your code will be structured and easily maintainable

you will inherently be able to have multiple, independant instances of your Control on the same page, without any code conflict occuring

This article looks at the process of extending YAHOO.widget.Module in order to build such a Control. Our example will be a potentially long list of contacts, which we will turn into a customisable, paginated list.

This may at first seem like unnecessary extra markup, but as you develop many controls, you will appreciate the consistency that this markup structure brings to your code (both Javascript and CSS), and the shortcuts YAHOO.widget.Module makes available to you as a result.Example 1

Including YUI dependencies

Building the Control step by step

YUI offers functionality such as namespacing, extension, configuration objects, and Custom Events which you can make use of when creating your own UI controls. After analysing each of these features separately, we will end up with a structured template of code which we will then use for our control.

Namespacing

You should use YAHOO.namespace in order to isolate your code into its own namespace, making sure it does not pollute the global scope or conflicts with any third party code (including YUI itself). Grouping all your code in an object under the global YAHOO object is considered standard practice:

Extending

You then need to define your constructor and specify that your new Object will be inheriting YAHOO.widget.Module:

The two parameters accepted by the constructor are:

el

A reference to the main HTMLElement for the Control (the "contact-list" element in our example)

userConfig

A configuration object (more on this later)

We are also augmenting at this stage our Control's prototype with EventProvider (see Custom Events).

We are now ready to instantiate our Control:

And start taking advantage of the functionality offered by YAHOO.widget.Module, for example the "element", "header", "body" and "footer" properties:Example 2

Configuration

You can define a default configuration for the properties of your Control. These could for example define how your Control should behave in a particular situation.

You can also override this default configuration for each instance of your Control. This is a structured way of implementing customisation.

The optional attributes of a configuration property are:

handler

Function called whenever the configuration property is set

validator

Function returning a Boolean to validate the new value for the property.

suppressEvent

When adding a new configuration property or setting a property's value, a CustomEvent is normally fired. Setting this option to true prevents the event from being fired.

supercedes

Array of Custom Event keys. When a Custom Event is fired after adding a new property or setting or property's value, it is possible to replace any previous Custom Event of the same type which is currently queued for firing by specifying its type in this Array.

You can then use the setProperty and getProperty functions of your Control's config object to modify and access the values of your properties.

Custom Events

YAHOO.util.Event provides a powerful mechanism for you to define custom events which can be triggered by your Controls and listened to by any other javascript function. This allows you to put in practice Event-Driven Development in a simple, lightweight way.

The first thing the YAHOO.widget.Module.prototype.init function does is to call the "initEvents" method. This means you can define any custom event you wish to use for your Control by defining this "initEvents" function as part of your object's prototype:

We are using the createEvent function which we made available by previously extending our Control's prototype with YAHOO.util.EventProvider. This is the standard way of creating Custom Events, which you should implement through all your YUI-based Objects (whether they extend YAHOO.widget.Module or not).

The code showing a different contact should then look like the following:

Note the fireEvent function is also part of YAHOO.util.EventProvider.

Any javascript function can now be set up as a subscriber (also commonly called "listener") to these events, with the possibility of cancelling the custom event. When a custom event fires, the subscriber functions will be called on a "first subscribed, first called" basis.

Init function

The init method of your Control is called upon instantiation. Although you are theoretically free to do what you want in this function, there are some important things you should make it do:

Call the init function of the superclass (ie. call YAHOO.widget.Module.prototype.init)

Fire "beforeInit" and "init" events when appropriate

Note there is no need to call this.initEvents(...) to initialise Custom Events as this gets done automatically when calling YAHOO.widget.Module.prototype.init.

Caching DOM references

YAHOO.widgets.Module automatically caches references to the main widget's HTML Element, and its header, body and footer child elements if they exist (class="hd", class="bd" and class="ft").

If you often need to manipulate certain HTML Elements in your Control, it is generally recommended to cache references to them as properties of your Control. Accessing a reference is faster than calling document.getElementById every time you wish to access an HTMLElement.

Having your main Element cached also means that, should you need to remove your Control from the page and then it it again later on, the state of your Control and all its HTML Elements will be preserved (see example 4).

I also systematically do a few things before exiting the init function:

initDOMManipulations

I call a function named initDOMManipulations which performs any DOM manipulation/transformation required in order to set up the Control. In our example, this is where the "previous" and "next" pagination links which sit in the footer will be created.

initEventListeners

I call a function named initEventListeners sets up any Event listener needed for the control to function. Note this can be regular DOM Events (set up using YAHOO.util.Event.addListener) or YUI Custom Events (using YAHOO.util.CustomEvent.prototype.subscribe).

Default behaviour

And of course you can call any function of your Control's code that needs to execute when the Control is first instanciated.

In our example we will update the display of our contact list to only show contacts meant to be on the first page.

To sum it up, here is the complete code of our init function:

Multiple Controls on the same page

You may want to have several instances of your Control on the same page (eg. for controls such as calendars). A good way to do this is to simply assign a class attribute to your Control's main container Element, fetch all Elements with this class once the page has loaded and create a new instance for each. You may also want to store references to your Control instances for later manipulations.

Putting it all together

Example 3 shows a complete example making use of everything we have seen in this article.

Example 4 is a more comprehensive example and uses the recently released YUI Profiler to demonstrate the speed advantage of caching references and Module instances, as opposed to always destroying your Elements and Objects. You will also notice that the current page is preserved when removing/reading a Control.

Note regarding YAHOO.util.Element

If your control deals mainly with one HTML Element and doesn't have a header/body/footer like structure, you may want to look at extending YAHOO.util.Element, which by default provides you with a few things:

EventProvider

allows you to use Custom Events, and to attach listeners to CustomEvents before their creation (defined in event.js)

AttributeProvider

allows you to use handle the configuration of all your Element's properties and attributes, including triggering events before and after their values are changed, via a unified API.

YAHOO.util.Element serves as the foundation for the DataTable and Charts controls.