SiteKickr Web Development

JavaScript Organization for web development

For those of you who have come across this post, and don't yet know about the object-oriented features of JavaScript, it will not carry much meaning. I used to be you, believing that JavaScript was only for validating web forms or making slick rollovers, and further-yet, that object-oriented programming is really only useful in game development or larger projects.

I don't have the talent to persuade you otherwise, but these two guys do:

With that being said, and assuming that if you're at this point in the post, you are familiar with OO development and JavaScript's OO features. You've probably authored a few JavaScript-based web apps, and are well aware of the challenges it presents when compared with traditional desktop OO development. Namely:

 

It's clear from a page load time perspective that condensing all of your classes into one file is the best choice. But having a few thousand lines of code in one file can make it difficult to locate the exact method you are looking for, as well as cause upload-time annoyances as the file grows larger and larger (if you are developing on a remote server).

To overcome these issues, you can:

  1. Do your JavaScript development on your local machine. JavaScript is interpreted inside your web browser, so it does not require a web server. 

    Another drawback for some is the same origin policy. If you are developing locally, browser security restrictions disallow you from making cross-domain requests. To get around this though, many browser have initialization flags that turn this security feature off. For example, you can start chrome with the –disable-web-security option.
     

  2. Use an IDE that recognizes JavaScript objects and organizes them into "classes, methods and properties" for you. I like Eclipse for this. The Project Explorer pane does a good job of classifying the objects, i.e.

    Although, I was a little disappointed to find that the project explorer doesn't catch on to this function definition syntax:

    myMethod = function() { }

    So, functions defined in this way, within a "class", don't show up in the explorer view.

 

If you need to develop on a remote server, and prefer to separate your JavaScript classes and packages into a folder/file hierarchy as is familiar to compiled languages, you're not outta luck. We just have to get creative!

In traditional OO languages, we are accustomed to included individual classes within packages on a "need-to-use" basis, i.e.

import mypackage.myclass

In JavaScript, we're kinda stuck with the <script> tag, which is really only useful for loading in an entire JavaScript source file. That source file might contain 5 classes or 50 classes, all of which we must load, regardless of which ones we'll actually use. Because of this limitation, we typically include all of our classes in one big file. It doesn't have to be this way!

We can extend JavaScript a bit, including just a few lines of code to emulate the package/class include methods found in our traditional languages. This of course, requires a few capabilities:

As Nicholas Zakas points out, we can encapsulate the loading of an external JavaScript file into a function, and provide a callback to execute after the script has loaded, so our method call might look like:

 

loadScript("/mypackage/myclass.js", function(){
    //initialization code
});

Now, if we alter his script a bit, to use the naming convention we suggested above, the loadScript function would be more like:

 

function loadScript(package_class, callback){
    var className = package_class.split('.').pop();
    if(eval('typeof ' + className) !== 'undefined'){
        callback();
    }

    var script = document.createElement("script")
    script.type = "text/javascript";

    if (script.readyState){  //IE
        script.onreadystatechange = function(){
            if (script.readyState == "loaded" ||
                    script.readyState == "complete"){
                script.onreadystatechange = null;
                callback();
            }
        };
    } else {  //Others
        script.onload = function(){
            callback();
        };
    }

    script.src = '/' + package_class.replace(/\./g, '/') + '.js';
    document.getElementsByTagName("head")[0].appendChild(script);
}

Essentially, we are replacing the dot notation with folder notation, so by calling this method:

loadScript('Mypackage.Myclass', function(){
    //initialization code
});

We are actually loading the /Mypackage/Myclass.js file. The Myclass.js file contains a Myclass function, which upholds our naming convention. Notice also, in the opening lines of the new loadScript method, we check to see if the class has already been loaded.

But, what if we want to load multiple classes, and have our callback fire only after all of the external scripts for those classes have been loaded? One possibility would be to create an array of script elements, load them all, then with each onload event, check to see if all scripts have been loaded:

 

function loadScripts(packages_classes, callback){
    var scripts = [], scriptCount = packages_classes.length;

    for(var i = 0; i < scriptCount; i++ {
      var className = package_class.split('.').pop();
      if(eval('typeof ' + className) !== 'undefined'){
          scripts[i] = {loaded: true};
      }
      else {
          scripts[i] = {loaded: false};
      }

      scripts[i].script = document.createElement("script")
      scripts[i].script.type = "text/javascript";

      if (scripts[i].script.readyState){  //IE
          scripts[i].script.onreadystatechange = function(){
              if (scripts[i].script.readyState == "loaded" ||
                      scripts[i].script.readyState == "complete"){
                  scripts[i].script.onreadystatechange = null;
                  scripts[i].loaded = true;
                  if(checkAllLoaded()) {
                    callback();
                  }
              }
          };
      } else {  //Others
          scripts[i].script.onload = function(){
              scripts[i].loaded = true;
              if(checkAllLoaded()) {
                callback();
              }
          };
      }

      scripts[i].script.src = '/' + package_class.replace(/\./g, '/') + '.js';
      document.getElementsByTagName("head")[0].appendChild(scripts[i].script);
  }

  // private function to check if all scripts (classes) have been loaded
  var checkAllLoaded = function() {
    for(var i = 0; i < scriptCount; i++ {
       if(!scripts[i].loaded) {
         return false;
       }
       else {
         return true;
       }
    }
  }
}

I have not tested the above code, at all! It's meant to be more theoretical. But, it would appear that loading multiple external files, and waiting for a callback is possible. So, a method like this, very clean and consise is actually possible in JavaScript!

 

loadScripts(['Mypackage.Myclass', 'Mypackage.Myclass2', 'Myotherpackage.Myotherclass'], function(){


  // tie it all together here!

});