Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / HTML

Aspect Oriented Programming in JavaScript using Humax Web Framework

2.50/5 (2 votes)
23 Mar 2008LGPL311 min read 1   57  
Using Humax v0.2, this article is going to explain how to write and apply aspect-oriented/metadata driven programming in JavaScript.

We, the programmers (architect/designer/developer) are used to engage with different programming lifestyle for developing applications. The journey started with sequential assembly programming. Before two decades, we were used procedural approach mainly in C format. Though SmallTalk introduced the object orientated approach, Bajarne Straustrop's C++ was created a biggest impact in programming using OO. Rest is history.

Along with these, two other programming approaches Functional Programming and Aspect Oriented Programming (AOP) are existing with us. JavaScript by default is a combination of mainly procedural, little bit functional and prototype based object oriented language. None of other modern languages (till now) do not have this capability.

To provide AOP support to JavaScript is not major a concern, because of the flexibility of JavaScript. Numerous frameworks available to apply aspect-orient approach in JavaScript. Humax is one of web framework provides AOP enabled JavaScript in version 0.2.

AOP - An Introduction

In a typical software application development, we design and develop the system with object in mind. In a healthcare application, for example, you might find objects of type Patient, Clinical Request, etc. Most of the modern technologies like Java, .NET are based on OO, because it is the more mature and well known programming approach. In addition to these, now a days we feel the necesscity of cross-cutting functionalities in our applications.

Cross-cutting Concerns

Assume that you are maintaining an enterprise insurance system. The system does not have instrumentation particularly logging. When the management requested to implement logging functionality into the system, in a typical OO way, you need to design and develop classes for logging and then instantiate and invoke necessary methods in each and every classes of the insurance system. This is called cross-cuttong concerns.

AOP is designed to handle cross-cutting concerns by Aspect for descriping these concerns and automatically incorporating them into the system.

Note that AOP is not a replacement of OOP, instead it works with OOP for resolving these issues.

Code Scattering

One of the issue we are facing in OOP or other programming methodologies is code maintainability when we have to make changes in a method signature. This would affect all the code consumed this method. Although, we can resolve this issue simply by versioning, AOP attempts to resolve this issue by allowing to express cross-cutting concerns in stand-alone modules called Aspects

Aspect and Its Concepts

Aspect

Aspect is a programming module designed to capture a functionality that crosscuts an applications. An aspect can alter the behavior of the non-aspect part of an application. This can be achieved by advice at various join points specified in a quantification or query called a pointcut.

Join Points

A point in the target program where one or several aspects apply. The point can be a method, constructor, fields, exception and/or event of a class or collection of classes.

Pointcuts

A pointcut is an entity which detects whether a given join point matches. Simply, a pointcut is either a quantification or query which specifies a set of joinpoints where an aspect applies. For example, we can apply an aspect in all methods starting with "get" in a class.

Advice

Advice is a mean of specifying code to run at a join point. The behavior in an advice are performed for all the join points that are included in the associated pointcut. Three types of advice exist:

  • before. The advice is executed before the execution of join point
  • after. The advice is executed after the execution of join point
  • around. The advice is executed around (part of advice code executed before join point and part of advice code executed after join point execution) the execution of join point.

Different AOP frameworks for different programming languages follow different model to adopt AOP. To know more about AOP concepts, start from Wikipedia: AOP page.

Programming with Metadata

Metadata programming is a way to add descriptive statement to annotate programming elements such as classes, properties and methods. These descriptive statement can be used used at runtime to affect application behaviour based on its value. In .NET, this can be achieved by Attributes (which is more mature and pioneer on using metadata programming) and in Java, by Annotation.

Humax allows you to use both AOP and metadata programming by Facet.

Facet Programming in Humax

Humax introduces Facet from version 0.2 onwards to support both aspect orientated programming and programming with metadata.

The following figure shows the high level view of Humax's Facet:
Screenshot - AOPInJavaScript.PNG

This section presents a simple inventory management example of Facet with Humax. This example introduces the syntax for writing aspects.

Note that the example code has been written based on Humax framework. If you are not familiar with some code, please visit Humax Web Framework at SourceForge.net.

Inventory Management Application

The sample we have used here is an inventory management which enables to fill and/or purchase stocks.

JavaScript
Humax.declareNamespace("HxTest");
    
//Inventory Class
//Constructor    
HxTest.Inventory = function()
{
  // Fields    
  this._capacity = 10;
  this._available = 0;
  this._reorderLevel = 3;
  // Declare a multicast delegate called StockAlert - 
  //If you do not know delegates, visit tutorial
  // section at http://humax.sourceforge.net
  this.StockAlert = new Humax.MulticastDelegate("StockAlert", 
    Function("message, ordered, available",""));
  // Declare an event called OnStockAlert
  this.OnStockAlert = new Humax.Event(this.StockAlert);
}

HxTest.Inventory.prototype = 
{    
    fillInventory : function(quantity)
    {
        if(this._available + quantity <= this._capacity)
        {
            this._available += quantity;
        }else
        {
            // if inventory is full, raise the OnStockAlert event
            this.OnStockAlert(Humax.EventAction.Trigger, 
        "Stock full", quantity,this._available);
        }
    },
    
    orderItem : function(quantity)
    {
        if(quantity <= (this._available - this._reorderLevel))
        {
            this._available -= quantity;
        }else
        {
            // if inventory reached minimum reorder level, raise the OnStockAlert event
            this.OnStockAlert(Humax.EventAction.Trigger, 
        "Fill the stock, reached minium reorder level", quantity,this._available);
        }
    },
    getAvailableStockCount : function() {return this._available;}
}

// declare the class Inventory as Humax compatible
Humax.declareClass("HxTest.Inventory",HxTest.Inventory);

The Inventory class has two primary methods fillInventory and orderItem. The _available and _reorderLevel fields are involved in filling and ordering in the inventory. The fillInventory and orderItem methods are used to fill and order items in inventory respectively. Both methods raise OnStockAlert event when the inventory size is changed.

Running the Application

In a html page, you can place the above code into html->head->script block. Add the following HTML declaration in the page.

<body önload="page_onload()">
Quantity to fill<input type="text" id="fillTextBox" /><input type="button" id="fillButton" value="Fill" önclick="fillButton_onclick()" />
Quantity to order<input type="text" id="orderTextBox" /><input type="button" id="orderButton" value="Order" önclick="orderButton_onclick()" />
Inventory capacity: 10
</body>

Add the following event handlers in html->head->script block. Also, a variable myInventory is declared for keeping Inventory instance global.

JavaScript
var myInventory = null;

function page_onload()
{
    myInventory = new HxTest.Inventory();
    myInventory.OnStockAlert(Humax.EventAction.AttachHandler,
    stockAlertHandler)
}

function fillButton_onclick()
{
    document.getElementById("output").innerHTML = "";
    myInventory.fillInventory(
    parseInt(document.getElementById("fillTextBox").value)
    );
    document.getElementById("status").innerHTML = 
    "Available Stock: " + myInventory.getAvailableStockCount();
}

function orderButton_onclick()
{                
    document.getElementById("output").innerHTML = "";
    myInventory.orderItem(parseInt(document.getElementById("orderTextBox").value));
    document.getElementById("status").innerHTML = 
    "Available Stock: " + myInventory.getAvailableStockCount();
}

function stockAlertHandler(message, ordered, available)
{
    document.getElementById("output").innerHTML = 
    message + " Ordered:" + ordered.toString() + " Available: " + available.toString();
}    

The page load event handler instantiates myInventory and attach stockAlertHandler as one of the handler of OnStockAlert event. The fillButton_onclick calls fillInventory method and displays the available stock to UI. The orderButton_onclick calls orderItem method and displays the available to the UI. The stockAlertHandler displays the messages, currently how many has been ordered and how many available in inventory on UI whenever a fill or order is invalid.

First Facet

As we know that AOP can be achieved by Humax Facet. We will now proceed with the development of our first facet. This facet monitors each filled item by displaying message before and after the fillInventory method in Inventory class. Add the following script below to above event handlers.

JavaScript
HxTest.InventoryValidatorFacet = function()
{
    //calls base class "Humax.Facet" constructor implementation
    this.base();
    //Facet usage
    this.$usage = Humax.FacetUsage.AllowMultiple | 
    Humax.FacetUsage.AllowAdvices;    
}        

The $usage and $target Fields

Like a Humax class declaration, you can declare a facet declaration using function constructor. The above code declare the facet InventoryValidatorFacet. In the constructor, it calls base class constructor implementation and specifies the target usage of this facet. The base class protected member $usage allows you to control the manner in which it is used by setting FacetUsage enumeration. In the above code, the facet InventoryValidatorFacet can be specified more than one for a given element using AllowMultiple enumeration value. The AllowAdvices should be specified if we want to use this facet to allow advices. Surprised!

Basically facet is an approach used in Humax for both meta-data programming and AOP. By default, a facet is only a container for metadata. As we know that Advice is a mean of specifying code to run at a join point. Even though you have implemented an aspect, without specifying AllowAdvices enumeration, your facet cannot be treated as Aspect.

In addition to $usage, a facet provides one more protected field named$target. This allows you to specify the type of join point if your facet is an aspect or application element if your facet is a metadata on which it is valid to apply a facet. The base class by default specifies Humax classes and interfaces. Note that by default a facet usage is AllowMultiple.

In the above code we specified that the we are going to use this facet as aspect which can be specified more than one for a give element. And it targets only classes and interfaces.

Now we are going to implement this aspect. Add the following code below to the facet declaration:

JavaScript
HxTest.InventoryValidatorFacet.prototype = 
{                                                                
    beforeFillInventory : function(sourceObject, joinPointOriginalArgs)
    {                                                                                
        alert("Going to fill");                    
    },
    
    afterFillInventory : function(sourceObject, joinPointOriginalArgs)
    {                                                                                                    
        alert(joinPointOriginalArgs[0] + " item(s) has been filled");                    
    },
    
    attachHandlers : function()
    {
        //calls the base class "Humax.Facet" method attachHandlers
        // to initiate before/after/around advice delegates
        this.callBaseMethod("attachHandlers");                                        
        this.BeforeAdvice(Humax.EventAction.AttachHandler, 
      this.beforeFillInventory, this);                    
        this.AfterAdvice(Humax.EventAction.AttachHandler, 
      this.afterFillInventory, this);
    }
}

Humax.declareClass("HxTest.InventoryValidatorFacet", 
  HxTest.InventoryValidatorFacet, Humax.Facet);            
// apply the facet on Inventory class to the member "fillInventory"
Humax.applyFacet(new HxTest.InventoryValidatorFacet(), 
  HxTest.Inventory, "fillInventory");

The above facet has three methods beforeFillInventory, afterFillInventory and attachHandlers. The first two methods are actually for our own facet methods. These methods will be called before and after the execution of myInventory.fillInventory() method. The beforeFillInventory method displays pop-up the message "Going to fill" and afterFillInventory method displays pop-up the message "No of items has been filled".

BeforeAdvice, AfterAdvice and AroundAdvice Events

The base class Facet provides three events BeforeAdvice, AfterAdvice and AroundAdvice to specify before, after and around advices of an aspect respectively.

The BeforeAdvice and AfterAdvice events expect their handlers signature as "sourceObject, joinPointOriginalArgs" where sourceObject contains the actual object on which the current advice going to execute; joinPointOriginalArgs contains the arguments passed to the target method. In the above code, the sourceObject is myInventory and joinPointOriginalArgs is the arguments actually passed tofillInventory() methods.

The AroundAdvice is different from before and after advices. It expect their handlers signature as "sourceObject, sourceMethod, joinPointOriginalArgs". The next section will explain this in detail.

attachHandlers Method

The attachHandlers is the member of Facet class which is used to specify which implementation of the aspect should be executed on a particular advice. This method first calls the base class implementation.

Note that it is must to call base class attachHandlers implementation for doing some initialization work.

In the above implementation, we added the beforeFillOrder to before advice and afterFillOrder to after advice.

Humax provides applyFacet() method to add a facet to a Humax type. This is the way to specify point cut. The first argument should be a valid Facet instance and second argument should be target type on which this facet is going to apply. The third argument should be the actual pointcut. In this case, it is fillInventory().

Note: Version 0.2.1 onwards, Humax supports to specify pointcuts in regular expression format, for example Humax.applyFacet(new HxTest.MyFacet(), HxTest.MyClass, "get*"); which means the facet applies to all methods start with "get".

Now execute the application.

More Facet Programming & MetaData Programming

AroundAdvice

In the previous section, we have seen the usage of BeforeAdvice and AfterAdvice. In this section, we are going to see how to use AroundAdvice. We have taken the same code which we used in the previous section.

In this section, we have made one change on fillInventory():

...
fillInventory : function(quantity)
{                    
    
    if(quantity <= 0)
        throw new Humax.Exception("Invalid quantity", 
      "HxTest.Inventory.fillInventory()");
    
    if(this._available + quantity <= this._capacity)
    {
        this._available += quantity;
    }else
    {
        this.OnStockAlert(Humax.EventAction.Trigger, "Stock full",
      quantity,this._available);
    }
},
...

In the above code, see the bold text. If the number of item to fill is zero or less than zero, it throws exception with message "Invalid quantity". Let us assume that the caller of this method do not write exception handling in most of the code like:

function fillButton_onclick()
{                
    document.getElementById("output").innerHTML = "";
    myInventory.fillInventory(parseInt(
    document.getElementById("fillTextBox").value)
    );
    document.getElementById("status").innerHTML = 
    "Available Stock: " + myInventory.getAvailableStockCount();
}

But he needs to implement exception handling on all part wherever he called fillInventory(). This is called code scattering as we learned in previous section. But by implementing AroundAdvice in InventoryValidatorFacet, we can achieve this easily. Now we are going to implement AroundAdvice in InventoryValidatorFacet. For your convenience, the modified or newly inserted code is in bold.

HxTest.InventoryValidatorFacet.prototype = 
{                                                                
    beforeFillInventory : function(sourceObject, joinPointOriginalArgs)
    {                                                                                
        alert("Going to fill");                    
    },
    
    afterFillInventory : function(sourceObject, joinPointOriginalArgs)
    {                                                                                                    
        if(joinPointOriginalArgs[0] > 0)
            alert(joinPointOriginalArgs[0] + " item(s) has been filled");                    
    },
    
    aroundFillInventory : function(sourceObject, sourceMethod, joinPointOriginalArgs)
    {
        try
        {
            //callSource is the Facet method to invoke actual joinpoint implementation
            this.callSource(sourceObject, sourceMethod, joinPointOriginalArgs);
        }catch(e)
        {
            alert(e.getMessage());
        }    
    },
    
    attachHandlers : function()
    {
        this.callBaseMethod("attachHandlers");                                        
        this.BeforeAdvice(Humax.EventAction.AttachHandler,
      this.beforeFillInventory, this);                    
        this.AfterAdvice(Humax.EventAction.AttachHandler, 
      this.afterFillInventory, this);
        this.AroundAdvice(Humax.EventAction.AttachHandler, 
      this.aroundFillInventory, this);
    }
}

In order to stop displaying message on afterFillInventory, if quantity is less than or equal to zero, a condition is applied in afterFillInventory().

callSource Method

As we know that aroundFillInventory has one more argument called sourceMethod which refers the current join point. The next big thing is how can we invoke current join point within aroundFillInventory. Facet provides a method named callSource() which actually invoke the join point. You can write your AroundAdvice implementation around this.callSource()

Now execute the application.

Metadata using Facet

We can use metadata to pass information to various programming components which can be parsed at runtime by the applied components. The information can be anyone of the following:

  • Code versioning information
  • Descriptive information that require to change the program flow at run time.

Humax allows you to declare Metadata by Facet which may or may not be an aspect. Assume that, in the HxTest.Inventory class, we have planned to supply a key code to fillInventory() and orderItem() methods. These methods simply parse the content and proceed further if it is valid.

Declaring Metadata

HxTest.InventoryPassportFacet = function(keyCode)
{
    this.base();
    this.$usage = Humax.FacetUsage.AllowMultiple;
    this._keyCode = keyCode;                                
}

HxTest.InventoryPassportFacet.prototype = 
{
    getKeyCode : function() { return this._keyCode;}
}

Humax.declareClass("HxTest.InventoryPassportFacet", 
  HxTest.InventoryPassportFacet, Humax.Facet);

In the above code, we have defined a facet InventoryPassportFacet without advice support means that we stated that this facet is purely for metadata. And it has one getter method getKeyCode().

Finally, we apply this facet on Inventory class members fillInventory and orderItem at some other place of the code.

Humax.applyFacet(new HxTest.InventoryPassportFacet("abc123zyx987"), 
  HxTest.Inventory, "fillInventory", "orderItem");

As explained, these methods should get the currently passed keyCode value for further process, in our case, we are going to simply displays the keycode. See the bold lines in the following code.

fillInventory : function(quantity)
{                    
    var facet = Humax.Facet.getFacet("HxTest.InventoryPassportFacet", this, "fillInventory");
    alert("This is fill inventory: Your key code is " + facet.getKeyCode());
    
    if(quantity <= 0)
        throw new Humax.Exception("Invalid quantity", 
      "HxTest.Inventory.fillInventory()");
    if(this._available + quantity <= this._capacity)
    {
        this._available += quantity;
    }else
    {
        this.OnStockAlert(Humax.EventAction.Trigger, 
      "Stock full", quantity,this._available);
    }
},

orderItem : function(quantity)
{                                        
    var facet = Humax.Facet.getFacet("HxTest.InventoryPassportFacet",
    this, "orderItem");
    alert("This is order item: Your key code is " + facet.getKeyCode());
    
    if(quantity <= (this._available - this._reorderLevel))
    {
        this._available -= quantity;
    }else
    {
        this.OnStockAlert(Humax.EventAction.Trigger, 
      "Fill the stock, reached minium reorder level", quantity,this._available);
    }
}

Humax.Facet class provides getFacet() method to get a specified facet which applied and specified for the current exeuction context. It also provides getFacets() to get all facets applied and specified for the current execution context.

Now, execute the program.

For more details or your contribution in Humax web framework, visit http://humax.sourceforge.net or
write me to udooz@hotmail.com.

License

This article, along with any associated source code and files, is licensed under The GNU Lesser General Public License (LGPLv3)