Keeping it Clean: Making Good Use of the YUI3 Global Object and Loader

If you are new to YUI3, you might want to visit the YUI3 website before reading.

Most of us know how much of a nightmare client-side code can be. We often come into a project that has a long list of included JavaScript and CSS files in the  <head> tag of a page (or several pages). Code that should be logically grouped together is often scattered across many files with not so meaningful names. In recent years, client-side debugging tools, such as Firebug, have helped us wade through such messes at runtime. Which of course means that you need to be able build, deploy, and run the application first. Let’s just say that is a less than ideal way to go about navigating and learning the client-side code for a project, and leave it at that.

Using the YUI3 global object and loader combined with our own modules and appropriate use of namespacing, we can spare ourselves (and others) from having a massive headache caused by client-side spaghetti (that’s probably been in the basement refrigerator for years and has coagulated into a solid mass held together by dried sauce and moldy Parmesan cheese).

The YUI global object is the root dependency for all YUI3 implementations. It is required for any page that uses YUI3, but it is the only dependency that is required. Additional dependencies are fetched by the YUI loader. A good use of the loader is to bundle your own code as custom modules and add them to the global YUI instance. The loader can then be used to fetch your own modules and their dependencies when needed. Following this model is conducive to writing clean, modular, and well thought out code.

Those familiar with YUI2 are probably used to including all of the necessary files. Let’s use the TabView widget as an example.

YUI2 TabView

http://developer.yahoo.com/yui/examples/tabview/frommarkup_clean.html

<link rel=”stylesheet” type=”text/css” href=”http://yui.yahooapis.com/2.9.0/build/fonts/fonts-min.css” />
<link rel=”stylesheet” type=”text/css” href=”http://yui.yahooapis.com/2.9.0/build/tabview/assets/skins/sam/tabview.css” />
<script type=”text/JavaScript” src=”http://yui.yahooapis.com/2.9.0/build/yahoo-dom-event/yahoo-dom-event.js”></script>
<script type=”text/JavaScript” src=”http://yui.yahooapis.com/2.9.0/build/element/element-min.js”></script>
<script type=”text/JavaScript” src=”http://yui.yahooapis.com/2.9.0/build/tabview/tabview-min.js”></script>
<script>

var myTabs = new YAHOO.widget.TabView("demo");

myTabs.addTab( new YAHOO.widget.Tab({
    label: 'Tab One Label',
    content: '

Tab One Content

',
    active: true
}));

myTabs.addTab( new YAHOO.widget.Tab({
    label: 'Tab Two Label',
    content: '

Tab Two Content

'
}));

myTabs.addTab( new YAHOO.widget.Tab({
    label: 'Tab Three Label',
    content: '

Tab Three Content

'
}));

myTabs.appendTo(document.body);

</script>

OK, so that’s 5 files that we need to include for one widget. Now let’s look at YUI3.

YUI3 TabView

http://developer.yahoo.com/yui/3/examples/tabview/tabview-basic_clean.html

<script type=”text/JavaScript” src=”http://yui.yahooapis.com/3.3.0/build/yui/yui-min.js”></script>

<script>


YUI().use('tabview', function(Y) {
    var tabview = new Y.TabView({
        children: [{
            label: 'foo',
            content: '
foo content
'
        }, {
            label: 'bar',
            content: '
bar content
'
        }, {
            label: 'baz',
            content: '
baz content
'
        }]
    });

    tabview.render('#demo');
});

</script>

The biggest obvious difference here is that in YUI3, the only file we need to include is the core file: yui-min.js. In the use() method, we tell the loader what modules we want. In this case, we only need the ‘tabview’ module.

If you’ve worked with YUI2, you know that the list of included files can become quite large (easily up to 15 or more). In addition, you will probably have a lot of your own JavaScript that needs to be included.

In YUI3, you can package your code as custom modules, which can then be added to the YUI global object. You can then access your custom modules via the use() method.

A major difference you will notice with YUI3 is that it doesn’t have nearly as many widgets out of the box as YUI2. Developers will most likely need to use the YUI3 gallery, or other libraries, in order to find some good widgets that are easy to instantiate and use. This can add many more dependencies. The configuration for such widgets is often fairly complex, but they are added to the global object in the same manner as your own custom modules.

Let’s assume that we have five custom modules, each within their own JavaScript file: foo.js, bar.js, app-config.js, app-validation.js, and app-events.js.

First, let’s look at a custom module in detail.

Let’s say that the example code here is all within the file app-events.js

First, we use the YUI.add() method to effectively add this module to the global YUI instance.


YUI.add('app-events', function(Y) {
//all JavaScript code goes here
},'0.0.1', {
requires: ['base', 'node', 'event-delegate', 'event', 'node-base', 'app-config', 'app-validation', 'foo', 'bar']
});

The first parameter of the add() method is the module name, i.e., ‘app-events’. Anytime we want to use this module, we will pass ‘app-events’ to the use() method. The second parameter is the body of the module (the executable JavaScript code). The next parameter is the version number of the module. And the last parameter states the dependencies for the module. This tells the YUI loader that it needs to load all of these other modules before executing the code within this module. In the example above, in addition to several YUI3 modules, we are stating that the custom modules ‘app-config’, ‘app-validation’, ‘foo’, and ‘bar’ are needed for the code within ‘app-events’ to be executed.

Now, we’ll add a namespace so that we can specify functions and/or variables that can be accessed from external modules.


YUI.add('app-events', function(Y) {
Y.namespace('com.opi.app.events');
//other JavaScript code here
},'0.0.1', {
requires: ['base', 'node', 'event-delegate', 'event', 'node-base', 'app-config', 'app-validation', 'foo', 'bar']
});

Then we might create something like this, which is publicly available, via the ‘com.opi.app.events’ namespace.


Y.com.opi.app.events.handleRadioButtonClick = function() {
   Y.com.opi.app.validation.checkInputs();
};

Here we are calling a function that exists within the ‘com.opi.app.validation’ namespace. In this example, that is the namespace for the code within the ‘app-validation’ module.

The handleRadioButtonClick() function can now be referenced externally, like so.

<input id=”button1″ onclick=”handleClick();” name=”button1″ type=”radio” value=”1″ />


function handleClick() {
	YUI().use("app-events", function(Y) {
		Y.com.opi.app.events.handleRadioButtonClick();
	});
}

However, a better practice would be to leave all of that script out of the main HTML body, and handle it with the YUI ‘event’ module, like so.


YUI.add('app-events', function(Y) {
Y.namespace('com.opi.app.events');

Y.com.opi.app.events.handleRadioButtonClick = function(e) {
     Y.com.opi.app.validation.checkInputs();
};

Y.on("click", Y.com.opi.app.events.handleRadioButtonClick, "#button1");
//more JavaScript here
},'0.0.1', {
   requires: ['base', 'node', 'event-delegate', 'event', 'node-base', 'app-config', 'app-validation', 'foo', 'bar']
});

Let’s say we had some links to other pages, and we wanted to do some validation on form inputs before allowing the user to proceed.

<div id=”myDiv1″>

<a href=”page2.html”>Go to page 2</a>

<a href=”page3.html”>Go to page 3</a>

</div>

We could use the YUI ‘event-delegate’ module and do something like this:


YUI.add('app-events', function(Y) {
	Y.namespace('com.opi.app.events');

	Y.com.opi.app.events.handleRadioButtonClick = function(e) {
		Y.com.opi.app.validation.checkInputs();
	};

	Y.delegate("click", function(e) {
    		var isValid = Y.com.opi.app.validation.isFormValid();

		if (!isValid) {
			Y.com.opi.app.foo.showDialog("Some fields have invalid data, please correct them before continuing to the next page.");
			e.halt();	//prevent the click event from continuing, do not allow user to proceed
		}
    	}, "#myDiv1", "a");

	//more JavaScript here
},'0.0.1', {
    requires: ['base', 'node', 'event-delegate', 'event', 'node-base', 'app-config', 'app-validation', 'foo', 'bar']
});

In the code above, we’re referencing another dependency of ‘app-events’, the ‘foo’ module, which contains the showDialog() function.

The final important step is to configure the YUI global object to use our custom modules.  We are basically telling it which modules we want to be available, and giving the path to the files for each module.

In this example, assume the code below is in the <head> tag after all of the included files.


YUI({
    modules: {
        foo: {
            fullpath: '../scripts/foo.js'
        },
        bar: {
            fullpath: '../scripts/bar.js'
        },
        appConfig: {
            fullpath: '../scripts/app-config.js'
        },
        appValidation: {
            fullpath: '../scripts/app-validation.js'
        },
        appEvents: {
            fullpath: '../scripts/app-events.js'
        }
    }
}).use('foo', 'bar', 'appConfig', 'appValidation', 'appEvents', function(Y){
});

Now these modules can be accessed at any time via the use() method.

Note: In the use() method above, you can see that the parameter values match the name of the configuration parameters that we have specified (i.e., ‘appEvents’).  When it comes time to actually use these modules, we will need to use the name that we provided in the YUI.add() method for each module.  In the example above, we used ‘app-events’ as the name of the module that we created in app-events.js.  Therefore, when it comes time to use that module, we will pass ‘app-events’ to the use() method and not ‘appEvents’ as shown above.

If the extent of our behavioral requirements is to react to events, assuming we put all of our event handling within the app-events module, we don’t need to put any JavaScript in the <body>.  That keeps things nice and tidy.

To summarize, the YUI3 global object and loader gives us a good foundation for client-side code management, while providing easy and efficient means for loading dependencies.  It also imposes a sense of structure that hopefully makes us more aware of how we can better organize our code into logical, purposeful, and reusable modules.

For more info:

Leave a Reply

Your email address will not be published. Required fields are marked *

*

*