Get your JavaScript in order
Mike Byrne provides the lowdown on a simpler way to structure your JavaScript – that should help prevent it fragmenting into messy, brain-bashing gobbledygook
In my last article, back in August 2011, I discussed a method I use to modularise CSS in a website build. In this follow up I’ll introduce a means of modularising your JavaScript to make it easier for you and your teams to develop and maintain. This isn’t the only way you can do this, but it’s a method that’s been working for me – and one you could also find useful when trying to organise your projects.
Recent browser wars have seemingly focused on JavaScript performance these past few years, and we’re using larger quantities of the stuff than ever before. We are moving into an era where we’ll be using more and more clientside scripting to handle what have traditionally been seen as server-side things, such as using JS template engines to render HTML. With this in mind, I’d say better organisation of your JavaScript is more important now than ever before.
Before we get going, I think its worth stating that I am no hardcore JavaScript programmer. I am a frontend developer with a design background, although I do seek to improve my JavaScript skill set. This solution was born from trying to make my life easier and so hopefully it can do the same for you.
My old approach
Over the last few years of increasingly using more and more JavaScript on websites, I have been through a fair number of methods for organising my JavaScript, and I was never totally happy with any of them.
Common methods I’d use included having one main application.js, a bunch of plug-in JavaScript files, a JavaScript library file (usually jQuery) and then, in a slightly desperate effort to make things easier to read, some functions would often end up splitting off into different JavaScript files. This often left a messy, confusing-looking JavaScript folder.
This never really seemed to be a problem while it was all in my memory. But six months later, when I’d have to try to recall what I was thinking at the time of development, it would be a much harder task.
Often each function would have some code that was there to check whether some elements existed or not in order to decide whether to run the rest of the function. Mostly these functions would then be looking for an event – usually click. So, something like:
if ($("#features").length > 0) { $("#features a").click(function(e){ // do something });}
Initially I had JavaScript written on an as-needed basis inside a DOM ready function. This very quickly became unreadable, and I remember one JavaScript file being thousands of lines long and dreading having to look at it.
When I realised that this system was proving to be a little unwieldy, I then started to have all the functions I would need outside the DOM ready and just have calls to them in the DOM ready. I thought this would help, but it only created some long files, and made all my variables and functions public.
This was fine until I started doing work for AOL, which required all JavaScript to be in namespaced objects. This subsequently led me to reading up about what they were and how to work with them. Having to learn about objects, properties and methods in this way was the impetus to try to step up my JavaScript game.
I grew to like the namespaced object method. Every function inside of it was a private function, except for a few returned public methods and a DOM ready inside it to kick off an init() function. This coincided with an effort I was making to reduce the amount of JavaScript I was writing and rely more on better HTML, newer CSS methods and simpler interactions. I came to recognise that I was making my interactions way too complex, simply because I could.
As browsers got better at handling JavaScript and the demands of the websites I was building increased, I began writing increasing amounts of JavaScript again. This was when the problem of working with one large JavaScript file showed up again. Worse, I often found that any other developers I worked with simply reverted to putting globally available functions outside of my nice namespaced object because it was simpler and easier for them. And that made my pretty JavaScript files ugly.
When you read this account of how I was organising my JavaScript you can probably draw some parallels with how you may have organised your JavaScript. Generally these application.js files were growing and becoming increasingly difficult to read and maintain.
My current approach
Handily, while working with a team of developers at Pivotal Labs in NYC I saw a new and much better method. Although it might appear that I’m name dropping, I mention Pivotal Labs to acknowledge where I saw this method. I’ve stolen it. (Well, you can take the man out of Manchester, but not the Manchester out of the man.) At least I’m doing the honourable thing and sharing it with you.
The result of this method is a much cleaner and more logical-looking JavaScript directory. All the JavaScript is now split up into different files in a logical folder structure for functions, plug-ins, templates and vendorsupplied JavaScript. You may come up with a more logical structure for your build yourself, but this is how I’m structuring it for now:
- Behaviours are bits of DOM interactions the site needs.
- Templates is a place to put templates for JavaScript rendering engines (Mustache, Dust and so on).
- Plug-ins is where I put any JavaScript plug-ins that I have written for the application.
- Vendor is for keeping library files – here jQuery and any community plug-ins that I won’t be updating.
Then, instead of a DOM ready event triggering functions that check for elements on a page and doing something if it finds them, I’m using a different method of wiring everything up. What I’m actually using is an attribute on the elements that need functions, and then looking for that attribute to trigger a function. Here is how I’m doing this:
Get the Creative Bloq Newsletter
Daily design news, reviews, how-tos and more, as picked by the editors.
The HTML
DOM elements that have JS functionality associated with them are getting a data-behavior attribute, with a name of a method. In this case it is slider:
<section id="features" data-behavior="slider">...</section>
Behaviour JavaScript files
The JavaScript for this method lives in /js/behaviors/directory. I give the filename the same title as the behaviour name in the HTML. This keeps tracking it down and keeping tabs on individual elements much simpler as the process goes on. This is the point on static builds when my amnesia gets the better of me, and I usually forget to update the HTML to include this new JavaScript file and then wonder why it isn’t working. But in my Rails 3 applications it’s done automagically, though you may need to update the included assets in your applications. This can prove incredibly helpful to absent-minded types like me.
This new JavaScript file is where I write whatever JavaScript that I want to have interacting with the DOM element, which in this case looks like this:
DLN.Behaviors.slider = function(container){ container.slider({ sliderInner: container.find(".features_list"), slideAmount: 990, itemsVisible: 1, currentSet: 1, budge: 0, looping: true, quickLinks: false, speed: 250, selfCentering: false, paginatorClassname: "features_paginator", keyControls: true, onMoveFunction: false });};
This behaviour triggers a plug-in, also named slider, which lives in the /js/plugins/ directory and passes some options into it, where container is the element from the DOM that had the data-behavior.
Application JavaScript
In order to call these methods and pass the container element into them, we’ll first of all need to construct the objects and make new instances of them. For this, we’ll take a look at my application.js.
var DLN = window.DLN || {};DLN.Behaviors = {};DLN.LoadBehavior = function(context){ if(context === undefined){ context = $(document);}context.find("*[data-behavior]").each(function(){ var that = $(this); var behaviors = that.attr('data-behavior'); $.each(behaviors.split(" "), function(index,behaviorName){ try { var BehaviorClass = DLN.Behaviors[behaviorName]; var initializedBehavior = new BehaviorClass(that); } catch(e){ // No Operation } }); });};DLN.onReady = function(){ DLN.LoadBehavior();};$(document).ready(function(){ DLN.onReady();});
So what is happening in this application.js? Let’s explore what I have been doing in a little closer detail. For this structure, we need a namespaced object to store our methods in. So I set up an object, DLN, and a Behaviors object within it by using some literal notation:
var DLN = window.DLN || {};};DLN.Behaviors = {};
DLN is simply an initialism of the project name, so you don’t have to use it. Just change this to whatever suits your job.
New objects inside of the namespaced object can be added – maybe for templates, helper functions or whatever – in the future, depending on what your project needs.
On DOM ready the JavaScript runs a method named onReady, which in turn, runs the LoadBehavior method:
DLN.onReady = function(){ DLN.LoadBehavior();};$(document).ready(function(){ DLN.onReady();});
LoadBehavior sets about looking through the DOM for any element with an attribute of data-behavior:
context.find("*[data-behavior]").each(function(){ ... }
When it finds an element with this attribute it first stores the element in question into a variable, which in turn stores a string of the content of the data-behavior attribute:
var that = $(this);var behaviors = that.attr('data-behavior');
Storing the element in the variable that allows the element to be passed into the target behaviour later, and is a common way to get around the change of scope, when it turns out that this is not what you want or expect it to be. We’ve all been there …
Using jQuery each, it loops through the behaviour names, split into an array with split(" "). This is useful so that one single DOM element can have multiple behaviours:
$.each(behaviors.split(" "), function(index,behaviorName){ ... }
To run the desired method, inside of a try-catch statement, we clone a dynamically selected behaviour into a variable (BehaviorClass) and then make an instance of it (initializedBehavior), passing through the element that had the data-behavior attribute (this becomes container in the behaviour JS files):
try { var BehaviorClass = DLN.Behaviors[behaviorName]; var initializedBehavior = new BehaviorClass(that);}
You may have observed that I have no catch on that try-catch statement – probably a rather naughty thing to have done. But this does provide a way of not generating errors if the appropriate behaviour method doesn’t exist.
This probably sounds more complex than following the code with your finger and simply working out what is going on. It is this stage where some familiarity with object-oriented JavaScript is useful. The Bearded Octo article ‘OO JS in 15 mins or Less’ is a fantastic resource for understanding writing object-orientated JavaScript.
Ajax
You may also note that there is a context parameter in the LoadBehavior function. This is to enable you to run LoadBehavior on elements you have ajaxed into place, and not simply only the elements present at DOM ready. You just need to pass through the element you want the to LoadBehavior function to search and it will set up behaviours on ajaxed content too. So for example:
DLN.LoadBehavior($("#ajaxed"));
Scope
The behaviours themselves, and any other objects inside the namespaced object you make, are available globally. So you can call one behaviour from another, or set globally available variables. In this example, to run the slider function from a different behaviour you could call:
DLN.Behaviors.slider($("#newElementNeedsSlider"));
Methods and properties inside the behaviour JS files are not available globally, unless you choose. To make them so you could define them as properties or methods of a behaviour object, so inside of a behaviour file you could define:
DLN.Behavior.slider.active = true;.
Then this would then be available from any other JavaScript on your site. Or, you may want to use globally accessible getter and setter functions, which you would set up a similar way. So a behaviour JavaScript could be:
DLN.Behaviors.slider = function(container) { var slide_amount = 13; DLN.Behaviors.slider.setSlideAmount = function(amount) { slide_amount = amount; } DLN.Behaviors.slider.getSlideAmount = function() { return slide_amount; } // ...}
Now you can globally update and retrieve a private variable to the method by:
DLN.Behaviors.slider.getSlideAmount();// 13DLN.Behaviors.slider.setSlideAmount(1312);DLN.Behaviors.slider.getSlideAmount();// 1312
Alternatively, to make a property or method globally available you could just add it to the top-level object:
DLN.version = "1.3.1.2";
or:
DLN.error = function(msg) { alert("error! "+msg);};
Just be careful you don’t make it too hard to follow what is defined where and end up making this simple method complex. If you are going to have a lot of site-wide variables and functions to store like this, consider making an object to put them in and a logically named folder of JavaScript files to contain them.
Production site
For development, lots of JS files with bits of code in are OK. But for production, concatenate and minify them into one file. Asset packers are a must, or you’ll have individual requests slowing your application’s load. Rails 3 has this built in, and similar asset packers are available for most application frameworks. A quick Google returns a wealth of advice on compressing JS, such as Rails 3.1’s Asset Pipeline, Minify and YUI Compressor for .NET.
Thanks to Ross Bruniges for his peer review of this tutorial
Find 35 top examples of Javascript at our sister site, Creative Bloq.
Thank you for reading 5 articles this month* Join now for unlimited access
Enjoy your first month for just £1 / $1 / €1
*Read 5 free articles per month without a subscription
Join now for unlimited access
Try first month for just £1 / $1 / €1
The Creative Bloq team is made up of a group of design fans, and has changed and evolved since Creative Bloq began back in 2012. The current website team consists of eight full-time members of staff: Editor Georgia Coggan, Deputy Editor Rosie Hilder, Ecommerce Editor Beren Neale, Senior News Editor Daniel Piper, Editor, Digital Art and 3D Ian Dean, Tech Reviews Editor Erlingur Einarsson and Ecommerce Writer Beth Nicholls and Staff Writer Natalie Fear, as well as a roster of freelancers from around the world. The 3D World and ImagineFX magazine teams also pitch in, ensuring that content from 3D World and ImagineFX is represented on Creative Bloq.