Applications: On-Demand Loading
Using AspectJS to Load Code as Needed and Transparently
T
he principles of good software-design hold that methods should do one thing alone, rather than conflate disparate functionality. Yet the conventional approach to 'on-demand' JavaScript commits that very sin by polluting methods with code for retrieving resources from the server. Ideally, on-demand loading should occur without such disjunctive clutter, and this page explains how to employ AspectJS to this effect.
Contents
To use the approaches demonstrated below, you must have a copy of AspectJS, which can be downloaded for free from the Download page. A comprehensive and detailed tutorial is also available, and you are strongly advised to read the Getting Started page before reading this one.
Additionally, the examples below use three functions called LoadStyleSheet, LoadLib_STH and LoadLib_XHR. The code for these, and instructions for using them independently from AspectJS can be found on their respective pages. Note that if you wish to use LoadLib_XHR you will also need a copy of the XMLHTTPRequest library that is also available on this site. Alternatively, you can use proprietary versions of all these resources.
For a detailed explanation of the rationale behind the techniques explored below, see the article entitled Transparency on Demand, which appeared in the November 2007 edition of Dr Dobbs Journal of Programming.
Core Principle
Transparent on-demand loading pivots on the notion that calls to retrieve code from the server can be removed from functions that make such calls, and can be applied instead as prefixes to those functions. In other words, invocation of a given function can be intercepted, such that a call to a library-loader executes first, followed by execution of the interceptee. This removes disjunctive syntax from the interceptee, thus restoring its cohesion.
Critically, and in the case of loading JavaScript (rather than CSS code), a given library need be retrieved only once, as the code it contains remains in the execution environment until a new page is loaded. AspectJS gives control over affix lifetime, therefore a 'loading prefix' can be set to execute just once before it is removed, thus improving efficiency even further.
Further to this, all prefix-related code can be placed in a library that is separate from all other code in the system, and this underlines the essential tenet of Aspect Orientation, which is that non-related, but cross-cutting concerns can be separated out, thus aiding the implementation and development of a system.
Loading Style-Sheets
Loading style-sheets transparently is a simple matter of attaching the function called LoadStylesheet as a prefix to the method that one wishes to trigger the download.
In example one, the call to AJS.AddPrefix, stipulates the global method called OnClick as the interceptee, and the prefix is indicated by the reference to LoadStylesheet. The third parameter is the argument that will be passed to that function (in this case the URL of the desired style-sheet), and the final parameter of 1 denotes that LoadStylesheet should be called for just one invocation of OnClick. This means that subsequent clicks on the body element will result in just a conventional call to OnClick.

 <!-- Example 1 -->

 <html>
    <head>
       <script type = "text/javascript" src = "AspectJS.js"></script>
       <script type = "text/javascript">

       function LoadStylesheet (LibURL) { ... }

       AJS.AddPrefix (this, "OnMouseClick", LoadStylesheet, "MyStylesheet.css", 1);

       function OnMouseClick ()
          {

          ...

          }

       </script>

    </head>

    <body onclick = "OnMouseClick ()"> ... </body>

 </html>
            
Loading JavaScript Asynchronously
Loading a JavaScript library transparently and asynchronously entails repeating the above principle, except that LoadLib_STH and the name of the library are stipulated as the third and fourth parameters in the call to AJS.AddPrefix. Apart from the fact that a JavaScript library is loaded, rather than a set of CSS style-definitions, the principle is exactly the same, and example two illustrates this.
Note that the asynchronous nature of the call means that the interceptee cannot rely on the library having been loaded while it is executing. The library therefore contains a call to OnLibLoaded at the end of the example, which will execute only once the library has made it into the execution environment. The fact of OnLibLoaded's execution implies that it is safe to access functions and any data that may be contained in Hello.js.

 <!-- Example 2 -->

 <html>
    <head>
       <script type = "text/javascript" src = "AspectJS.js"></script>
       <script type = "text/javascript">

       function LoadLib_STH (LibURL) { ... }

       AJS.AddPrefix (this, "OnMouseClick", LoadLib_STH, "Hello.js", 1);

       function OnMouseClick ()
          {

          ...

          }

       function OnLibLoaded ()
          {
          SayIt ();
          }

       </script>

    </head>

    <body onclick = "OnMouseClick ()"> ... </body>

 </html>


 // -- Contents of Hello.js ------------

 function SayIt () { alert ("Hello World"); }

 OnLibLoaded ();
            
Loading JavaScript Synchronously
There may be times when it is desirable to load a JavaScript library synchronously. Again the principle demonstrated in the above sections is the same, requiring only a minor change to the parameters that are passed to AddPrefix. Example three demonstrates this.
Note that in this case there is no need for the call-back technique used in the asychronous example shown above, as the interceptee will not execute until the library has been loaded. Given this, it is safe for OnMouseClick to invoke SayIt itself. In cases where the transaction with the server may fail, the application can supply an error-handling call-back function that LoadLib_XHR will invoke, and this error handler can then throw an exception, thus preventing OnMouseClick from proceeding.

 <!-- Example 3 -->

 <html>
    <head>
       <script type = "text/javascript" src = "AspectJS.js"></script>
       <script type = "text/javascript" src = "XHRLib.js"></script>
       <script type = "text/javascript">

       function LoadLib_XHR (Args) { ... }

       AJS.AddPrefix (this, "OnMouseClick", LoadLib_XHR, { LibURL : "Hello.js" }, 1);

       function OnMouseClick ()
          {

          SayIt ();

          }

       </script>

    </head>

    <body onclick = "OnMouseClick ()"> ... </body>

 </html>


 // -- Contents of Hello.js ------------

 function SayIt () { alert ("Hello World"); }
            
Multiple Loading-Prefixes
Clearly, there are times when multiple interceptees will require multiple libraries to be loaded. One approach here is to load those libraries from within a single prefix that is attached to all the interceptees concerned. This is perfectly adequate where those interceptees all require retrieval of the same code.
However, where they share common requirements, while having other specific needs this tactic will only introduce inefficiencies. This is because a call to a given interceptee will cause some libraries to be downloaded that it does not actually need, and which are never used subsequently by other interceptees because they are never invoked by the action's of the user concerned.
Example four illustrates this. Here Func_A requires the presence of Lib1_Func and Lib2_Func (to be found in Lib1.js and Lib2.js respectively), whereas Func_B requires Lib1_Func and Lib3_Func (the latter of which resides in Lib3.js).
Prefixing both Func_A and Func_B with LoadLibs means that a call to, say, Func_A will cause all three libraries to be downloaded. This is fine as long as Func_B is called subsequently. If not, however, the download of Lib3.js will be rendered superfluous because Func_A requires nothing contained in that library.

 <!-- Example 4 -->

 function LoadLib_XHR (Args) { ... }

 function LoadLibs ()
    {
    LoadLib_XHR ({ LibURL : "Lib1.js" });
    LoadLib_XHR ({ LibURL : "Lib2.js" });
    LoadLib_XHR ({ LibURL : "Lib3.js" });
    }

 AJS.AddPrefix (this, "Func_A", LoadLibs, null, 1);
 AJS.AddPrefix (this, "Func_B", LoadLibs, null, 1);

 ...

 function Func_A ()
    {
    Lib1_Func ();
    Lib2_Func ();
    }

 function Func_B ()
    {
    Lib1_Func ();
    Lib3_Func ();
    }


 // -- Contents of Lib1.js -------------

 function Lib1_Func () { ... }


 // -- Contents of Lib2.js -------------

 function Lib2_Func () { ... }


 // -- Contents of Lib3.js -------------

 function Lib3_Func () { ... }
            
One solution might be to apply multiple prefixes to the interceptees in question, where each prefix loads just one library, thus allowing the downloads that a given method triggers to be specified precisely. Example five illustrates this, using the scenario in example four.
Here Func_A and Func_B now have two prefixes each, namely LoadLib_XHR. The two prefixes for Func_A cause the download of Lib1.js and Lib2.js, and the prefixes attached to Func_B cause the download of Lib1.js and Lib3.js.
However, this is not the solution (in this case) because, while execution of Func_A will cause the prefix that loads Lib1.js to be detached, The Func_B prefix that does the same thing will remain in place. A better solution is required, and this is covered in the next section.

 <!-- Example 5 -->

 function LoadLib_XHR (Args) { ... }

 AJS.AddPrefix (this, "Func_A", LoadLib_XHR, { LibURL : "Lib1.js" }, 1);
 AJS.AddPrefix (this, "Func_A", LoadLib_XHR, { LibURL : "Lib2.js" }, 1);

 AJS.AddPrefix (this, "Func_B", LoadLib_XHR, { LibURL : "Lib1.js" }, 1);
 AJS.AddPrefix (this, "Func_B", LoadLib_XHR, { LibURL : "Lib3.js" }, 1);

 ...

 function Func_A ()
    {
    Lib1_Func ();
    Lib2_Func ();
    }

 function Func_B ()
    {
    Lib1_Func ();
    Lib3_Func ();
    }


 // Contents of libraries not shown
            
Managing Redundant Prefixes
The solution to the problem of redundant prefixes is to keep track of those that download a given library, and remove all of them on execution of just one of their interceptees. (Note that the tutorial covers the topic of self-removing affixes).
Example 6 demonstrates this, where LoadLib1 is attached as a prefix to both Func_A and Func_B, and the affix objects returned from the two calls to AddPrefix are pushed onto the array called Lib1LoadingPrefixes.
When either Func_A or Func_B is invoked, LoadLib1 executes first, and downloads Lib1.js. It then pops each prefix-object off the end of the array, calling the Remove method of each, so as to detach them from their interceptees. Finally, it sets the reference to the array to null, to allow the array to be garbage-collected subsequently.
Note that, in this example, the removal loop also attempts to removes the prefix instance that is executing currently (i.e. itself). This is redundant, as the prefix will already have been removed from the interceptee automatically by AspectJS. It is, however, entirely harmless because calling Remove on an affix object that has already been removed has no effect. Moreover, this makes the code considerably simpler.

 <!-- Example 6 -->

 function Func_A ()
    {
    Lib1_Func ();
    }

 function Func_B ()
    {
    Lib1_Func ();
    }

 ...

 function LoadLib_XHR (Args) { ... }

 var Lib1LoadingPrefixes = new Array ();

 function LoadLib1 ()
    {
    LoadLib_XHR ({ LibURL : "Lib1.js" });

    while (Lib1LoadingPrefixes.length > 0)
       {
       Lib1LoadingPrefixes.pop ().Remove ();
       }

    Lib1LoadingPrefixes = null;.

    }

 Lib1LoadingPrefixes.push (AJS.AddPrefix (this, "Func_A", LoadLib1 , null, 1));
 Lib1LoadingPrefixes.push (AJS.AddPrefix (this, "Func_B", LoadLib1 , null, 1));


 // Contents of Lib1.js not shown
            
Copyright © Dodeca Technologies Ltd. 2007