Tutorial

Getting Started

Importing the AJS Object

As with any JavaScript library, including AspectJS in an application is a simple matter of including the appropriate script-tag in the relevant HTML page, and Example 1 illustrates this.

The AspectJS libraries can also be pulled into a given execution environment by means of 'on-demand' JavaScript (also known as the 'script-tag hack'), and through the use of XMLHTTPRequest. This is not explored here.


 <!-- Example 1 -->

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

       // Application code

       </script>
    </head>

    <body> ... </body>

 </html>
            

Applying Prefixes

Within the scope of AspectJS, a function that executes when another function is invoked, and which executes before that intercepted function (the 'interceptee'), is called a 'prefix'. Conversely, a function that executes after the interceptee is called a 'suffix' - collectively, these are referred to as 'affixes'.

Applying a prefix (or suffix, or wrapper) requires just a single call to the AJS object, and Example 2 illustrates the essential principle. Here the code adds a prefix (called 'prefixFunc' here, although its name could be any legal JavaScript function-name) to a function called myFunc.

The code then calls myFunc, the result being that prefixFunc executes first, after which myFunc executes.

Examining the arguments that are passed to addPrefix:

  1. The first parameter indicates the object of which the interceptee is a member (the 'interceptee owner'). In the example, myFunc is a member of the Global object, therefore 'this' is passed to addPrefix because the call has been made at global scope. Were myFunc a member of another object, the name of that object would form the first parameter.
  2. The second parameter indicates the name of the method that is to be intercepted (the interceptee). This must be in quotes.
  3. The third parameter indicates which function should serve as the prefix to myFunc. This must be a reference to a function.

 // Example 2

 function prefixFunc ()
    {
    alert ("prefixFunc executed");
    }

 function myFunc ()
    {
    alert ("myFunc executed");
    }


 AJS.addPrefix (this, "myFunc", prefixFunc);

 myFunc ();

 --------------------------------------

 Output:

 prefixFunc executed
 myFunc executed
            

The diagram illustrates what happens semantically when myFunc is invoked, where the blue line indicates entry into a function, and the red line indicates return from that function.

Diagram showing execution path through an interceptee and a prefix function

How does it Work?

When the AJS object applies an initial prefix (or suffix etc) to an object's method, it modifies the object's internal reference to the method body, such that it points to a 'proxy' function that is generated by the AJS object.

When client code invokes (what it thinks is) the method, the proxy function executes instead, and calls any prefixes that have been applied. Once the last prefix has returned, it executes the intercepted method itself, before executing any suffixes that the application may have applied.

The AJS object manages everything associated with this mechanism, even down to preserving any members that the intercepted method may have (functions can have members in JavaScript) as it goes from a non-intercepted state, through its time as an interceptee, and back to non-interception again (if applicable).

Moreover, should an interceptee acquire new members during the time it possesses affixes, it will retain these (optionally, and by default) should it revert back to non-interception again.

You do not need to understand the nuts and bolts of the interception mechanism to work with AspectJS but, if you are curious, the article entitled Transparency on Demand explains the relevant concepts from first principles.

Considerations

Note that, when setting an intercept on a global function from within a method of another object, the this reference will point to that other object (unless 'this' has been changed by calling the method using call or apply). In those cases 'window' should be passed to the AspectJS function in question ('window' being an alias for the Global object).

Note also, that arrays, strings and functions can also have methods, which are also 'interceptable'. Numbers and booleans, however cannot possess methods.

Finally, you should note that none of the methods of the AJS object (or of the objects they generate) perform any error checking on the number, type or value of the arguments that callers pass to them. Should you, for example, pass a string where a function-reference is expected then, at best, your code will behave unexpectedly. At worst, it will cause the interpreter to raise an exception.

However, you can guard against this simply and completely by making a single call to the function called aJS_DbC before calling any other AspectJS methods. This will cause the checking of all parameters passed during a given method call, and the generation of an exception (replete with an informative error-message) whenever a bad or missing argument is detected.

This is explored fully in a later section of this tutorial.

Applying Suffixes

Applying suffixes is the complement of applying prefixes, and the diagram illustrates the execution path when a suffixed interceptee is called.

Diagram showing execution path through an interceptee and a suffix function

To add a suffix to a method, call AJS.addSuffix, as Example 3 demonstrates. Here a call to myFunc causes that procedure to execute first, after which the suffix executes. Aside from that, the meaning of the arguments passed to addSuffix is identical to those passed to addPrefix; although do note that the full calling-signature available to suffix functions is more sophisticated than that which is available to prefixes. This is explored in subsequent sections of this tutorial.


 // Example 3

 function suffixFunc () { alert ("suffixFunc executed"); }
 function myFunc     () { alert ("myFunc executed");     }


 AJS.addSuffix (this, "myFunc", suffixFunc);

 myFunc ();

 --------------------------------------

 Output:

 myFunc executed
 suffixFunc executed
            

Applying Wrappers

The application of a wrapper — where a method gains both a prefix and a suffix — follows the same principle as addPrefix and addSuffix, but is a little more involved because it requires passing more parameters.

Example 4 illustrates the application of a wrapping intercept to a function. Here, the first two arguments describe the interceptee, as in the previous examples, and the third describes the prefix function. The fourth and fifth can be ignored here (their purpose is explored in a subsequent section), and the sixth is a reference to the suffix function.


 // Example 4

 function prefixFunc () { alert ("prefixFunc executed"); }
 function suffixFunc () { alert ("suffixFunc executed"); }

 function myFunc     () { alert ("myFunc executed");     }


 AJS.addWrapper (this, "myFunc", prefixFunc, null, Infinity, suffixFunc);

 myFunc ();

 --------------------------------------

 Output:

 prefixFunc executed
 myFunc executed
 suffixFunc executed
            

Symmetric Wrappers

Nothing prevents a given function from acting as both a prefix and suffix to a given interceptee, and passing the same reference for the suffix and prefix to addWrapper yields such double-duty.

However, there is no need to stipulate the reference to the prefix function a second time when calling addWrapper. If the argument denoting the suffix is omitted, then addWrapper will use the argument that denotes the prefix as a default.

Furthermore, in this case, there is no need to provide values for the fourth and fifth arguments, as these will default to null and Infinity respectively. Given this, Example 5 illustrates creating a simple symmetric wrapper.


 // Example 5

 function wrapfixFunc (IArgs, Value) { alert ("wrapfixFunc executed"); }
 function myFunc      ()             { alert ("myFunc executed" );     }

 AJS.addWrapper (this, "myFunc", wrapfixFunc, null, Infinity, wrapfixFunc);

 myFunc ();

 --------------------------------------

 Output:

 wrapfixFunc executed
 myFunc executed
 wrapfixFunc executed
            

Using Inline Affix-Function Definitions

Affix functions do not have to be defined separately from the call to the AJS method that applies them to a given interceptee.

JavaScript allows a function definition (rather than a reference to a function that is defined elsewhere) to act as an argument to another function (one of the great strengths of the language) and this means that a prefix or suffix can be defined within a call to the relevant AJS method.

Example 6 illustrates this by calling AJS.addPrefix wherein the prefix is defined 'in-place' in the argument set.


 // Example 6

 function myFunc ()
    {
    alert ("myFunc executed - Jazz is weird");
    }

 AJS.addPrefix ( this,
                "myFunc",
                 function ()
                    {
                    alert ("Inline-defined prefix executed - that's Jazz");
                    }
               );

 myFunc ();

 --------------------------------------

 Output:

 Inline-defined prefix executed - that's Jazz
 myFunc executed - Jazz is weird
            

Parameterising Affix Application

The AJS object can add only a single prefix, suffix or prefix/suffix pair at a time. However, affixes can be added to multiple methods with just a single call to a general-purpose utility function called findIn. This forms part of the AspectJS distribution set, and is described fully in the Supplementary section.

findIn is capable of searching for object-members by name, using regular-expressions, and whenever it encounters a member that conforms to the search criteria, it invokes a function that (if present) is provided by its caller.

That function can do whatever it wishes, including calling AspectJS methods, and this intersection of findIn and AspectJS allows the application of affixes to arbitrary numbers of arbitrary methods at arbitrary locations within a given object.

findIn is provided with a full tutorial that explores these issues in depth, nevertheless Example 7 (taken from that tutorial) demonstrates the essence of findIn and AspectJS working in concert. Here an object is defined, using literal syntax, as having three member-objects. Each of those has three members, one of which is a method called getValues.

The code defines a prefix and then calls findIn, specifying MyObj as the object to search, and getValues as the member to search for. It also passes a reference to a function called addPrefix that findIn must invoke should it encounter a conforming member.

addPrefix takes a reference to the object that contains the member, and the name of the conforming method (as a string), and when invoked it calls the addPrefix member of the AJS object. This applies a prefix to the member, and then returns true to indicate that findIn should continue searching.

Upon return from the call to findIn, the code calls the getValues member of the Barney member of myObj, whereupon the prefix executes before getValues.


 // Example 7

 // Note that you must import a copy
 // of findIn for this to work

 function findIn (...);

 var MyObj =
    {
    Fred   :
       {
       Age       : 36,
       Spouse    : "Wilma",
       getValues : function () { alert (this.Age + " " + this.Spouse); }
       },

    Barney :
       {
       Age       : 34,
       Spouse    : "Betty",
       getValues : function () { alert (this.Age + " " + this.Spouse); }
       },

    Wilma  :
       {
       Age       : 32,
       Spouse    : "Fred",
       getValues : function () { alert (this.Age + " " + this.Spouse); }
       }

    };

 function prefixFunc () { alert ("prefixFunc Executed"); }

 function onFound    (Obj, Method)
    {
    AJS.addPrefix (Obj, Method, prefixFunc);
    return true;
    }

 findIn (MyObj, "getValues", onFound);

 MyObj.Barney.getValues ();

 --------------------------------------

 Output:

 Prefix Executed
 34 Betty
            

Specifying Execution-Maxima

All the affixes in the examples shown above execute indefinitely. This means that they will continue to execute before or after the interceptee (when it is called) until the execution context is destroyed (e.g. the user loads a new page into the browser)

However, affixes can be removed explicitly (which is explored in a subsequent section), and the number of times a given affix executes can also be limited by passing an integer parameter (the Execs argument) to the relevant AJS.add??????????-method.

In Example 8, prefixFunc is set to execute two times, and once that has happened it is removed automatically. Moreover, and in this example, the interceptee has only one prefix, meaning that, upon removal of that prefix, the interception mechanism disappears, and the relationship between the interceptee and its owner-object returns to normal.

Note that, once again, the fourth argument is unused here, and that its purpose is explored in a subsequent section of this tutorial. Note also that addWrapper will accept an Execs argument for the suffix, and that omission of this will cause addWrapper to use the prefix's Execs argument as a default.

Note also that, should a prefix or suffix throw an exception when called, the call still counts as one execution, thus bringing it one step closer to its removal (assuming a finite execution-limit). In other words, prefixes and suffixes cannot keep themselves attached to an interceptee indefinitely by throwing exceptions.


 // Example 8

 function prefixFunc () { alert ("prefixFunc executed"); }
 function myFunc     () { alert ("myFunc executed");     }

 AJS.addPrefix (this, "myFunc", prefixFunc, null, 2);

 myFunc ();
 myFunc ();
 myFunc ();

 --------------------------------------

 Output:

 prefixFunc executed
 myFunc executed
 prefixFunc executed
 myFunc executed
 myFunc executed
            

Go forward to Part 2 of this tutorial.