Code Structure Organization

It is hard to test tangled code, because of four things:

  1. A general lack of structure; almost everything happens in a $(document).ready() callback, and then in anonymous functions that can’t be tested because they aren’t exposed.
  2. Complex functions; if a function is more than 10 lines, like the submit handler, it’s highly likely that it’s doing too much.
  3. Hidden or shared state; for example, since pending is in a closure, there’s no way to test whether the pending state is set correctly.
  4. Tight coupling; for example, a $.ajax success handler shouldn’t need direct access to the DOM.

Take a less tangled approach to our code, breaking it up into a few different areas of responsibility

  • Presentation and interaction
  • Data management and persistence
  • Overall application state
  • Setup and glue code to make the piece work together

Our new code will follow a few guiding principles:

  • Represent each distinct piece of behavior as a separate object that falls into one of the four areas of responsibility and does't need to know about other object.
  • Support configurability, rather than hard-coding things. It will prevent us from replicating our entire HTML environement in order to write our tests.
  • Keep object's method simple and brief.
  • Use constructor functions to create instance of objects. This will make it possible to create "clean" copies of each piece of code for the stake of testing.

document is used as a global message bus, passing messages through it so individual components don't need to know each other.

  • Refactoring Ajax call
// original ajax call 
$.ajax('/data/search.json', {
  data : { q: query },
  dataType : 'json',
  success : function( data ) {
    loadTemplate('people-detailed.tmpl').then(function(t) {
      var tmpl = _.template( t );
      resultsList.html( tmpl({ people : data.results }) );
      pending = false;
    });
  }
});
// reconstructor the code 
var SearchData = function () { };

SearchData.prototype.fetch = function (query) {
  var dfd;

  if (!query) {
    dfd = $.Deferred();
    dfd.resolve([]);
    return dfd.promise();
  }

  return $.ajax( '/data/search.json', {
    data : { q: query },
    dataType : 'json'
  }).pipe(function( resp ) {
    return resp.results;
  });
};

// code can be simplified as 

var resultsList = new SearchResults('#results');

var searchData = new SearchData();

// ...

searchData.fetch(query).then(resultsList.setResults);
// the code will look like: 
$(function() {
  var pending = false;

  var searchForm = new SearchForm('#searchForm');
  var searchResults = new SearchResults('#results');
  var likes = new Likes('#liked');
  var searchData = new SearchData();

  $(document).on('search', function (event, query) {
    if (pending) { return; }

    pending = true;

    searchData.fetch(query).then(function (results) {
      searchResults.setResults(results);
      pending = false;
    });

    searchResults.pending();
  });

  $(document).on('like', function (evt, name) {
    likes.add(name);
  });
});

Libraries and Approaches

Module Pattern, Variations and Common Practices

In JavaScript application, the module pattern is a popular design pattern that encapsulates 'privacy', state and organization using closures. A module helps keep units of code cleanly separately and organized. It provides a way of wrapping a mix of public and private methods and variables, protecting pieces from leaking into the global scope and accidentally colliding with another developer's interface. No matter if you are using any Javascript framework or not, module pattern is one of the best practices in JavaScript development to organize and limit code scope in any project. Module pattern helps you to structure your code base more towards a Object-Oriented fashion as we are doing in C# or Java.

  • The JavaScript language doesn't have class, but we can emulate what classess can do with modules.

  • A module helps encapsulate data and function into a single component

  • A module limits scope so the variables you create in the module only live within it.

  • A module gives privacy by only allowing access to data and functions that the module wants to expose.

Basic Module Pattern Structure

The core of module pattern is based on IIEF (Immediate Invoked Function Expression). It is a anonymous function, execute immediately. All of the code run inside the function lives in a closure, which provide privacy and state through the life time of the application.

// All Private 
(function () {

  // Detail logic goes here 

}()); //calling this method.

The code snippet above hide all of your implementation from external code. The function runs once and only once.

Module Pattern and variations
  • Anonymous Object Literal

A version evolved from the simple IIEF is return a anonymous object literal that would expose privilege methods

var Module = ( function ( ) {
    // private ... 
    var privateVariable = "some priviate value",
        privateMethod = function ( ) {
            //do something.
    };   

  return {
         //the method inside the return object are 
         //called as privileged method because it has access 
         //to the private methods and variables of the module.
         privilegedMethod : function ( ) {
          //this method can access its private variable and method by using the principle of closure.  
          console.log("outputting " + privateVariable); //accessing private variable.
          privateMethod( ); //calling private method
        }
    };

})( );

By using the principle of closure, the privileged method can access its private variables and methods. The returned object literal will be added to Window (global) name space. It returns the only parts we needs, leaving the other code out of the global scope.

  • Locally scoped object literal

Another variation is to create and return an object containing all of the public members as its properties.

var Module = (function () {
  // locally scoped Object
  var myObject = {};

  // declared with 'var', must be "private"
  var privateMethod = function () {};

  myObject.someMethod = function () {
    // make it a public method
  };
  return myObject; // only the object send back, not the name

})();

Again, the private member inside the module is created using closure. The module is created by using anonymous function expression. Any declared function or variable will be private unless they are added as properties to the returned public object.

  • Revealing Module Pattern

Revealing module pattern is very common pattern, widely used JavaScript applications. Comparing with Locally scoped object literal version above, Revealing Module Pattern doesn't create a separate returned object and then adding properties to it. But instead, we define all the function private first, then decide which ones should be public by return those methods as properties of an anonymous object literal.

var Module = (function () {

  var privateMethod = function () {
    // private
  };
  var someMethod = function () {
    // public
  };
  var anotherMethod = function () {
    // public
  };  
  return {
    someMethod: someMethod,
    anotherMethod: anotherMethod
  };

})();

it is a very flexible variation of Module Pattern. It can better control the name of the public API. If it is required to change the name, all we need to do is to change the name from the returned object without any impact of the function name inside the module. Also, it controls what should be public by adding/removing the properties.

Common Practices
  • Passing External Library

Most of time, our module needs to reference other third party libraries, like jQuery or Kendo. JQuery uses '$' as shortcut to reference JQuery element, but '$' is not just used by jQuery. To avoild confilict, it is a common practices to pass '$' as a parameter to the Module.

var Module = (function ( $ ) {
  var privateMethod = function () {
    // private
  };

  var someMethod = function ( x ) {
    // use jQuery function/features 
    if ($.isArray(x)) {
          // do something 
    }
  };

  var anotherMethod = function () {
    // public
  };

  return {
    someMethod: someMethod,
    anotherMethod: anotherMethod
  };

})( jQuery );
  • Reference Other Modules

    Module can be referenced by other modules, which provides a compsite solution.

var Module01 = (function () {
      var sayHelloPrivate = function (name) {
          console.log("Hello, " + name);
      };

      return {
          sayHello: function (name) {
              sayHelloPrivate(name);
          }
      };
  }()); 

  var Module2 = (function ( m1 ) {
      return {
          startThingsOff: function (name) {
              m1.sayHello(name);
          }
      };
  }(Module01));
  • Extending Module Pattern (augmentation)

    Module can be extended or overwritten by other module as well.

    // extending and overwriting the module
    var Module1 = ( function (oldModule) {
        var 
            //assigning oldmodule in to a local variable.
           parent = oldModule;    
            //overriding the existing privileged method.
            parent.privilegedMethod = function ( ){
            //do something different by overriding the old method.
        };
    
        //private method accessing privileged method of parent module.
        var privateMethod2 = function ( ) {
            //can access privileged method of Module
            parent.privilegedMethod();
              //can access public method of Module
            parent.publicMethod1(); 
        }
        return {
            newMethod : function ( ) {
              ///do something for this brand new module.
              ///use some functionality of parent module.
              /// parent.privilegedMethod( );
            }
        };
    
    } )(Module);//Module object is the existing module that I want to extend.
    

  • Passing Name Space Parameter

    Besides other module or library can be passed to the module as argument, it is very common practice to take name space parameter to define name space for the module.

    (function( skillet, $, undefined ) { 
      //Private Property 
      var isHot = true;
    
      //Public Property
      skillet.ingredient = 'Bacon Strips';
    
      //Public Method
      skillet.fry = function() {
        var oliveOil;
    
        addItem( 'tn Butter nt' );
        addItem( oliveOil );
        console.log( 'Frying ' + skillet.ingredient );
      };
    
      //Private Method
      function addItem( item ) {
        if ( item !== undefined ) {
            console.log( 'Adding ' + $.trim(item) );
        }
      }
    
    }( window.skillet = window.skillet || {}, jQuery ));
    
  • Consideration of Multiple Instance

    If the module will be created multiple instance, we can apply prototype to save memory. Because, instead of each instance having a copy of the member, the single prototype member is shared. So we can return a constructor function from the module.

    // code from http://stackoverflow.com/questions/21909785/java-script-revealing-module-pattern-create-multiple-objects
    
    var Slider = (function( $ ){
    
        function slider(id) {  
            this.id = id; 
        }
    
        slider.prototype = {
            init: function(){
                this.pageLoad();
            },
            pageLoad: function(){
                 console.log('pageLoad called from instance with id', this.id);
            },
            getId: function(){
                return this.id; // <- 'this' refers to the current instance
            }
        };
    
        return slider;
    
    })(jQuery);
    
    var slider1 = new Slider(1);
    var slider2 = new Slider(12);
    
  • Organizing Modules with different Files

​ We already see how to extend the module previously. In some situation when the JavaScript module is overly complicate, We can apply this approach to separate the module into multiple files. You see, in the same below, I split the entire Module implementation into two separate files. If we understand how the module references to be extended, we will have no problem understand this code structure. In both files, it take Module as parameter to extend its functions.

// Module.js
var Module = (function($, pub){
       //jQuery will still be available via $
       var mem = new Array(); //private variable

       pub.publicMethod01 = function(val){
            mem.push(val);
        };

       pub.publicMethod02 = function(){
            return mem.pop();
       };     
       return pub;

})(jQuery, Module || {});

// Module-Additional.js
var Module = (function($, pub){

    pub.publicMethod03 = function(val){
        // do something 
    };

     pub.publicMethod04 = function(){
        // do something else 
     };     
     return pub;

})(jQuery, Module || {});

CommonJS

A module standard. Commonly used on the server (NodeJS). Each file is a (single) module, each module is a seperate file, A global exports variable is available that you can assign your exports to.

AMD (RequireJS)

  • require() function is used to run immediately
  • define() function is used to define modules which may be used from multiple locations.

PubSub

MVVM

JQuery Plugin

Reference

results matching ""

    No results matching ""