How to Load JavaScript Contents Dynamically

This post is related to my previous post in terms of performance of loading page. Compressing and Combining .js files is very good thing to do. But not all .js files are essential for page functionality in the very first seconds after site is loaded. Almost in all web applications we are facing that situation. Application has many users with different privileges and roles. Not all list of menu is accessible for every user and in most cases user will not open all menus in one session. So, it has no purpose to load all .js files for the first time, because most of them will not be executed at all. Let’s make our architecture so that a .js file was loaded only after demand on function that is defined there. To explain better I’ll give very clear example. For example I have a web application with menu items (Let’s call them applications or subapplications ):

  • Menu 1
  • Menu 2
  • Menu 3

After the first load of site it is enough to load just those contents that displays my menu. And after click on Menu 1 firstly, let’s download that .js files where are defined functionality of Menu 1 and secondly, execute the functions that creates and displays the layout of Menu 1. I think I’m clear what I want to do. To approach the goal we have to create a singleton class that handles application content loading staff. All .js content will be downloaded by this class except essential files for displaying initial layout of web application. And this is the class that handlers dynamic loading:

(function(){
  var appManager, loadScript, head, App;
  appManager = {};
  appManager.cache = {}; //App instances will be cached here
  /**
  * Loads JavaScript from specified URL in DOM
  * @param {string} src
  *     URL for JavaScript file
  * @param {function} callBack
  *     function that will be called after file downloading finishes.
  *     Usually callBack function will call one of the function that
  *     is defined in newly downloaded file.
  */
  loadScript = function(src, callBack){
    var script;
    //get and cache head of document
    head && (head = document.getElementsByTagName("head")[0]);
    script = document.createElement('script'); //create script tag
    script.type = 'text/javascript';
    //listen to moment when downloading finishes (for IE)
    script.onreadystatechange= function() {
      if (this.readyState == 'complete') callBack();
    };
    //listen to moment when downloading finishes (for others than IE)
    script.onload = callBack;
    script.src = src; //assing src property to script tag
    head.appendChild(script); //append to head to begin download.
  };
  /**
  * Application class that has properties:
  *  name - name of subapplication
  *  src - URL of application's .js file
  *  callBack - function that will be executed after file loads,
  *  status - weather application file is downloaded or not.
  */
  App = function(name, callBack){
    this.name = name;
    /**
    * .js file name is considered to be
    * dirName + "_min.js" in directory
    * named dirName.
    */
    this.src = name + "/" + name + "_min.js";
    this.callBack = callBack;
    this.status = 'instantiated';
    /**
    * Cache application because not to
    * create and download more than once
    * the same application.
    */
    appManager.cache[name] = this;
  };
  /**
  * Method of App class that downloads its
  * .js file and executes callback function
  */
  App.prototype.load = function(){
    var that;
    this.status = 'loading';
    that = this;
    loadFinished = function(){
      that.callBack();
      that.status = 'loaded';
    };
    loadScript(that.src, loadFinished);
  };
  /**
  * By this function will be loaded applications
  * from global scope.
  * @param {string} appName
  *       Name of application, typically name of application
  *       and the name of directory under which application
  *       .js files are.
  * @param {function} callBack
  *        Function that will be executed after application
  *        loads.
  */
  appManager.load = function(appName, callBack){
    var app;
    /**
    * Create an application or get it from cache if it was created
    * before.
    */
    app = this.cache[appName] || new App(appName, callBack);
    if(app.isLoaded()){
      /**
      * If application is taken from cache and its file is already
      * downloaded then directly execute callBack.
      */
      callBack();
    }else if(app.isLoading()){
      /**
      * If application is taken from cache and the process of
      * downloading is in progress do nothing.
      */
      return;
    }else{
      /**
      *  Application is created for the first time and load it.
      */
      app.load();
    }
  };
  //make appManager global.
  window.appManager = appManager;
})();

This code is useful for understanding an idea and to run demos. But in real application, when there are many changes at runtime, this class also needs some additional functionalities such as: dependences on applications, some special files to be downloaded except core file and so on…

Now we have got dynamically loaded JavaScript content architecture.

Advertisement