Skip to content

Javascript – Wrappers

October 24, 2010

I often avoid working with Javascript for as long as possible, for a couple of reasons:

  • I prefer to work on content-driven sites, not application-driven sites. This line is often blurry…there are no ‘pure’ sites in either category. But I tend to like working on sites that drive content very close to the foreground.
  • In content-driven sites, Javascript should be the last step, after writing HTML and then styling it. If you start with HTML built to accommodate the Javascript, you’ve probably missed, sacrificed, or compromised accessibility, internationalization, search engine optimization, and performance.

When I do roll up my sleeves to script, I prefer to start with libraries, like jQuery, YUI, or prototype. In many of the places I’ve worked, home-brew libraries were used, mainly because of cultural biases within the firm. Most agencies specialize in providing service to a particular industry, and you’ll find yourself building the same things again and again, creating a ‘library’ on top of whatever you’ve used as your base.

Like most other programmers, I scavenge what has worked from project to project and use it again. Recently, I’ve begun the process of moving this from dozens of fragmented files into a consolidated file, with some documentation and examples. I can keep this on a pen drive and have it with me when I need to resolve a past problem.

I’m going to talk about some things that may seem trivial, but I see often when working with legacy code. It may be minutia, but it drives me crazy.

Wrapper functions

How many times have you seen this:


	<a href="javascript:scroll( 0,0 );">Go to top</a>

For a minute, let’s skip the fact that:

  • The javascript pseudo-url is being used, which breaks the link if Javascript is disabled.
  • It breaks the default behavior of the browser: the user will expect the back button to return them to the link, and it doesn’t.
  • An event handler should have been assigned via Javascript, allowing the content to be repurposed in other contexts.

Yes, all that is bad. But the worse offense in a scenario like this?

Using a built-in Javascript function ‘naked’. If the functionality ever needs to change, you must recode all those links. Even if you followed the advice above, and the scroll() was in an external script and assigned with Javascript event handler routines, you may still have scroll() in a dozen spots in your code.

If scroll() was wrapped inside a custom function declaration, you could change the functionality everywhere, at once, site-wide. Even if you were forced to embed it directly into the HTML (sometimes, despite best intentions, this happens), doing this:


	<a href="javascript:myScrollFunction('goToTop');">Go to top</a>

is better than this:


	<a href="javascript:scroll( 0,0 );">Go to top</a>

because in the first example, you’ve hidden the specific details inside the custom function. If the meaning of 'goToTop' ever changes, you change it only within the function you’ve made, not the raw HTML.

I tend to wrap a lot of simple Javascript built-in functions in this way, even if I’m not using HTML event handlers, such as print, close, focus, reset, write and escape.

Sidenote: I’m using ‘built-in’ to denote functions that come with the browser engine, not the core Javascript language. Most of the built-ins I’m referring to aren’t part of the core language, but part of the core DOM engine for most browsers. You’d see a different set of core built-ins in the Adobe Creative Suite Javascript engine. For this discussion, functions in the core language and core browser engines are used interchangeably.

This is a design issue, as well. Sometimes you’ll be writing an application where you only need to use the built-in once. In this case, creating a wrapper doesn’t make sense.

When you have multiple scripts running on the same page that access or use data from the same interface elements, those elements should behave the same way. If those elements start off behaving in the way that print() or scroll() normally work, fine. But if they evolve, you can avoid costly code changes by having those scripts reference a wrapper instead of the raw Javascript built-in. Wrappers can be a powerful design pattern.

I have a collection of wrappers that I use all the time. In almost all cases, all they do is wrap the Javascript built-in function. When the time comes to change functionality, I rewrite the interior of the function. Sometimes, I have to change arguments, but there’s a limit to how much change you can anticipate.

Wrapper example

A fairly straightforward example of a wrapper function is for writing messages, either for debugging or injection into the document. The function below allows you to take a string and create:

  • an alert dialog
  • use document.write to insert the string via script into the page
  • write to the console in Firebug, Chrome, or other browsers that support the console object
  • write the string to an uncaught exception
  • replace the HTML inside an element with the innerHTML property

util.echo();


/*
You can instantiate the object any other way
you like.
*/
if ( typeof util == "undefined" ) {
	util = new Object();
}

/*
wrapper for document.write(), alert(),
and console.log(). Can also throw
uncaught exception. The default 
condition in the switch statement
can be modified to suit your
development style.

First argument is mandatory.
Second and third arguments are optional,
but if the third is used, the first
and second must be present.
*/
util.echo = function( string, mode, id ) {
	if ( !string ) {
		return;
	}
	switch( mode ) {
		case 'WRITE':
			document.write( string );
		break;

		case 'ALERT':
			alert( string );
		break;

		case 'LOG':
			if ( typeof console != 'undefined' ) {
				console.log( string );
			}
		break;

		case 'THROW':
			throw string;
		break;

		case 'DOM':
			if ( !id ) {
				return;
			}
			var element = document.getElementById( id );
			element.innerHTML = string;
		break;

		default:
			alert( string );
	}
};

/* Usage */

/* uses document.write */
util.echo('Hello World!');

/* uses document.write */
util.echo('Hello World!', 'WRITE');

/* uses alert */
util.echo('Hello World!', 'ALERT');

/* uses console.log */
util.echo('Hello World!', 'LOG');

/* uses uncaught exception */
util.echo('Hello World!', 'THROW');

/* injects the string into the DOM
via innerHTML. third argument is
the id of the element to write to */
util.echo('Hello World!', 'DOM', 'myID');

One important detail in the function above is that it provides a way to reference the built-in function. Whatever extra functionality you provide, there should always be a way to get to the built-in function in the way it normally behaves. Otherwise, the custom function you’ve written is just a custom function.

The case statement provides a default clause, which allows you to just have the string as the argument. This duplicates code, but allows you to call each state of the function explicitly, and have one ‘lazy’ mode where you just pass a string. Whatever mode you use util.echo the most for can be set to the default (for instance, if your debugging style is to write to the console, setting that as the default state saves you from passing the second argument).

You can also quickly add different ‘modes’ by adding more clauses in the switch statement, without substantially changing the behavior of the function, breaking it for developers already using it. So, if you already have a custom-built logger, you could add a ‘LOGGER’ mode to write to your logging object.

Another enhancement would be to have the first argument accept both strings and JSON fragments. You could then pass in an arbitrary amount of HTML elements or other Javascript arrays\objects as the destination point of the string. You wouldn’t need the third argument, at that point.

It looks like an awful amount of code, but can be crushed down with a minimizer to almost nothing.

Wrapper functions are just one simple design pattern. They won’t write your application for you. They are helpful when you need to encapsulate behavior that you think may change in the future. Like any other design pattern, overuse will hinder more than help.

Advertisements
No comments yet

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: