*.dannyg

Observer pattern in Javascript

Feb
18

The observer pattern is extremely powerful in many cases where you want to have a Javascript class “listen” to another object.  The class or object attaches to a Subject and waits an update command and then notifies all Observers.    Another key concept here is the idea of inheritance in Javascript. 

I have a simple Javascript function that handles inheritance:

function inherits(base, extension)
{
    for (var property in base)
    {
        try
        {
            extension[property] = base[property];
        }
        catch(warning)
        {
        
        }
    }
}

 

Using this concept in mind I can now extend any additional functionalities of any Javascript class or object on the DOM.  Now let’s take a look at the Observer and Subject classes.

function Observer()
{
    this.Update = function(data)
    {
        alert(this.id + " Update() function has not been implemented");
    }
}
 
function Subject() {
    this.observers = new ArrayList();
}
 
Subject.prototype.Notify = function( context ) {
    var m_count = this.observers.Count();
            
    for( var i = 0; i < m_count; i++ )
        this.observers.GetAt(i).Update( context );
}
 
Subject.prototype.AddObserver = function( observer ) {
    if( !observer.Update )
        throw 'Wrong parameter';
 
    this.observers.Add( observer );
}
 
Subject.prototype.RemoveObserver = function( observer ) {
    if( !observer.Update )
        throw 'Wrong parameter';
   
    this.observers.RemoveAt(this.observers.IndexOf( observer, 0 ));
}

 

We can now go forward and actually implement the code on our HTML page.  Let say we have a series of form fields and we want to set that data equal to the value of another text box.  We could easily subscribe or unsubscribe to listen for updates.  We’ll then use a simple HTML button to send updates to the listening controls.

Our page looks like this in the browser

image

And now for the underlying HTML code.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>    
    <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script>
    <script type="text/javascript" src="../Resources/Javascripts/library.js"></script>
    <title>Observer Pattern Example</title>
</head>
<body>
    <form>
        <br />textbox1: <input type="text" id="textbox1" />
        <br />button2: <input type="button" id="button2" />
        <br />notify data: <input type="text" id="textbox3" />
        <input type="button" id="button1" value="Notify" onclick="NotifyObservers(this)" />
    </form>
</body>
</html>
    <script type="text/javascript">
        $(function() {
            inherits(new Observer(), $("#textbox1")[0]);
            inherits(new Observer(), $("#button2")[0]);
            inherits(new Subject(), $("#button1")[0]);
 
            $("#button1")[0].AddObserver($("#textbox1")[0]);
            $("#button1")[0].AddObserver($("#button2")[0]);
 
            $("#textbox1")[0].Update = function(data) {
                this.value = data;
            }
 
            $("#button2")[0].Update = function(data) {
                this.value = data;
            }
        });
 
        function NotifyObservers(subject) {
            subject.Notify($("#textbox3")[0].value);
        }
    </script>

 

As you can see the DOM objects themselves inherit the respective Javascript class functions and properties.  I’m using jQuery to directly call the functions on the objects.  When I fill in some value on the textbox3 that data gets passed through the individual implementations of the Update() method on each object that had been attached to the Subject.

image

The attached objects have decided to update their own values, but in theory I can do anything with that data.  A very powerful design pattern.

This also uses an ArrayList a feature not included in Javascript.  The ArrayList class is seen below.

function ArrayList() {
    this.aList = []; //initialize with an empty array
}
        
ArrayList.prototype.Count = function() {
    return this.aList.length;
}
        
ArrayList.prototype.Add = function( object ) {
    return this.aList.push( object ); //Object are placed at the end of the array
}
 
ArrayList.prototype.GetAt = function( index ) //Index must be a number {
    if( index > -1 && index < this.aList.length )
        return this.aList[index];
    else
        return undefined; //Out of bound array, return undefined
}
        
ArrayList.prototype.Clear = function() {
    this.aList = [];
}
 
ArrayList.prototype.RemoveAt = function ( index ) // index must be a number {
    var m_count = this.aList.length;
            
    if ( m_count > 0 && index > -1 && index < this.aList.length ) 
    {
        switch( index )
        {
            case 0:
                this.aList.shift();
                break;
            case m_count - 1:
                this.aList.pop();
                break;
            default:
                var head   = this.aList.slice( 0, index );
                var tail   = this.aList.slice( index + 1 );
                this.aList = head.concat( tail );
                break;
        }
    }
}
 
ArrayList.prototype.Insert = function ( object, index ) {
    var m_count       = this.aList.length;
    var m_returnValue = -1;
                
    if ( index > -1 && index <= m_count ) 
    {
        switch(index)
        {
            case 0:
                this.aList.unshift(object);
                m_returnValue = 0;
                break;
            case m_count:
                this.aList.push(object);
                m_returnValue = m_count;
                break;
            default:
                var head      = this.aList.slice(0, index - 1);
                var tail      = this.aList.slice(index);
                this.aList    = this.aList.concat(tail.unshift(object));
                m_returnValue = index;
                break;
        }
    }
                
    return m_returnValue;
}
 
ArrayList.prototype.IndexOf = function( object, startIndex ) {
    var m_count       = this.aList.length;
    var m_returnValue = - 1;
                
    if ( startIndex > -1 && startIndex < m_count ) 
    {
        var i = startIndex;
                    
        while( i < m_count )
        {
            if ( this.aList[i] == object )
            {
                m_returnValue = i;
                break;
            }
                        
            i++;
        }
    }
                
    return m_returnValue;
}
        
        
ArrayList.prototype.LastIndexOf = function( object, startIndex ) {
    var m_count       = this.aList.length;
    var m_returnValue = - 1;
                
    if ( startIndex > -1 && startIndex < m_count ) 
    {
        var i = m_count - 1;
                    
        while( i >= startIndex )
        {
            if ( this.aList[i] == object )
            {
                m_returnValue = i;
                break;
            }
                        
            i--;
        }
    }
                
    return m_returnValue;
}

 

Enjoy!  Tweet me if you have any questions @dannygnj

JQuery & JSONP

Feb
08