Using YUI to load your Javascript modules +
As promised earlier, i’ll discuss in this post how we load the Javascript modules we have written over the last months. Please note that despite i am mainly talking about portlets in this post, the technique described is also usable in web applications that need to load additional Javascript after page load.
The setting
Let me explain the structure of our project a bit. The main constraint we are facing when developing rich user interfaces for the web platform is that we were up to do so using Java Portlets. Using the portlets standard (JSR-168), we are able to meet our customer’s needs in a way we never could have done when developing traditional, monolithic web applications. But this flexibility comes at a price: we do not know which apps are on a page when it gets loaded; further, we want to be able to add new portlets to each page at any time. From a Javascript perspective, this means that traditional “just put script tags into the header”-style Javascript coding will lead to a very bad user experience, since every page would be bloated with stuff the user will never use and – even more important- the user experience will degrade as we are adding new features to our product, even if our customer does not need or want the new features. As explained before, adding Javascript code to HTML/JSP Parts of the Portlets were no option and would have caused massive double-loading of script code. As our platform grew, we saw the need to put together a Javascript library to support code reuse up front. We clearly needed a mechanism to load our Javascript modules as they are needed. After some experiments (including the use of unholy synchronous XHR calls… shame on me!), we tried the YUI Loader.
Meet the YUI Loader Utility
Its no secret that we are using YUI in our product. As YUI comes with a special module for loading dependencies, the YUI Loader Utility, we explored if we could use YUI for our needs. The Loader’s feature list reads like our wish-list:
- Reliable, sorted loading of dependencies
- Safe, efficient mechanism for adding new components to a page on which YUI may already be present.
- Automatic use of rolled-up files.
So we’ve developed our own solution on top of the YUI loader for our Framework. It consists of two components: A Compile-time dependency analyzer and a configuration file generated by the dependency analyzer that tells YUI how to load our modules.
Analyzing JS dependencies
First of all, every JS module file starts with a set of comments telling the dependency analyzer how the current module is called and what modules are required by the declared module:
// @@module agimatec.topic
// @@requires event, agimatec.datatypes
// Module code goes here
// ...
// ...
// ...
// ...
// ...
// Tell the loader that this module has been loaded
YAHOO.register("agimatec.topic", agimatec.topic, {version:'1.0',build:'000'});
The analyzer (which is current implemented as stand-alone groovy script that is run by maven – we have plans to implement it as maven Mojo) parses the dependency information in the module files and generates a Javascript configuration file for the YUI loader from it:
// ******************************
// Generated Code - DO NOT EDIT
// ******************************
/*
This script adds all AJF module definitions to the YUI Loader utility as noted by
'// @@' - style comments inside the *.js files.
If the global variable 'agimatec_config' is not defined, it is defined by this script.
*/
(function(){
var arrModules =[
//...
{
name:'agimatec.scaffolding',
type:'js',
skinnable:false,
path: '../../agimatec/scaffolding-2.1.11-SNAPSHOT-min.js',
requires:["yahoo","event","calendar","tabview","agimatec.topic","agimatec.util","agimatec.util.table","agimatec.util.element","agimatec.util.minibrowser","agimatec.util.datePicker","agimatec.util.hintedText","agimatec.validator","agimatec.metadata"]},
{
name:'agimatec.topic',
type:'js',
skinnable:false,
path: '../../agimatec/topic-2.1.11-SNAPSHOT-min.js',
requires:["event","agimatec.datatypes"]},
{
name:'agimatec.util.browser',
type:'js',
skinnable:false,
path: '../../agimatec/util/browser-2.1.11-SNAPSHOT-min.js',
requires:["yahoo","dom","event","animation","datatable","agimatec.topic","agimatec.util","agimatec.util.yui","agimatec.util.lucene","agimatec.util.element","agimatec.preferences"]},
{
name:'agimatec.util.connector',
type:'js',
skinnable:false,
path: '../../agimatec/util/connector-2.1.11-SNAPSHOT-min.js',
requires:["yahoo","dom","event","container","datatable","agimatec.util","agimatec.metadata","agimatec.util.table","agimatec.portletservice","agimatec.util.minibrowser"]},
{
name:'agimatec.util.context',
type:'js',
skinnable:false,
path: '../../agimatec/util/context-2.1.11-SNAPSHOT-min.js',
requires:["agimatec.metadata","agimatec.util"]},
{
name:'agimatec.util.datePicker',
type:'js',
skinnable:false,
path: '../../agimatec/util/datePicker-2.1.11-SNAPSHOT-min.js',
requires:["yahoo","dom","event","agimatec.util","agimatec.util.hintedText"]},
// ...
];
agimatec_config = agimatec_config || {};
agimatec_config._modules = arrModules;
agimatec_config.getLoader = function(){
var modulesLength=agimatec_config._modules.length, i;
var configObject = {};
configObject.base = agimatec_config.yuiBasePath;
if(agimatec_config.moduleFilter){
configObject.filter=agimatec_config.moduleFilter;
}
if(undefined !== agimatec_config.allowRollup){
configObject.allowRollup = agimatec_config.allowRollup;
}
// configure the default skin
configObject.skin = {
'defaultSkin':agimatec_config.defaultSkinName,
'base': 'assets/skins/',
'path': 'skin.css',
'rollup': 3
};
var loader = new YAHOO.util.YUILoader(configObject);
for(i=0; i
This file is included inside the header of every portlet page. You see that thanks to the YUI Loader, we can mix YUI modules with our own ones without hassle, making development of a comprehensive Javascript Library much easier. (If your wondering why we add version numbers to the referenced module names, see this article)
Loading a module is as easy as:
// ...
var loader = agimatec_config.getLoader()
loader.require("agimatec.topic");
loader.insert(function(){
// use agimatec.topic and ALL of its dependencies here !!!!
});
You see that using modules is very easy. All you have to do is to tell the loader what you need and provide a function that should get executed as soon as the dependencies are resolved by adding the noted Javascript files.
This solution served us very well in the last months, in fact our complete webapp development depends on this loading mechanism. It made growing a stable and feature-rich javascript library possible for us without destroying the user experience.
This post just scratches the features of our Javascript module tooling. We have also added support for a module description file format that allows us to describe Javascript modules using XML and generate the loading and dependency management code, along with support for “rollup” JS modules that allow us to reduce the number of requests needed to load portal pages.
If you are interested in this component, just tell us so in the comments and we will see how fast we can get this utility out of the door as Open Source.
This is part two of an article series about Ajax Portlet development. In my next post, i will tell you about how we handle inter-portlet communication in our framework and how our approach makes our application extremely flexible without hardcoding dependencies between portlets.
This is exactly what I’ve been looking for!
Nice to hear that there is interest in our solution. There is some work to do on our side before we are able to release the code as open source.
Can you describe a bit what you are trying to do? — This may be a good starting point for a how-to (because documentation of the features is definitly something we need…).
[...] Simon Tiffert told us of a co-workers post on loading your JavaScript modules with the YUI Loader. [...]
[...] Simon Tiffert told us of a co-workers post on loading your JavaScript modules with the YUI Loader. [...]
[...] Simon Tiffert told us of a co-workers post on loading your JavaScript modules with the YUI Loader. [...]
[...] Simon Tiffert told us of a co-workers post on loading your JavaScript modules with the YUI Loader. [...]
[...] Simon Tiffert told us of a co-workers post on loading your JavaScript modules with the YUI Loader. [...]
Can you send me the code please.
[...] JSUnit, Scriptaculous Unit Test Runner and YUI test. With a lot of YUI components like the YUI Loader and many YUI widgets we refactored our unit tests to use YUI Test. We like the Yahoo User Interface [...]
I am currently working on a open-source version of the code. I hope that we are able to release it soon.
Stay tuned.
[...] Using YUI loader for your own javascript modules [...]
Nice writeup. I’m currently working on something similar so it’s nice to see an overview how someone else is configuring their rich client-side UI via the server. I’m looking setting up a config object on the server and interacts with the YUI 3 loader (which is more baked into core than in the 2.x branch).
I am currently working on an open-source version of the tool (as was mentioned in the article). I try to make things as pluggable as possible, using an annotaion/template mechanism. Maybe it would be possible to create a YUI 3 version of the config file created at compile time. It would be nice if you could provide a sample how to tell YUI 3 about new modules so i can include that functionality into the solution (or – even better – you could provide one yourself).
I expect to be able to release a working version aof the tool within the next 2-3 weeks, so stay tuned, i will definitely write an article about how to use it and what is possible with it.
[...] See the YUI loader in action, the list of modules shows in a popup dialog while the page is loading. [...]
Have you released this code yet? We’re very much faced with the same problem at my company, and would love to check out your implementation. Thanks!
Hi!
Sort of ;-) Have a look at http://code.google.com/p/agimatec-javascript-tools/ , you`ll need to check out the sources using Subversion, but there are some examples that should work. The code is not in a state that i would call ready for a release, and i did not have as much time as i expected but it works. It lacks proper error reporting by now, so be warned to double-check your module names.
If you want to see a working example, check out the example found at http://code.google.com/p/agimatec-javascript-tools/source/browse/#svn/trunk/js-dependency-loader/examples/basic_modules.
As always, feedback is very welcome, help (in whatever form), too!
If you have any questions / problems, drop me a line, i will be happy to help.