Configuring your JavaScript

Let’s assume that you’ve written a stand-alone script that you want to release to the public. You’ve run it through JSLint, minified it, checked for memory leaks, global variable flooding, tested it thoroughly and you’re happy that it works as intended. Now let’s assume that that you’d like an easy way for the user to configure this script, allowing them to make it fit perfectly onto their web page. In this blog post, I’ll show you 4 ways that you can do this and present the pros and cons of each method. Those 4 ways are:

  • A configuration object
  • A global variable
  • A query string
  • A custom attribute

Each method has its own pros and cons and may not be a sensible solution everytime. I’d recommend taking a good look at the cons that I point out and selecting the one that will work best in your situation, although there is no reason you couldn’t use them all.

Configuration Object

This one is very simple, you create an object literal at the top of your script and put your settings in there. Generally speaking, this is a good idea anyway since it can make finding your script settings very easy and anyone who sees it will instantly understand what it’s for.

Your script would start something like this:

(function(win, doc, undefined) {
    var configuration = {
        elemClass: "lorem", // The class name that will be assigned to the element
        fadeTime: 500 // How long the fade will take (miliseconds)
    };
// the rest of the script...

Because of the way that JavaScript works, minifiers can’t shrink property names (it even says so right here) so when your script gets minified, the properties will still be human-readable. The script above will end up looking something like this:

(function(a,b,c){var d={elemClass:"lorem",fadeTime:500}; // the rest of the script...

Although you’ve lost your helpful comments, if you named your properties well they’re still understandable. For example, it wouldn’t take a coding genius to work out that setting d.fadeTime to 1000 will make a fading animation take longer.

The obvious down-side of only relying on this technique is that whoever wishes to tweak your script for their page will need to open the script up to modify it. While this may not be a big issue for you and I, there are a lot of learners out there who will simply add the script to their site; do you remember how scary a minified script looked to you the first time you saw one?

Every other technique in this article will not require the user to modify your script itself but if your script has this configuration object, expanding it will be a lot easier.

Global variable

This one is easy for any newcomer to understand and modify. By loading your script after a pre-defined global variable, the user can configure it to their heart’s content.

<script type="text/javascript">
var scriptSetup  {
    elemClass: "lorem",
    fadeTime: 500
};
</script>
<script type="text/javascript" src="/path/to/yourScript.js"></script>

All your script needs to do is check to see if that variable exists and use it if it does. If this is combined with the configuration object mentioned above then we can create a simple function to read that variable and overwrite any default configuration. This would mean that the user would not have to define everything, only the parts they wanted to change. The function to do this is tiny:

// Simply loop through the properties of the new object and assign them to the base object.
function expand(baseObj, newObj) {
    for ( var key in newObj ) {
        if ( newObj.hasOwnProperty(key) ) {
            baseObj[key] = newObj[key];
        }
    }
}

// If the user has defined a scriptSetup, use their settings to overwrite our configuration.
if ( typeof(scriptSetup) !== undefined ) {
    expand(configuration, scriptSetup);
}

The expand function is a massively simplified version of jQuery’s extend method and if your script uses jQuery, I’d strongly recommend using that method instead of the one I’ve just shown you.

If you like the idea of this method, but you don’t like the fact that we now need 2 script tags, John Resig (lead programmer of jQuery) wrote a script that degrades script tags allowing the example a little higher up to look like this:

<script type="text/javascript" src="/path/to/yourScript.js">
var scriptSetup  {
    elemClass: "lorem",
    fadeTime: 500
};
</script>

There is a down-side to this method, the same down-side that we always hit with global variables: they’re global. If at any other time another user (or even the same user) creates another variable called “scriptSetup”, the second one will overwrite the first and the user may not get the result they were expecting (I think the degraded example might not suffer from this problem). To get around that, you’d have to think up a variable name that is very unlikely to be used by someone else.

Query string

The most popular example of this that I can remember is script.aculo.us. The principle is very simple and remarkably graceful. When adding a script to the page, put a question mark at the end and pass it a query string. Your main script would need to handle this query but it’s obvious to anyone that changing those will affect the script. The script.aculo.us example looks like this:

<script src="scriptaculous.js?load=effects,dragdrop" type="text/javascript"></script>

As you can see, this is a lot more graceful than the global variable since no other script will have access to the query string. You don’t need to imagine a complicated variable name, “class” and “fade” will be fine. Using this method, you script may looks a little like this:

>script type="text/javascript" src="/path/to/yourScript.js?class=lorem;fade=500"></script>

This technique is time-honoured and the article that first introduced me to thinking about it is on elektrum.org. That article explains how to make your script read the query strings so I won’t re-write it here. There is a potential downside that the article does mention that I will repeat here; the potential performance hit.

To your browser, the following three scripts have different locations:

<script type="text/javascript" src="/path/to/yourScript.js?class=lorem;fade=500"></script>
<script type="text/javascript" src="/path/to/yourScript.js?fade=500;class=lorem"></script>
<script type="text/javascript" src="/path/to/yourScript.js?class=ipsum;fade=1000"></script>

This means that yourScript.js will have to be re-downloaded each time which can be a problem if the scripts are big. If the user is only likely to chose a class and keep that consistent throughout their website (or at least, consistent throughout the pages that use this script) then the browser will retrieve the cached version each time and the performance hit vanishes. Depending on why the user will configure your script, this could be a show-stopper.

Custom Attribute

This is something I’ve only seen used by the Dojo Toolkit but I think is a wonderful way to customise a script differently across a variety of pages without the performance hit of the query string. Using this technique, your script would look like this:

<script type="text/javascript" src="/path/to/yourScript.js" scriptSetup="class: lorem, fade: 500"></script>

The code necessary to read that attribute is very straight forward. JavaScript has had the ability to get the contents of attributes for a while, all we need to know if which script tag to look at. if you’ve been reading some of the external links, you’ll know by now that as the DOM loads and script tags are found, they’re evaluated before the page continues. This means that if you count the script tags at that point, your script will always be the last one in that nodeList.

function getSetup() {

        // All script tags on the page at the moment.
    var allScripts = document.getElementsByTagName('script'),
        // This script tag.
        thisScript = allScripts[ allScripts.length - 1 ],
        // The contents of our custom "scriptSetup" attribute.
        setupAttribute = thisScript.getAttribute('scriptSetup'),
        // Each of the configuration options.
        keyValPairs = [],
        // If we find our custom "scriptSetup" attribute, these will be used to
        // loop through the options.
        i, parts;

    // If our custom attribute wasn't found, setupAttribute will be empty.
    if (setupAttribute) {
        // Remove all the spaces. split() and join() is used here for the
        // performance advantages over replace(). For more information, visit
        // http://www.adequatelygood.com/2009/11/JS-Find-and-Replace-with-SplitJoin
        setupAttribute = setupAttribute.split(' ').join('');
        // Split the setup options.
        keyValPairs = setupAttribute.split(',');
        i = keyValPairs.length;

        // Loop through the options and use them to overwrite our configuration.
        while (i--) {
            parts = keyValPairs[i].split(':');
            configuration[ parts[0] ] = parts[1];
        }

    }
}

All your script has to do is call that function and the configuration will be set. The only downside to this technique that I can see is that it requires a custom attribute which means that the user’s page won’t validate against W3C standards. If this user is adding this script to their page using a CMS that doesn’t allow them this freedom, they won’t be able to configure your script. At least one of these appears to be a genuine concern as even the Dojo Toolkit allows the user to use the global variable method instead.

Conclusion

Allowing the user to customise you script without having to trawl through your code is always a popular move, especially if your script relies on a certain element or ID existing. It gives users who aren’t competent coders the opportunity to tweak your script while leaving you the control over how they customise it.

If you happen to come across any other ways to configure your JavaScript, I’d be fascinated to see them.

All the scripts that I have on this page are free to use. I’m pretty sure the same is true of the external links, but be sure to check before using them.

Leave a Reply

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