Skip to content

Configure — a Javascript utility

December 14, 2009

The back-story

Configure grew out of a need I had on a project almost two years ago. We needed a way to have blog entries have two separate templates depending on the contents of the entry. Unfortunately, I haven’t yet found a way to do this in most blogging packages. You can only have one template per entry. As a sidenote: I know that most blogging packages will let you enter as many ‘alternate’ templates (for mobile, feeds, etc) as you want, but we needed to have the blog publish all the web-based entries with multiple templates, and that can’t be done, as far as I can tell.

What I ended up with was creating one template with both designs coded into it, and used a javascript variable to hide one and show the other. The javascript variable was entered into the blog entry itself. This proved to be very clumsy, as the blog was set up for content specialists who had little to no prior experience with scripting. One missing quotation mark, and I was in debug mode.

A further sidenote: the blogging package was upgraded this year, and now has support for custom fields, so if I was to do this project over, I would still need to use Javascript, but the end-user would just click a checkbox. Ahhh, progress…

This isn’t a rant about unskilled end-users. The end-users were very intelligent, skilled journalists who ended up creating an award-winning site (I only wrote the shell, the real star was the content). I have no complaints about them. The project, unfortunately, was tied to a deadline in the real-world (journalists can’t slow down or stop real-world events), so I had to live with what I could give in the time frame that I had.

But it did get me thinking about the possibility of creating a small set of functions that could read configuration data from a web page that could be entered by folk with almost no training in web development. Here’s what I came up with.

Introducing configure

configure.js is a small (just under 3K!) library designed to retrieve configuration options from HTML elements within a page. It looks for any element with a class of ‘dataconfig’ and pulls out the text inside. It splits on whitespace. These options are put into an array that can be queried by custom functions written by the developer.

For instance, a div with a class=”dataconfig” can contain the words ‘option1’, ‘option2’, ‘printonlymoduleA’, etc. A function can be written to query if ‘option1’ is in the div; if so, the function can trigger an event, hide an HTML element, etc.

Any amount of dataconfig elements can appear on the page, and will be collected into the array configure.js returns. configure.js provides an empty object, configure.execute, for functions that use the array configure.js returns, avoiding namespace collisions. Passing ‘debug’ as an argument into configure.js will append a div to the HTML document to let developers know what is in the array.

Configure.js converts all strings to lower-case, so ‘option1’ and ‘Option1’ will be identical within the array.

Why it is the way it is

Configure started out as a pure DOM implementation, but I quickly abandoned that. I found it much easier to grab the entire contents of each ‘dataconfig’ element and perform regex matching than to walk the DOM. From a performance perspective, I imagine that either are roughly equivalent (although I haven’t measured this). The regex implementation has the advantage of being very compact, and easily extendable.

One of the key design constraints is that a user could put pretty much anything into the element and, as long as there’s a complete word that matches what the web developer is looking for, the intended effect will happen.

One of the outgrowths of that design decision was to allow users the ability to enter extended characters, punctuation, HTML…practically anything they want to. As long as it can be split on whitespace, it can be put into the configure array. The array itself will filter out what it doesn’t want. It’s designed the way HTML is designed, to be liberal in accepting input, and filtering what isn’t needed, allowing the user to trip occasionally, but not fall over on trivial syntax issues.

I chose to limit the options to a single word to keep configure small and easy for the end-users who will populate the dataconfig elements. Creating a mini-language may have been better, but it would force the users to learn an additional layer of syntax, which is what I’m trying to avoid.

Using configure.js

Initialize configure.js

You can do this with an onload event function, or simply with the onload attribute of the body element:


<body onload="configure.init();">

Passing ‘debug’ as an argument will append a <div> element to the end of the document with a list of all items in the array:


<body onload="configure.init( 'debug' );">

Add a dataconfig element to the page:


<div class="dataconfig"><!-- option1 option2 option3 --></div>

<ol class="dataconfig"><li>list element example</li></ol>

<script class="dataconfig"><!--
//script element example
--></script>

As long as the dataconfig class is in place, the element doesn’t matter. This allows a lot of flexibility to incorporate configure into your projects.

Run your function

You can call your function whatever you like, but configure provides two convenience objects for you. The first, configure.execute, is a placeholder in which you can place your functions. This allows you to sidestep namespace collisions. The second, configure.get, queries the array that configure uses to store the values retrieved from the dataconfig elements. You can always query the array directly, but configure.get reduces the code needed.

configure.execute.setUp = function() {
	if ( configure.get( 'option1' ) ) {
		makeSomeChanges();
	}
};

The complete source

/* Configure: a library for pulling config data out of HTML elements. */
var configure = {

	options : [], /* array to hold values collected */

	init : function( debug ) {
		var configString = '';
		var configElements = configure.getElementsByClass( 'dataconfig' );

		if ( configElements == '' ) { return; }

		var comments = /(<!-{2,})|(-{2,}>)/g; /* strip comments */
		var tags = /<\/?[^>]+>/gi; /* strip tags */
		var space = /[\s]+/g; /* normalize whitespace */
		var trim = /^\s+|\s+$/g; /* trim leading/trailing space */

		for ( var i = 0; i < configElements.length; i++ ) {
			var configNodes = configElements[i].innerHTML;
			configString += configNodes + ' ';
		}

		configString = configString.toLowerCase();
		configString = configString.replace( comments, ' ' ).replace( tags, ' ' );
		configString = configString.replace( space, ' ' ).replace( trim, '' );

		var needles = configString.split( ' ' );

		for ( var i = 0; i < needles.length; i++ ) {
			configure.options.push( needles[i] );
		}

		if ( debug == 'debug' ) {
			configure.debug( configString );
		}
	},

	get: function( value ) {
		for ( i = 0; i < configure.options.length; i++ ) {
			if ( configure.options[i] == value ) {
				return true;
			}
		}
	},

	execute : {}, /* Placeholder for functions that use input */

	/* get elements by class value */
	getElementsByClass : function( theClass ) {
	  var elementArray = [];
	
	  if ( typeof document.all != "undefined" ) {
		 elementArray = document.all;
	  } else {
		 elementArray = document.getElementsByTagName( "*" );
	  }
	
	  var matchedArray = [];
	  var pattern = new RegExp("(^| )" + theClass + "( |$)");
	
	  for ( i = 0; i < elementArray.length; i++ ) {
		 if ( pattern.test( elementArray[i].className ) ) {
			matchedArray[ matchedArray.length ] = elementArray[i];
		 }
	  }
	  return matchedArray;
	},

	debug : function( theString ) {
		var theLog = document.createElement( 'code' );
		theLog.setAttribute( 'id','configLog' );
		document.body.appendChild( theLog );

		theLog.innerHTML = '<p><b>configString:</b> ' + theString + '</p>';
		for ( var i = 0; i < configure.options.length; i++ ) {
			theLog.innerHTML += "value " + i + ": '" + configure.options[i] + "'<br />";
		}
	}

};

Ways to reduce the javascript

Obviously, pushing configure.js through a minimizer will help. In general, the usual ‘script kiddie’ tricks (placing an if statement on a single line, reducing variable declarations to a single character, etc) will not compact the code any more than it already is, and make it more difficult to read when you experience a problem or extend the functionality.

However, if you really need more bang for the buck, eliminating the configure.debug function will save you almost half a kilobyte. You can also remove configure.getElementsByClass if you have a library that already provides something similar. Eliminating configure.get will save a little more, but you will always have to sift through the configure.options array manually in any function you write to access it, so it isn’t recommended.

Hope you can use it

If you got this far, thanks. If you find bugs, let me know.

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: