AJS_ODL (AspectJS On-Demand Loading) is a singleton object that, like AJS_Validator and AJS_Logger, is a client of the AJS object, and which is instantiated by a call to a function called createAJS_ODL. It possesses just three methods that can be used to effect the loading on-demand of disparate resources from a server into a user agent, without the drawbacks that more-conventional approaches carry, and without having to manage the complexities of doing it yourself by making calls to the AJS object directly.
While it allows you to load a variety of resources, such as images, JavaScript components and CSS code, it is possible to view it simply as a configurable, heavy-duty XHR transaction-handler, and you should see the sections on the XHR LoadMethod and XHR Queueing and Dispatch for detailed points in this regard.
Do note that, if you deploy a minified codebase that includes the AJS_ODL object, you should read the Minification page, which apprises you of the issues that pertain here. Note also that the AJS_ODL object carries an AJS_Validator_Tag property with the value "AJS_ODL", which may be of value during development.
Note too that, given that AspectJS is a commercial product, it is not possible to give 'live' demonstrations of AJS_ODL's capabilities that the various examples in this user guide demonstrate, because that would entail deploying it on this site, which, in essence, would be to give the product away.
Contents |
Introduction Importing AJS_ODL LoadDefs The Attributes Property Event Handlers Applying LoadDefs LoadDef Queues Multiple LoadDefs LoadDef Expiry Validating AJS_ODL Method-Calls |
// -- AJS_ODL-Object Interface Snapshot (pseudo code) ------------------------ // // See the API documentation for formal property-definitions. // var AJS_ODL = { applyLoadDef : function (MethodOwner, LoadDef[, MethodOwnerName]) { ... }, pushLoadDef : function (MethodOwner, LoadDef[, MethodOwnerName]) { ... }, applyLoadDefQueue : function () { ... }, //------------------------------------- Version : "1.0", AJS_Validator_Tag : "AJS_ODL" };
To use AJS_ODL, import AJS_ODL.js into your application, along with the files that define the AJS object, throwException and AJS_DefMgr. Then call createAJS_ODL, passing a reference to the AJS object, the createAJS_DefMgr function and the throwException function.
Having followed these steps, your application is free to create and apply 'LoadDefs' (explored below) to whatever objects you choose, and Example 1 demonstrates these points.
If you are using AJS_ODL in a deployed application, you can import ThrowException_Remote.js instead of ThrowException.js, and then call createThrowException_Remote. You should then pass the function-reference returned from that call to createAJS_ODL. This will cause exception notifications to be sent back to the server, rather than to (possibly) be presented to the application user, and this enables remote debugging. Example 2 shows the syntax you should use if using throwException_Remote.
Note that, as the other AspectJS user-guides point out, you are entirely free to pass references to your own exception handler, as long as it presents the same calling signature as the AspectJS implementations of throwException/throwException_Remote.
<!-- Example 1 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> <html> <head> <script src = "ThrowException.js"></script> <!-- Import ThrowException_Remote.js to effect --> <!-- exception-reporting across a network. You may employ --> <!-- your own implementation of this function. --> <script src = "AJS.js" ></script> <!-- Note that ThrowException_Remote.js requires --> <script src = "AJS_DefMgr.js" ></script> <!-- ThrowException.js. --> <script src = "AJS_ODL.js" ></script> <script> "use strict"; var AJS_ODL = createAJS_ODL (AJS, createAJS_DefMgr, throwException); // Other code, including including LoadDef instantiations // and calls to AJS_ODL methods etc. occur hereon. </script> </head> <body> ... </body> </html>
// -- Example 2 -------------------------------------------------------------- var throwException_Remote = createThrowException_Remote ("http://mydomain.com/OnClientSideException.php"); var AJS_ODL = createAJS_ODL (AJS, createAJS_DefMgr, throwException_Remote); // Other code, including including LoadDef instantiations // and calls to AJS_ODL methods etc. occur hereon.
AJS_ODL defines the concept of a 'Loading Definition' (referred to hereinafter as a 'LoadDef'). A LoadDef is an object that defines which methods of a given object should, upon their execution, cause the automatic and transparent retrieval of one or more resources, such as code, and graphical page-elements, and which specifies the resource(s) that should be loaded, along with the 'loading method' that AJS_ODL should employ.
LoadDefs are similar to ValidationDefs and LogDefs in the use of AJS_Validator and AJS_Logger respectively; you create a LoadDef object, and then submit it to AJS_ODL, passing also a reference to an object (the 'MethodOwner') to which the LoadDef should be applied. AJS_ODL takes care of things from there on, according to the information contained in the LoadDef object.
Note that, while the full set of permissible-LoadDef properties is rather involved, your LoadDefs are likely be very simple affairs that carry only a MethodOwner and a URLs property, along with a LoadMethod, and perhaps a few other supporting elements. Nevertheless, and as Example 3 shows, LoadDefs may possess the following case-sensitive properties:
// -- Example 3 (pseudo code) ------------------------------------------------ var LoadDef = { URLs : // Mandatory. [ 'URL 0', // Mandatory string. . . . 'URL n' // Optional string. ], MethodNames : // Optional. [ 'MethodName 0', // Mandatory string. . . . 'MethodName n' // Optional string. ], LoadMethod : 'AUDIO' // Optional string; default is 'XHR', 'WS' may be implemented | 'EMBED' // in future, values are case sensitive. | 'IFRAME' | 'IMG' | 'LINK' | 'OBJECT' | 'SCRIPT' | 'VIDEO' | 'XHR', Attributes : // Optional; meaningful only if LoadMethod is not 'XHR'. { Name : Value, // Optional string or function reference. . . . Name : Value // Optional string or function reference. }, onLoad : function, // Optional; mandatory if LoadMethod is 'XHR'. onError : function, // Optional; mandatory if LoadMethod is 'XHR'. onTimeout : function, // Optional; meaningful only when LoadMethod is 'XHR'. TimeoutDelay : number, // Optional; meaningful only when LoadMethod is 'XHR'. Asynch : boolean, // Optional; meaningful only when LoadMethod is 'XHR' or 'SCRIPT'. ResponseType : string, // Optional; meaningful only when LoadMethod is 'XHR'. MimeType : string // Optional; meaningful only when LoadMethod is 'XHR'. onCreation : function, // Mandatory unless LoadMethod is 'LINK', 'SCRIPT' or 'XHR'. Retain : boolean, // Optional, meaningless if LoadMethod is 'SCRIPT' or 'LINK'. };
The table shows which properties are permissible/disallowed/etc. for a given LoadMethod. Note that, where a given properties-permutation disallows a given property, any trangressions on the part of a given LoadDef will be trapped only if you are using AJS_ODL.val.js (see below). If not, transgressions are likely to result in obscure exceptions that occur within the AJS_ODL object.
Note also that, in respect of LoadMethod values, the value 'WS' is reserved because AJS_ODL may be extended at some point to support loading using WebSockets.
Finally in this section, note that LoadDefs may carry arbitrary properties that AJS_ODL does not define, and which may be of value to you, but which AJS_ODL will ignore.
Asynch | Excluded Attributes | onCreation | onLoad | onError | onTimeout | TimeoutDelay | ResponseType | MimeType | |
AUDIO | src | ||||||||
EMBED | src | ||||||||
IFRAME | src | ||||||||
IMG | src | ||||||||
LINK | src | ||||||||
OBJECT | data | ||||||||
SCRIPT | href, asynch, text | ||||||||
VIDEO | src | ||||||||
XHR | |||||||||
Optional | |||||||||
Mandatory | |||||||||
Redundant |
Exploring the Attributes property in more detail: all LoadMethods other than XHR operate by means of HTML-element synthesis, followed by injection of the synthesised-element into the DOM hierarchy for a given tract of HTML (either by your onCreation handler, or automatically, in the case of the 'LINK' and 'SCRIPT' LoadMethods), where the element in question may carry a number of attributes.
Given this, the Attributes property allows you to specify values for those attributes, by means of name-value pairs, where each name corresponds to an HTML attribute that the element-type concerned can carry (according to the W3C HTML-recommendation that applies).
Note that the values given must be string-literals, strings or function-references, and so an Attributes property for a LoadDef that stipulates the IMG LoadMethod, could look something like this:
Attributes : { alt : "Picture of me on a much-needed holiday", class : "CaptionedImage" }
...Where AJS_ODL would copy the value of the alt property of this object to the alt attribute for the <img> element that it brought into being, and where the class attribute would acquire the class property-value. If an Attributes property is a function reference, AJS_ODL will attach it to the nascent element using addEventListener.
Given these points, and given that AJS_ODL sets the src/href/data attribute of each element it creates using a URL taken from the URLs property of the corresponding LoadDef, those attribute-names must not appear as properties of the LoadDef's Attributes-property. Equally, and when loading using the 'SCRIPT' LoadMethod, the Attributes property of the LoadDef in question must not contain an Asynch property (because LoadDefs themselves can carry an Asynch property).
The table given here re-displays the Excluded Attributes column given in the table in the previous section, and shows which attribute names must not appear in a LoadDef's Attributes property, and do note that using AJS_ODL.val.js (see below) will trap any problems that arise in this respect.
Note that, in the case of boolean attributes, you should give a value of 'true' (as a literal string), or give the name of the attribute, as a literal string, as the value for that attribute. For example:
Attributes : { loop : "loop" }
...When using the 'VIDEO' LoadMethod, will cause the video to play in a repeating loop. Similarly:
Attributes : { loop : "true" }
...Will have the same effect.
Excluded Attributes | |
AUDIO | src |
EMBED | src |
IFRAME | src |
IMG | src |
LINK | src |
OBJECT | data |
SCRIPT | href, asynch, text |
VIDEO | src |
XHR | Attributes Property Disallowed |
The signatures of the event handlers that AJS_ODL recognises in a LoadDef, namely: onCreation, onLoad, onError and onTimeout are given formally in AJS_ODL's API documentation. Do note here that the onCreation handler applies only to the element synthesis LoadMethods, and that the onTimeout handler applies only to the 'XHR' LoadMethod (and that only some of the handlers are allowed for certain LoadMethods – see the table in the LoadDefs section above). Note also that the signatures for the onLoad and onError functions differ when using the element-synthesis LoadMethods to the signatures that apply when using 'XHR'.
Skeletal implementations for the element-synthesis handlers are given in Example 4, which shows that, for an onCreation handler, AJS_ODL passes a reference to the newly-created element, and a reference to the corresponding LoadDef. Passing this second argument facilitates the sharing of the handler between multiple LoadDefs (when the handler executes, it knows which LoadDef is associated with that execution).
Similarly, the onLoad and onError handlers are passed the same two arguments along with a leading Event-argument, which is a reference to the Event object that the user agent generated.
// -- Example 4: Element Synthesis Event-Handlers ---------------------------- function onCreation (LoadDef, Element) { // Insert the Element into the DOM hierarchy here, or... } function onLoad (LoadDef, Element, Event) { // ...wait until this handler executes before DOM insertion. } function onError (LoadDef, Element, Event) { }
For the 'XHR' LoadMethod event handlers, skeletal implementations are given in Example 5. Note that an onCreation handler is disallowed (because the 'XHR' LoadMethod does not create new HTML element-objects) and, in place of the Event and Element arguments that are passed to the element-synthesis handlers, they are passed a reference to the XHR object that performed the transaction. The onTimeout handler is passed only the LoadDef in question.
// -- Example 5: XHR Event-Handlers ------------------------------------------ function onLoad (LoadDef, XHRObj) { // Access the XHR object's responseText and/or // response and/or responseXML properties here. } function onError (LoadDef, XHRObj) { // Access the XHR object's status and statusText // properties here. } function onTimeout (LoadDef) { }
As with AJS_Validator and AJS_Logger, there are two ways to apply LoadDefs. The immediate way is to use the applyLoadDef method of AJS_ODL, and this accepts two arguments: a reference to the object that possesses the methods to which the LoadDef should be applied (the MethodOwner), and a reference to that LoadDef object.
applyLoadDef uses the AJS object to attach a prefix-function to the methods of the object to which the MethodOwner argument refers. If no MethodNames property is present, AJS_ODL will attach prefixes to every method in the object, but if a MethodNames property exists, AJS_ODL will attach prefixes to only those methods to which the members of that array refer.
Following application of the LoadDef, the first subsequent call to a prefixed-method of the MethodOwner causes the prefix to execute, which attempts thereon to load the resource(s) referred to in the URLs property of the LoadDef, using the LoadMethod that the LoadDef stipulates. The details of this process are explored in following sections of this guide.
Example 6 shows the use of applyLoadDef (wherein the LoadDef stipulates the 'SCRIPT' LoadMethod, detailed coverage of which is to be found in a subsequent section of this guide).
Note that AJS_ODL guarantees to issue requests in the the order in which LoadDef-intercepted method-calls occur, and in the order in which URLs are listed in a given LoadDefs URLs property, but these requests occur asynchronously except where the LoadMethod is 'SCRIPT' or 'XHR', and then only under certain conditions. See the SCRIPT and XHR sections for more information.
// -- Contents of MyOtherObj.js ---------------------------------------------- var MyOtherObj = { method_A : function () { console.log ("MyOtherObj.method_A Executed"); } };
// -- Example 6 -------------------------------------------------------------- var MyObj = { method_A : function () { }, method_B : function () { console.log ("MyObj.method_B Executed"); }, method_C : function () { } }; var MyObj_LoadDef = { URLs : ["MyOtherObj.js"], LoadMethod : "SCRIPT", onLoad : function (LoadDef, Element, Event) { MyOtherObj.method_A (); } }; AJS_ODL.applyLoadDef (MyObj, MyObj_LoadDef); MyObj .method_B (); // A call to method_B will trigger the retrieval of MyOtherObj.js, // which, when complete will cause onLoad to execute, which will // call MotherObj.method_A. -- Output -------------------------------------------------------------------- MyObj.method_B Executed MyOtherObj.method_A Executed
The second way of applying LoadDefs involves the use of the pushLoadDef method. This also accepts two arguments, which are the MethodOwner and the LoadDef that should be applied to that object's methods, and it adds the LoadDef to an internal queue, where it remains until a subsequent call to the applyLoadDefQueue method. A call to that method then applies each LoadDef to its respective object in the order in which the LoadDefs were pushed onto the queue, emptying the queue as it does.
Example 7 demonstrates the use of pushLoadDef and applyLoadDefQueue.
Note that the issues that apply to LoadDef queues are identical to those that apply to ValidationDef- and LogdDef-queues, and so you should read the AJS_DefMgr Overview for information on these important points.
// -- Example 7 -------------------------------------------------------------- // // Content of MyOtherObj as shown for Example 6. // var MyObj = { method_A : function () { }, method_B : function (CallCount) { console.log ("MyObj.method_B Executed. CallCount: " + CallCount); }, method_C : function () { } }; var MyObj_LoadDef = { URLs : ["MyOtherObj.js"], onLoad : function (LoadDef, Element, Event) { MyOtherObj.method_A (); }, onError : function (LoadDef, Element, Event) { // ... } }; MyObj .method_B (1); // This call will not trigger loading of MyOtherObj.js... AJS_ODL.pushLoadDef (MyObj, MyObj_LoadDef); MyObj .method_B (2); // ...Nor will this... AJS_ODL.applyLoadDefQueue (); MyObj .method_B (3); // ...But this will. -- Output -------------------------------------------------------------------- MyObj.method_B Executed. CallCount: 1 MyObj.method_B Executed. CallCount: 2 MyObj.method_B Executed. CallCount: 3 MyOtherObj.method_A Executed
Note also that AJS_ODL does not allow multiple LoadDefs to be applied to the same method of a given object, where the LoadMethod is the same. Given that you can get exactly the same effect by bundling all the URLs for a given method-call into a single LoadDef, allowing this would complicate AJS_DefMgr's implementation unnecessarily.
However, you may apply as many different LoadDefs as you wish to a given method, where their respective LoadMethods differ, and Example 8 demonstrates this. Here, LoadDef_0 directs the loading of MyOtherObj.js, and LoadDef_1 directs the loading of MyImage.svg, both of which are applied to MyObj.method_B. The call to method_B then causes the two resources to be retrieved.
Note that the retrieval requests are made in the order in which multiple LoadDefs are applied to the method in question. That is: in Example 8, LoadDef_0 is applied first, so the request for the resource it stipulates is issued before the request for the resource indicated by LoadDef_1. Do remember, however, that given the asynchronous nature of network communications, the order in which the resources will be returned cannot be guaranteed, and this may introduce concurrency issues. This area is discussed in a subsequent section in this guide.
// -- Example 8 -------------------------------------------------------------- // // Content of MyOtherObj as shown for Example 6. // var MyObj = { method_A : function () { }, method_B : function () { }, method_C : function () { } }; var LoadDef_0 = { URLs : ["MyOtherObj.js"], LoadMethod : "SCRIPT", onLoad : function (LoadDef, Element, Event) { console .log ("LoadDef_0: onLoad executed"); MyOtherObj.method_A (); } }; var LoadDef_1 = { URLs : ["MyImage.jpg"], LoadMethod : "IMG", onCreation : function (LoadDef, Element) { console .log ("LoadDef_1: onCreation executed"); document.body.appendChild (Element); }, onLoad : function (LoadDef, Element, Event) { console .log ("LoadDef_1: onLoad executed"); } }; AJS_ODL.applyLoadDef (MyObj, LoadDef_0); AJS_ODL.applyLoadDef (MyObj, LoadDef_1); // Fine, a second or third etc. LoadDef application where each has a // different LoadMethod is allowed. MyObj .method_B (); // MyOtherObj.js and MyImage.svg will both be retrieved at this point. -- Output -------------------------------------------------------------------- LoadDef_1: onCreation executed LoadDef_0: onLoad executed MyOtherObj.method_A Executed LoadDef_1: onLoad executed
As the motivation page explains, AJS_ODL uses the AJS object to attach a prefix function to a given method, where a call to that method causes the prefix to execute first. That causes AJS_ODL to effect the retrieval of one or more resources in accordance with the values of the properties in the corresponding LoadDef. Normally, as part of this process, AJS_ODL also removes the loading-prefix from the intercepted method, meaning that, where the method has no other prefixes or suffixes, the AJS object will return it to its normal non-intercepted state (behaviour that is documented in the user guide for the AJS object).
This means that subsequent calls to the same method will not cause repeat retrievals of the resource in question, and this is entirely what we want in the case of the SCRIPT and LINK LoadMethods. This is because we need to load executable code and stylesheet rules only once into a browser during a given instantiation of the application in question. Moreover, it follows that, at the least, it is redundant to apply subsequently a LoadDef that attempts to cause a repeat retrieval of the same JS and/or CSS resources. Similarly, in the case of resources that are retrieved by means of the AUDIO, EMBED, IFRAME, IMG, OBJECT and VIDEOLoadMethods, it is redundant to cause the re-synthesis of the corresponding page-element.
Note that AJS_ODL maintains an internal list of all URLs to which all applied LoadDefs refer. Given this, and in keeping with the load-once-only policy explained above, a call to a given method will cause AJS_ODL to disassociate from their target methods all LoadDefs that refer to the same URL from that URL. That is, if LoadDefs A and B refer respectively to URLs P and R, where both LoadDefs also refer to URL Q, a call to a method that LoadDef A covers will mean that a call to a method that LoadDef B covers will not cause a repeat retrieval of resource Q.
The diagram illustrates this point.
This means that, when all URLs referred to by a given LoadDef have been traversed in relation to other LoadDefs, that LoadDef is detached from the methods it covers (even if those methods have yet to be called). This is one of the principal benefits of using AJS_ODL rather than implementing proprietary technology, because doing it yourself means having to wrestle with the issue of co-referenced URLs.
In the case of XHR-based retrieval, however, you may be using that LoadMethod precisely because the resource you wish to retrieve is dynamic in nature (i.e. it changes from time to time – it may, for example, be a news-feed of some kind), and, in this case, the automatic expiry of the LoadDef in question may not be what you want.
This is the purpose of the Retain property that LoadDefs may possess. By using this property with a value of true, AJS_ODL will not remove the loading-prefix from the method in question when that method is called, but will leave it in place. This means that subsequent calls to the method will cause repeated retrieval from the corresponding URL.
Example 9 demonstrates this.
// -- Contents of GetTime.php ------------------------------------------------ <?php echo microtime (true); ?>
// -- Example 9 -------------------------------------------------------------- var MyObj = { method_A : function () { }, method_B : function (CallCount) { console.log ("MyObj.method_B Executed. CallCount: " + CallCount); }, method_C : function () { } }; var MyObj_LoadDef = { URLs : ["GetTime.php"], onLoad : function (LoadDef, XHRObj) { console.log ("The time including microseconds is: " + XHRObj.responseText) }, onError : function (LoadDef, XHRObj) { // ... }, Retain : true }; AJS_ODL.applyLoadDef (MyObj, MyObj_LoadDef); MyObj .method_B (1); MyObj .method_B (2); MyObj .method_B (3); -- Output -------------------------------------------------------------------- MyObj.method_B Executed. CallCount: 1 MyObj.method_B Executed. CallCount: 2 MyObj.method_B Executed. CallCount: 3 The time including microseconds is: 1424023939.1576 The time including microseconds is: 1424023940.4476 The time including microseconds is: 1424023941.3336
As the Product Overview page explains, none of the functions and methods that comprise AJS_ODL validate the number, type or value of the arguments that they receive. You could, for example, pass a LoadDef to AJS_ODL.applyLoadDef that contains an onLoad property that is not a function reference.
However, as the Product Overview page also explains, the AJS_Validator object uses the AJS object to implement validation of calls to the methods of arbitrary objects. It follows that you can use AJS_Validator to validate calls to AJS_ODL's methods. This will give you all the validation you need when developing code that uses AJS_ODL with, if you so choose, none of the concomitant overhead when you deploy (simply change the call to createAJS_ODL_Validated to a call to createAJS_ODL).
This is the purpose of the file called AJS_ODL.val.js, and to use this facet of AspectJS, you should read the Dog-Food Consumption section in the Product Overview page first, before returning here to follow the pattern given in Example 10.
Do remember that the ValidationDef in AJS_ODL.val.js does not scrutinise the contents of the Attributes property, other than to check for the presence of the disallowed values listed in the table in the LoadDefs section above, and to check that the value of each proprty is a string. It is up to you therefore to ensure that the attribute names and values with which you populate a given LoadDef's Attributes property are relevant and correct.
<!-- Example 10 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> 01 <!DOCTYPE html> 02 <html lang = "en"> 03 <head> 04 <title>Example 10</title> 05 06 <script src = "Libs_JS/Abstract/ThrowException.js"></script> <!-- Or ThrowException_Remote.js, or your own exception --> 07 <script src = "Libs_JS/AspectJS/AJS.js" ></script> <!-- handler. Note that ThrowException_Remote.js requires --> 08 <script src = "Libs_JS/AspectJS/AJS_DefMgr.js" ></script> <!-- ThrowException.js. --> 09 <script src = "Libs_JS/AspectJS/AJS_Validator.js" ></script> 10 <script src = "Libs_JS/AspectJS/AJS_ODL.js" ></script> 11 <script src = "Libs_JS/AspectJS/AJS_ODL.val.js" ></script> 12 <script> 13 14 "use strict"; 15 16 var AJS_Validator = createAJS_Validator (AJS, createAJS_DefMgr, throwException); 17 var AJS_ODL = createAJS_ODL_Validated (AJS, createAJS_DefMgr, throwException, AJS_Validator); 18 19 // 20 // Application code such as the following from here on... 21 // 22 23 try 24 { 25 var MyObj = 26 { 27 method_A : function () { } 28 } 29 30 var MyObj_LoadDef = 31 { 32 MethodNames : [], // <-- A deliberate error here - empty 33 // MethodNames arrays are not allowed. 34 URLs : ["MyOtherObj.js"], 35 LoadMethod : "SCRIPT", 36 onLoad : function (LoadDef, Element, Event) { MyOtherObj.method_A (); } 37 38 }; 39 40 AJS_ODL.applyLoadDef (MyObj, MyObj_LoadDef); // The LoadDef and other factors are now 41 // validated by AJS_Validator using the 42 // ValidationDef in AJS_ODL.val.js. 43 } 44 45 catch (E) { console.log (E.name + ": " + E.message); } 46 47 </script> 48 49 </head> 50 51 <body> ... </body> 52 53 </html> 54 -- Output -------------------------------------------------------------------- AJS_Validator_Exception: the MethodNames property of a LoadDef passed to AJS_ODL.applyLoadDef has a length of 0, which is smaller than the minimum of 1. Exception occurred during execution of a function at global scope, at line 40, column 11, in //localhost/AJS_ODL_Examples/Example_10.htm