JavaScript in the Design Framework

General principles

  • Easy for developers to implement.
  • Easy to upgrade.
  • Minimal initial markup.
  • Progressively enhance the non-JavaScript experience.
  • Easy to use on either a standard web page or single page app (SPA).

Keep the markup and JavaScript separate

What we do is use JS to attach behaviours to the markup.
This keeps the JS totally separate from the markup. This makes it easier to maintain: we don't need to remember to change the JavaScript in lots of places in the markup (which isn't controlled by the Design Framework), but we can just change it in one file (which is).
So, we could completely replace the JavaScript for a particular widget in a future version of the Design Framework and the developers wouldn't have to change a thing in the markup.
See http://blog.teamtreehouse.com/unobtrusive-javascript-important for a good explanation.

Use the revealing module pattern

There are many ways to organise JavaScript code. This is the pattern we've ended up using.
Read more about it here:

The main advantage is that all of our functions and variables can be "namespaced" so they don't clash with other functions and variables. It can also be clearer to see which functions we've made "public" so that other developers can call them.

Why is using a module pattern so important?

JavaScript functions and variables are global by default, which means anything can change them at any time.
This can have unfortunate consequences.
For example, open a browser console and type the following:

alert('hello')

and press return. A nice little popup should appear.
Now, what if some bit of JavaScript somewhere wanted to define an alert message and store it in a variable. It could do this:

alert = "My lovely little alert message."

That would completely overwrite the alert() function we used before. Try typing that code into the console and then try running alert('hello') again. The error message will tell us that alert() is no longer a function, which is entirely true: we've just made it a string of text.

So, we need to be careful. A module pattern helps us wrap up our functions and variables so they don't overwrite other bits of code.

Use an init() function to set things up

This is what happens when a standard page using the Design Framework loads (things are a bit different for an SPA, which we'll come to in a bit).

  1. first, base.js is loaded. This contains only a little code, for font-loading and for a thing called the messaging bus.
  2. then vendor.js is loaded. This contains jQuery, which, for now, some parts of the Design Framework need, as do some of the sites that use it. We stop jQuery from firing its "ready" event, though, as we want to do that later.
  3. eventually, full.js is loaded. This contains all of the Design Framework modules, for things like accordions and tabs. The code looks inside of the modules for an init() function and runs that, which does things like preparing accordions and tabs so they're ready to be used.
  4. custom JavaScript for the site is loaded, which may depend on jQuery.
  5. finally, ready.js is loaded. This fires the jQuery "ready" event, which some bits of custom code need to run, and also a Design Framework "ready" event.

What should go in an init() function?

  • Look for anything we need to attach some JavaScript behaviour to
  • Add or change any markup we need to
  • Set any variables

A simple example:

/**
 * Initialise.
 */
  function init () {
    checkForAdverts()
    updateBodyAttributes()
  }

What happens in a single page app?

With a single page app, we don't have all the HTML on the page to start with, so there's no point in calling all the init() functions once. Instead, developers have to call the appropriate init() when adding something to the page. For example, they might add an accordion and there need to call the accordion init().

Allow a context to be set

By setting a context we can limit where our code looks for things to act on. We don't always need to do this, but often it's useful.
For example, when adding accordions to an already-loaded page, the developer may want to initialise only the new accordions in a specific area of the page. By passing a context, they can do this.

/**
 * Initialise.
 */
function init (context) {
  context = context || document
  doThatAccordionSetupThing(context)
}

In this example, the line context = context || document sets document as the default context if the developer hasn't specified something.

Clear things down with a destroy() function

There is not often a need for this, but it's something that needs to be considered: do we need to clean up after ourselves?

Use plain JavaScript

Because the JavaScript in the Design Framework is used in all sorts of applications, it makes sense to keep it as lightweight as possible. While jQuery makes some things easier, we don't need lots of its browser-support and most of the code we write is very simple stuff. Removing a need for jQuery means less code needs to be downloaded.

Format your code using standardjs

Like lots of other folks we use JavaScript Standard Style for our code formatting. There's details in the Design Framework README on how to set it up.

Using this standard style makes it easier to review code and understand what other people have written.

Comment code using JSDoc

 /**
  * A stand-in for forEach until such time we can use it.
  * @public
  * @param {array} a - the array.
  * @param {function} fn - the callback.
  * @return {object} context - the context or scope.
 */