How I like my Javascript…

Organized and contained…

My beef with JavaScript is that it works (sometimes) no matter how ugly the code is behind the screen. I’ve set out to organize my JavaScript to meet these requirements:

  1. Use a simple MVC concept - With this, I consider my HTML page to be the view, and the JS files to be the Models/Controllers (traditionally only controllers)
  2. Only run the script that is needed on the page - why should script be loaded if it isn’t being used?
  3. Organized and easy to read  - who wouldn’t want this?
  4. Limit the <script> tags to 2 - I’m a freak who likes to keep his html files as clean as possible

JS MVC? Of course, well, sorta…

Since I regularly use jQuery for my scripts, I am going to explain this as if I am using jQuery, but these scripts can easily be translated to a non-jQuery environment.

I create a single loading script, aptly called initiate_scripts.js, in a “js” folder.

Within that folder, I create another folder called “controllers”. The “controllers” folder is where I put my code and third party script files.

First I create a globals namespace -

var GLOBALS = {
    initiated = false, // My document.ready check
    site_url: ‘http://localhost/’, // Include trailing slash    controller_loc: ’js/controllers/’
}

Then my initiate function, not namespaced, accounting for if I want to load JS files locally, absolutely, or from an external source -

function initiate(jsFile) {
   if( jsFile.indexOf(‘/’) < 0 ) {
    jsFile = GLOBALS.site_url + GLOBALS.controller_loc + jsFile;
  }

  document.write(‘<script type=”text/javascript” src=”’+ jsFile + ‘”></scr’ + ‘ipt>’); 
}

Now I call my controllers -

initiate(‘form_controller.js’);
initiate(‘capture_controller.js’);

And my repository files -

initiate(‘https://code.google.com/apis/gears/gears_init.js’);

And my plugins/third party

initiate(‘third_party_scripts.js’);

As you can see, I am loading each file dynamically… None of the files should self-execute (I’m covering that in the next section). This is merely to keep my <script> area clean in my html. It’s also easy to add another .js by calling the initiate() function in this script file. You want to make sure you are calling jQuery first before initiate_scripts.js in your HTML file.

In each controller, I organize the code as follows -

VAR CONTROLLERNAME_CONTROLLER = {
  variable_one: ”,
  variable_two: ”,
  initiate: function() {
    …
    // Put the execute code here
    …
  }, //initiate()
  anotherfunction: function(){
    …
    …
    …
  } //anotherfunction()

Referred to as Namespacing, this allows me to organize everything into one controller. If I needed to validate a form, I would put everything needed for that validation in a file called - form_controller.js and name it var FORM_CONTROLLER = {}

Why am I yelling? That’s a common best practice for Namespacing: capitalize the controller object name.

Now I know what some of you are thinking, “This isn’t MVC?”… I know, but for the rest of this post I will still use the terms controllers and methods, I like it that way. There are other options out there, some that require an extra .js file. However, this is a nifty little way to achieve the concept of MVC without much effort. Where do the models come to play? I’ll typically put all of my “ajax” calls in a separate file, namespaced after the controller (i.e. FORM_MODEL), but I just don’t cover that in detail in this post.

Wide load! Why load?

This one was easy. I will also explain why I have an “initiated” variable.

We’re still in initiate_scripts.js, now let’s prepare our document ready call -

jQuery(document).ready(function($) {

});

Now in the ready function, we’re going to make sure it hasn’t been called before -

if(!GLOBALS.initiated) {
  GLOBALS.initiated = true;

};

Why do I check to see if it’s already been initiated? It was an issue I ran into a while back using Facebook Connect. When initiating FB’s Connect via JS I managed to trigger jQuery’s document ready function twice, this caused much heartache (double event handlers etc). So it’s a simple few lines of code that won’t hurt anyone, and is a backup in case it happens to me again.

Now the fun part, initiate each controller as the page needs. If you need to run a your form validator, why initiate it unless you need it?

if( ($(‘#myform’).length > 0) ){
  FORM_CONTROLLER.initiate();
}

I dynamically put classes on my <body> tag using PHP, this helps me easily target the home page -

if( $(‘body’).attr(‘class’).indexOf(‘home’) >= 0 ){
  BANNER_CONTROLLER.initiate();
}

Conclusion

Using these methods I’ve been able to create a very clean, easy to readJavaScript environment. My file structure keeps all .js files in one area:

/js
   /controllers
      /form_controller.js
      /plugins.js
   /initiate_scripts.js

Namespacing keeps everything out of the global space (with only a few exceptions). My “GLOBALS” namespace allows me to have global variables accessible to each controller via GLOBALS.global_var. While JS files are loaded, they are not executed, with internet speeds these days I have no problem having the user download the file, but this lifts some of the number crunching the local browser might have to do on JS heavy sites.

Lazy Shortcuts and tricks

This might not be best practice, but I use a few lazy shortcuts to make my typing go a little faster.

In the local method variable call line I use this shortcut - 

var vars = FORM_CONTROLLER, func = vars;

Now when I want to access a controller wide variable or sibling method I call them via vars.variable or func.internal_function() respectively instead of FORM_CONTROLLER.variable and FORM_CONTROLLER.internal_function(). “vars” and “func” are the same, but I use them separately to help me visually separate them.

Notice I said variable line. All of my variables are called in one JS line like so:

var vars = FORM_CONTROLLER, func = vars,
var1,
var2,
var3 = “value”;

I multiline them for readability at first (notice the commas, making JS read them as one line), then at launch I crunch them down. Repeating “var” throughout is a waste of characters.

At the end of each method I always comment the name of the function and variables/types expected -

internal_function: function(var1, var2, var3) {
  …
  …
  …

}, // internal_function(var1 = int, var2 = str, var3 = array)

You’ll notice in my document.write function that my end </script> tag is broken

document.write(‘<script type=”text/javascript” src=”’+ jsFile + ‘”></scr’ + ‘ipt>’);

It’s important to do this, otherwise the browser will assume the end of the script and stop translating the rest of the code as JavaScript.

Thank you for reading, feel free to contact me with any comments about observations/suggestions you have or mistakes/errors you find.