» Modules and Javascript
Submitted by kplawver on Sun, 2006-04-30 15:10.Modules

Introduction

If you've read through the workshops and are now looking for some more information on how JavaScript works inside modules, and what we recommend you do, you're in the right place! JavasScript is powerful stuff, and in the wrong hands, can do all kinds of damage. We certainly have—you have no idea how many times we've crashed browsers with one line of JavaScript. Here are some guidelines for avoiding that kind of frustration.

Onload Functions

When you create your module, you set the onload attribute of the body to something. When your module is put on the page, that function is called with two arguments: instance and action. The instance is the id of the element that contains your module. The action is (for right now) either "view" or "edit". If your module has an edit interface, then your onload function is called when the edit pane is opened as well. These two arguments allow modules to find themselves on the page, and to know which set of functions to run.

It's imperative that you keep your onload function in the onload attribute. If it's somewhere else (using window.onload or dojo.event.connect), then the including page won't know where to find it, and your module won't know where it is on the page. Finding your module is harder than you think, especially considering that users may want to place multiple instances of your module on their pages.

Keeping it Local

If you're going to go through your module looking for other DOM elements, you should always start from the instance passed in by your onload function. That way, you're staying within your module and not running amok over the whole page (well, unless that's your intention, you maverick).

The other way to keep it local is to keep your functions sandboxed in an object. As you'll see in the skeleton, keeping things in an object is a good way to store configuration options for your module, and to keep tabs on the various instances of your module that might be on the page. By keeping the object name linked to your module-name (see the Creating A Module page for more information on that part of it), you're pretty much assuring yourself that no other module, or the page framework, will nuke your object and mess with your functions. Don't create generic function names like "debug," "init" or "go." If some other module developer uses the same function names, whoever gets loaded last wins that battle. Avoiding the fight is the safer bet here.

Settings and Preferences Using setModuleData

In many cases, your module may need to save data somewhere in the page. We've created a little API for doing that. There are three important functions you need to be aware of:

  • lop.Page(): Create a Page object that contains the functions for setting and getting data.
  • page.setModuleData(moduleNode,key,data): Where "page" is the object returned by lop.Page, you pass in the DOM node that you got back from document.getElementById(instance), the key is the title of the piece of data you want to save (lsuch as  "ur" or "count.") and the data is the string you want to save. Note: Everything gets saved as a string. If you need the data returned as an int, you can wrap getModuleData in parseInt().
  • page.getModuleData(moduleNode,key): Get out the data you set with setModuleData. If we don't find anything, you get back null.

Here's an example:

var page=new lop.Page(); //creating the object
page.setModuleData(module,key,data); // module is the DOM node for the module, key is the name of the setting, data is the value.
var url=page.getModuleData(module,"url"); // returns the set data or "null"

The Please Don'ts

There are some things you really shouldn't do. Why? Mostly because they'll cause trouble with other modules and the page as whole, but also because they're just bad habits you should try to break.

window.onload

Don't use window.onload. It wipes out every other onload function the page tries to run. This is not only just rude, it could break your module and everything else on the page. I know we've just given all the script kiddies out there ideas for chaos, but please don't do this. We recommend using the onload attribute of the body element, which we talk about somewhere else in this document.

Generic function names

Don't use generic function names. We can't emphasize too much how important this is. You can't create a module that depends on a function called "go" when there are ten other modules on the page that may also use that same function. Can't we all just get along?

The easiest way around this that we've found is to create an object for your module, and append functions to the object. Crazy, you say? Maybe so... but if your module is called "aim-fight," you could create an object at the top of your module's JavaScript called "aimFight" and append functions to it as shown below. There's more about this below in the skeleton section:

if ( !aimFight ) {
	var aimFight={};
	aimFight.go=function(url) {
		document.location=url;
	}
}

That's not so bad, is it?

Global Variables

Don't use generic global variables. Keep all your variables scoped to your object or function. Inside your functions, all your variables should be created with var. Example:

// good: var myvar=true;
// bad: myvar=true;

Just like generic functions, generic global variables could do all kinds of bad things to other modules and the rest of the page, and just like generic functions, you can keep variables you want to keep around in your object, which is global.

Don't Overwrite djConfig

This can be an issue when you want to do development in your browser before uploading a module. djConfig holds things such as where Dojo should look for its packages and various other things. It can break the entire experience for users (and your module) if you rewrite it in the live environment. So, if you need to do it, we suggest using the skeleton, and if your standalone variable is true, rewrite djConfig. That way, you can flip just that variable before you upload and everything's fine.

Please Do's

Use Our Copy of Dojo

We love Dojo. Unfortunately, including multiple copies of it in a page causes some problems, especially with drag and drop and getting packages. So, if you're developing a module, please include our copy of Dojo. You can grab it from our Content Delivery Network at the following URL:

http://o.aolcdn.com/iamalpha/.resource/jssdk/dojo-0.2.2/dojo.js

If you need to include a local copy because you're developing without a network connection, feel free to download it and include your local copy. Just remove it before you upload your module.   Also, please don't use any other versions of Dojo in your module.   That could cause all sorts of calamity.

Use Dojo

I know, we all want to do it ourselves, but please, use Dojo. That's what it's there for. Dojo provides all kinds of neato things that you probably want to use, and it will save not only time (because you don't have to write it again) and your user's time because they don't have to download all of your code. They've already got Dojo—all you need to do is use it. We've got more info about Dojo in our introduction to Dojo, and there is a great deal of information about the different packages in the toolkit in their manual. We love it, and we think that you will too.

Use the Skeleton

By now, we've built a few modules, and we've found that the following skeleton cuts down on development time, makes it easier to keep things contained, and makes sure we don't run over  the functions of other modules:

if ( !moduleName ) {
	var moduleName={}; // Creating a new object.
	moduleName.standalone=false; // Setting a couple configuration options
	moduleName.debug=false;
	moduleName.instances=[]; // Creating an array to hold information about the various instances of your module that may end up on the page.
}

moduleName.init=function(instance,action) {}
moduleName.view=function(instance) {}
moduleName.edit=function(instance) {}
moduleName.save=function(form) {}
moduleName.dbg=function(msg) {
	if (moduleName.debug) {
		dojo.debug(msg)
	}
}

If you use the skeleton, be sure to replace all the instances of "moduleName" with a camel-case version of your module name. If your module name is "joes-paragraph", then moduleName should be replaced with "joesParagraph". This way, you'll have a single object you can add properties to and be fairly sure that no other module will mess with them.

Let's go through the individual pieces now:

if ( !moduleName)

This section sets up your initial object if it doesn't exist yet. This is where you can set things such as whether or not your module is in debug or standalone modes, or any other properties you might need to set, such as URLs for different services, or other configuration settings. We always end up using standalone and debug, so we included them.

moduleName.init=function(instance,action)

We'll talk more about this in the onload functions section, but this is the onload function for your module.

moduleName.view=function(instance)

Depending on how complex your module is, you may be able to keep all of your code within moduleName.init. But, if that gets cumbersome, it's easy enough to break it up into its own function. For our modules, the view function gets module data, grabs any data we might need over the network, and displays things to the user.

moduleName.edit=function(instance)

Just like view, edit sets up the edit form. The one thing to remember about edit is that it's torn down and recreated every time you open the edit pane, so you can't just keep DOM node references around for edit. (We learned this the hard way.)

moduleName.save=function(form)

This is the "form handler" for the edit pane. We usually use Dojo's event system to make this function the onsubmit handler for the form. Here's how we do it (inside of moduleName.edit):

var module=dojo.byId(instance);
var f=module.getElementsByTagName("form")[0];
dojo.event.connect(f,"onsubmit",function(evt) { moduleName.save(evt.currentTarget); } );

It works so well, you almost don't even need to know what it's doing! Well, if you really want to know what it's doing and what else you can do with Dojo's event system, check out that link above.

moduleName.dbg=function(msg)

This is a handy one to keep around while you're developing your module. You can put various calls to moduleName.dbg throughout your code, and if you've set moduleName.debug to true, you'll get your debug info. When you want to stop getting debug info, you just set debug back to false. You don't need to take out your debug messages or anything. Pretty handy, huh? There are fancier ways to do debug messages, but this is the simplest.

Debugging Your Module

We all want to know what's going on inside our modules. It's natural. Unfortunately, users don't really want to see a bunch of alerts or other debug info messing with their page. So, how do you debug your module? We talked about this a little bit above in the skeleton section, but we've also created a special debug module that you can drag on your page which will give you a nicely formatted debug area where you can see all your debug messages right there on the page next to your module. It's called "debug-window" and you can drag it in. It allows you to set debug on or off so you don't have to see the debug messages if you don't want to.

There are also some very nicely done debugging tools out on the Internet. Here are some of our favorites:

  • Javascript Shell: Lets you inspect vars, create functions and generally execute JavaScript in real time in the context of the currently loaded page.
  • Firebug: Lets you see JavaScript, CSS, and XmlHttpRequest errors in a handy little console. You can also execute JavaScript inside the currently loaded page, all from the bottom of your browser.
  • View Rendered Source: With so much being built by DOM manipulation or Ajaxed in, this is the only way to easily see what's really on the page, well, other than wading through DOM inspector
  • Web Developer Toolbar: Everyone should already know about this but just in case. Real-time CSS editing is a huge timesaver. Too many other goodies to mention.

Conclusion

JavaScript is better than sliced bread. We love it. But, it's also dangerous, especially in a shared environment where a lot of different scripts are flying around doing their own thing without a whole lot of knowledge about what else is on shared pages. If we're all careful about how we write it, we'll all be happy little module developers and our users will be happy little module users. And then we'll all eat cake! Hooray, cake!