Click here to Skip to main content
15,886,864 members
Articles / Programming Languages / Javascript

salient.Delegate - a non-DOM JavaScript Delegate Implementation

Rate me:
Please Sign up or sign in to vote.
4.33/5 (2 votes)
18 Apr 2010CPOL6 min read 11.7K   10  
salient.Delegate is a class that enables the implementation of numerous software design patterns that can accelerate design and development and improve the architecture of your JavaScript code by enabling reliable loose coupling of JavaScript code.

download this article in .doc format

Introduction

salient.Delegate is a class that enables the implementation of numerous software design patterns that can accelerate design and development and improve the architecture of your JavaScript code by enabling reliable loose coupling of JavaScript code.

Overview

The intention of salient.Delegate is to provide a lightweight, robust and easy to use JavaScript delegate that can be used to implement a multitude of design patterns including Event, Observer, Command, Chain of Responsibility patterns.

These patterns can drastically simplify the architecture of an application by means enabling simple and robust implementations of dependancy inversion and clear seperation of concerns.

Client script applications can be designed in a more modular, or encapsulated, fashion facilitating isolated testing and in turn enabling agile methodologies.

Don't let all of these buzz words frighten you. I am simply describing things that we all do, hopefully, in some degree every day, knowingly or not.

While this article is not intended to be any sort of primer on patterns, we certainly will explore a few that should be quite familiar even if not by their formal name.

Disclaimer:

  • This class is not intended to handle DOM events. Of course it can be purposed to that end but there are much more appropriate implementations, notably Dean Edwards' Event implementation.
  • The descriptions and interpretations of GOF design patterns presented are just that, interpretations and approximations. Any input regarding these implementations are especially welcome.

Usage

Lets first examine the API:

Listing 1: salient.Delegate API

var Delegate = function(name)
{
    /// <summary>
    /// A simple event Delegate implementation
    /// </summary>
    /// <param name="name" type="String">The name of the event.</param>
    /// <returns type="salient.delegate"/>

};

Delegate.prototype.addHandler = function(fn, context)
{
    /// <summary>
    /// Adds supplied function reference to the list of functions to be called upon raise of 
    /// this event and returns a token that can be used to remove the DelegateContext
    /// </summary>
    /// <param name="fn" type="function">The function to be called by this delegate.</param>
    /// <param name="context" type="Object">
    /// The context in which the handler should be called. (the this).
    /// If ommited the context will be that of the delegateContext. Probably not the desired effect.
    /// </param>
    /// <returns type="String">
    /// The unique identifier of this handler. To be used to remove this handler in the
    /// event that the function is not available for identification.
    /// </returns>
};
Delegate.prototype.removeHandler = function(handle)
{
    /// <summary>
    /// Removes a function reference from the list of handlers to this event.
    /// You can pass the numeric token issued when the DelegateContext was
    /// attached or the DelegateContext itself. Closures obviously cannot be passed.
    /// Be sure to retain the handle if needed.
    /// </summary>
    /// <param name="handle" type="Object">
    /// the handle token issued or the function reference it was assigned to
    /// </param>
};

Delegate.prototype.invoke = function()
{
    /// <summary>
    /// calls the function ref for each DelegateContext that subscribed to
    /// this event with supplied arguments.
    ///
    /// if the function returns the static DelegateCancellation the handler chain is terminated.
    /// any other return values are returned to the caller. If multiple handlers return values they 
    /// will be returned as an array.
    /// </summary>
    /// <returns type="Object">the return value(s) of the handler(s). If multiple an array is returned.</returns>
};

 

Event/Observer Pattern

Listing 2: Simple event/observer pattern example

var observedType = function()
{
    var _foo;

    var change = this.propertyChange = new salient.Delegate("observed.propertyChange");

    function onChange(name, value)
    {
        change.invoke(name, value);
    }

    this.setFoo = function(value)
    {
        _foo = value;
        onChange("foo",_foo);
    }
    this.getValue = function()
    {
        return _value;
    }
}


var observerType = function()
{
    var observed = this.observed = new observedType();

    function observedPropertyChanged(name, value)
    {
        alert("observed property " + name + " new value is " + value);
    }

    observed.propertyChange.addHandler(observedPropertyChanged, this);

}

var observer = new observerType();

observer.observed.setFoo("bar");

// prop change invokes the handler on observer

 

Chain of Responsibility

A traditional Chain Of Responsibility pattern uses a linked list of handlers and the invocation chain is controlled by explicit passing of responsibility within the handlers themselfs. This requires that not only the owner of the COR be aware of all of the handlers, each handler is aware of the next.  Additionally, logic must be emplaced to manage a linked list, adding to the complexity of the implementation.

With a bit of imagination we can generalize this pattern and envision it as a simple array of handlers that are invoked in order of appearance, each invocation posessing the power to terminate the invocation chain by signalling cancellation in the return value.

Using salient.delegate, a Chain Of Responsibility is quite easy to implement because that is the design pattern that salient.Delegate's implementation most closely resembles, albeit shaped a bit differently as described above.

With deliberate addition of handlers you may construct your chain of responsibility and pass the subject as an argument in the .invoke() invocation. If, during any handler invocation, the chain should stop, simply return salient.DelegateCancel.

I will leave it to the reader to implement methods to manipulate  the  handler chain after creation. The handler array is exposed as delegateInstance.handlers.

Listing 3: Simple Chain Of Responsibility pattern example using salient.DelegateCancel

// using the CoinHandler example from BlackWasp
// http://www.blackwasp.co.uk/ChainOfResponsibility.aspx

var Coin = function(diameter, weight)
{
    this.Weight = weight;
    this.Diameter = diameter;
}

var FivePenceHandler = function()
{
    this.handleCoin = function(coin)
    {
        if (Math.abs(coin.Weight - 3.25) < 0.02 && Math.abs(coin.Diameter - 18) < 0.1)
        {
            alert("Captured 5p");
            // instead of falling through and explicitely invoking the next
            // handler in a linked list we simply return a cancellation token
            // and let Delegate break the invocation chain.            
            return salient.DelegateCancel;
        }
    }
}

var TenPenceHandler = function()
{
    this.handleCoin = function(coin)
    {
        if (Math.abs(coin.Weight - 6.5) < 0.03 && Math.abs(coin.Diameter - 24.5) < 0.15)
        {
            alert("Captured 10p");
            return salient.DelegateCancel;
        }
    }
}

var TwentyPenceHandler = function()
{
    this.handleCoin = function(coin)
    {
        if (Math.abs(coin.Weight - 5) < 0.01 & Math.abs(coin.Diameter - 21.4) < 0.1)
        {
            alert("Captured 10p");
            return salient.DelegateCancel;
        }

    }
}

var FiftyPenceHandler = function()
{
    this.handleCoin = function(coin)
    {
        if (Math.abs(coin.Weight - 8) < 0.02 && Math.abs(coin.Diameter - 27.3) < 0.15)
        {
            alert("Captured 50p");
            return salient.DelegateCancel;
        }

    }
}

var OnePoundHandler = function()
{
    this.handleCoin = function(coin)
    {
        if (Math.abs(coin.Weight - 9.5) < 0.02 && Math.abs(coin.Diameter - 22.5) < 0.13)
        {
            alert("Captured £1");
            return salient.DelegateCancel;
        }
    }
}

// an addition to the black wasp implementation
var CounterfeitCoinHandler = function()
{
    this.handleCoin = function(coin)
    {
        alert("Coin is counterfeit");
    }
}

var h5 = new FivePenceHandler();
var h10 = new TenPenceHandler();
var h20 = new TwentyPenceHandler();
var h50 = new FiftyPenceHandler();
var h100 = new OnePoundHandler();
var bogus = new CounterfeitCoinHandler();

var coinChainOfResponsibility = new salient.Delegate("CoinCOR");

coinChainOfResponsibility.addHandler(h5.handleCoin, h5);
coinChainOfResponsibility.addHandler(h10.handleCoin, h10);
coinChainOfResponsibility.addHandler(h20.handleCoin, h20);
coinChainOfResponsibility.addHandler(h50.handleCoin, h50);
coinChainOfResponsibility.addHandler(h100.handleCoin, h100);
coinChainOfResponsibility.addHandler(bogus.handleCoin, bogus);

var tenPence = new Coin(24.49, 6.5);
var fiftyPence = new Coin(27.31, 8.01);
var counterfeitPound = new Coin(22.5, 9);

coinChainOfResponsibility.invoke(tenPence);
coinChainOfResponsibility.invoke(fiftyPence);
coinChainOfResponsibility.invoke(counterfeitPound);

 

Alternately you could implement this pattern using return values. Handler return values are returned to the .invoke() call. If multiple values are returned, e.g. multiple handlers with return values, the .invoke() return value is an array.

Listing 4: Simple Chain Of Responsibility pattern example using return values

// using the CoinHandler example from BlackWasp
// http://www.blackwasp.co.uk/ChainOfResponsibility.aspx

var Coin = function(diameter, weight)
{
    this.Weight = weight;
    this.Diameter = diameter;
}

var FivePenceHandler = function()
{
    this.handleCoin = function(coin)
    {
        if (Math.abs(coin.Weight - 3.25) < 0.02 && Math.abs(coin.Diameter - 18) < 0.1)
        {
            return "5p";
        }
    }
}

var TenPenceHandler = function()
{
    this.handleCoin = function(coin)
    {
        if (Math.abs(coin.Weight - 6.5) < 0.03 && Math.abs(coin.Diameter - 24.5) < 0.15)
        {
            return "10p";
        }
    }
}

var TwentyPenceHandler = function()
{
    this.handleCoin = function(coin)
    {
        if (Math.abs(coin.Weight - 5) < 0.01 & Math.abs(coin.Diameter - 21.4) < 0.1)
        {
            return "10p";
        }

    }
}

var FiftyPenceHandler = function()
{
    this.handleCoin = function(coin)
    {
        if (Math.abs(coin.Weight - 8) < 0.02 && Math.abs(coin.Diameter - 27.3) < 0.15)
        {
            return "50p";
        }

    }
}

var OnePoundHandler = function()
{
    this.handleCoin = function(coin)
    {
        if (Math.abs(coin.Weight - 9.5) < 0.02 && Math.abs(coin.Diameter - 22.5) < 0.13)
        {
            return "£1";
        }
    }
}


var h5 = new FivePenceHandler();
var h10 = new TenPenceHandler();
var h20 = new TwentyPenceHandler();
var h50 = new FiftyPenceHandler();
var h100 = new OnePoundHandler();

var coinChainOfResponsibility = new salient.Delegate("CoinCOR");

coinChainOfResponsibility.addHandler(h5.handleCoin, h5);
coinChainOfResponsibility.addHandler(h10.handleCoin, h10);
coinChainOfResponsibility.addHandler(h20.handleCoin, h20);
coinChainOfResponsibility.addHandler(h50.handleCoin, h50);
coinChainOfResponsibility.addHandler(h100.handleCoin, h100);

var tenPence = new Coin(24.49, 6.5);
var fiftyPence = new Coin(27.31, 8.01);
var counterfeitPound = new Coin(22.5, 9);

alert(coinChainOfResponsibility.invoke(tenPence) || "bogus coin");
alert(coinChainOfResponsibility.invoke(fiftyPence) || "bogus coin");
alert(coinChainOfResponsibility.invoke(counterfeitPound) || "bogus coin");

NOTE: In that the Coin argument is an object  it is being passed by reference and can me manipulated by each handler in turn. This enables the implementation of what I would like to call a Chain Of Custody pattern.

Command Pattern

Included is a simple interpretation of the Command pattern. The Command patterns allows loose coupling of a function and the code that invokes it. The target needs no foreknowledge of possible invokers and invokers can be added at any time and need no foreknowledge of the command they are meant to invoke other than the command key.  Compare this to the Event pattern in which you explicitly bind a reference to a handler function to the event. Command pattern presents one more level of decoupling. Decoupling with Command pattern can be taken to the extreme of completely encapsulating the behaviour of an object, making private functions invocable via a command. This is a powerful concept that is not typically associated with JavaScript code.

A typical use case for a Command pattern can be inferred from any typical windows application. An application generally has some type of 'Save' functionality. You can generally invoke this functionality from an arbitrary number of places. e.g. Main Menu>File>Save, Toolbar>Save button, Keyboard> CTRL-S, etc.

Instead of writing code to hardwire each of these triggers to the Save function, we can use a Command pattern that drastically simplifies the architecture and implementation of an application.

I am explaining this pretty badly. Let me show some code.

Listing 5: salient.Commands API

var Commands = function()
{
    /// <summary>
    /// A simple event dispatcher. Useful for implementing a command patter.
    /// Sole purpose is to maintain a hash of unbound delegates that can be 
    /// attached to and invokeed by anyone using string keys.
    /// </summary>
    /// <returns type="salient.Commands"></returns>
}

Commands.prototype.publish = function(name)
{
    /// <summary>
    /// Creates a named event if not exists
    /// </summary>
    /// <param name="name" type="String">The command key. Unique to the command manager</param>
}

Commands.prototype.unpublish = function(name)
{
    /// <summary>
    /// Detaches and removes the named command, if present.
    /// </summary>
    /// <param name="name" type="String">The command key. Unique to the command manager</param>
}

Commands.prototype.subscribe = function(fn, context, name)
{
    /// <summary>
    /// Registers function as an eventHandler for named command. The command is published if necessary
    /// </summary>
    /// <param name="fn" type="function">The function to be called on this event</param>
    /// <param name="context" type="Object">The context in which the handler should be called. (the this).</param>
    /// <param name="name" type="String">The command key. Unique to the command manager</param>
    /// <returns type="String">event handle token</returns>
}

Commands.prototype.unsubscribe = function(name, handle)
{
    /// <summary>
    /// Detaches eventHandler from command. If handle is null all eventHandlers for named command are detached.
    /// </summary>
    /// <param name="name" type="String">The command key. Unique to the command manager</param>
    /// <param name="handle" type="Object" optional="true">
    /// Optional Function or token. If empty the named command is detached from all eventHandlers
    /// </param>
}

Commands.prototype.invoke = function(name, sender, args)
{
    /// <summary>
    /// Signals all eventHandlers to this command with the source (sender) and args
    /// </summary>
    /// <param name="name" type="String">The command key. Unique to the command manager</param>
    /// <param name="sender" type="Object" optional="true">Optional. </param>
    /// <param name="args" type="Object" optional="true">Optional. </param>
    /// <exception cref="Error">
    /// If command is not registered.
    /// </exception>
}

Commands.prototype.clear = function()
{
    /// <summary>
    /// Detaches and clears all commands
    /// </summary>
}

Listing 6: A simple Command pattern example

// a simple Command pattern implementation that enforces
// a clean encapsulation and seperation of concerns.

var myAppCore = new function()
{
    // creating a private reference and a priveledge accessor
    // in one expression.
    var commands = this.commands = new salient.Commands();

    // command targets use standard event handler signature
    function save(sender, args)
    {
        alert("I saved " + args);
        // the event handler signature protocol also presents
        // a reference to the invoker in the sender argument.
        // This violates the spirit of seperation but enables
        // a lot of interesting implementation possibilities.
        
    }

    // add a keyed command that exposes a private function
    // subscribing also publishes the command if it is not already

    commands.subscribe(save, this, "SAVE");
    
}


var myAppSaveButton =
{
    click: function()
    {
        myAppCore.commands.invoke("SAVE", this, "name of doc  (invoked via button)");
    }
}

var myAppKeyboardSave=
{
    press: function()
    {
        myAppCore.commands.invoke("SAVE", this, "name of doc (invoked via keyboard)");
    }
}

myAppSaveButton.click();
myAppKeyboardSave.press();

This implementation publishes core functionality, Save, as a command named "SAVE".   If an invocation is attempted on a non existant command, an exception is thrown.

A further level of decoupling can be accomplished by simply publishing a command in the application scope.  You can then attach/detach subscribers at will. Invocation of commands that are published but have no subscribers results in a noop.

In that the Command pattern is especially useful in a single threaded runtime like JavaScript I will revisit this subject and present a more formal implementation of the Command pattern, including command journalling to enable 'undo'/'redo' in a later article.

Implementation

salient.Delegate, at it's core, is a variation of a Chain of Responsibility pattern in that it is basically an array of context objects (handlers) that are called in sequence upon invocation of the delegate.  In addition to the standard behaviour you would expect from an event,  the addition of return value(s), arbitrary numbers of arguments, arbitrary scoping and the capability of cancelling the invocation chain leads me to characterize it as a Delegate for lack of a better term.

Initially, the invocation chain was implemented as a simple iteration of an array of callbacks. While this works quite well when everything is in it's proper place, an error during the invocation of a handler leaves us with 2 options: silently swallow the error and continue the invocation chain or let the error propigate and break the invocation chain.

What I really had in mind was a more robust behaviour more closely resembling the native handling of events, in that an error can occur in one handler that can be handled or not but does not affect the invocation of any subsequent handlers.

I struggled with this for quite some time until I happened to visit Dean's blog and read this post, Callbacks vs Events, in which he describes a means of wrapping a callback in the native event model. Exactly what I was after. With a not-too-significant degree of adaptation I was able to implement his idea without altering the advertised behavior or API of salient.Delegate.

Listing 7: salient.Delegate and salient.Commands source

// 
// salientJS JavaScript Library v1.0
// http://skysanders.net/code/salientJs/
// 
// Copyright (c) 2009 Sky Sanders - sky@skysanders.net
// Dual licensed under the MIT and GPL licenses.
// http://skysanders.net/code/salientJs/license.txt
//
// this code for this demo is an exerpt from the salientJS javascript library.
// for full functionality see the main project

var salient = {};
(function()
{
    var DelegateContext = function(fn, context, token)
    {
        /// <summary>
        /// Holds a function reference to be called in response to an event.
        /// </summary>
        /// <param name="fn" type="function">The function to be called on this event</param>
        /// <param name="context" type="Object">The context in which the handler should be called. (the this).</param>
        /// <param name="token" type="String">
        /// The unique identifier of this handler. To be used to remove this handler in the event that the function is not available for identification.
        /// </param>
        /// <field name="fn" type="function">The function to be called on this event.</field>
        /// <field name="context" type="Object">The context in which the handler should be called. (the this).</field>
        /// <field name="token" type="String">
        /// The unique identifier of this handler. To be used to remove this handler in the event that the function is not available for identification.
        /// </field>
        /// <returns type="DelegateContext"></returns>
        this.fn = fn;
        this.token = token;
        this.context = context;

    };


    DelegateContext.prototype.destroy = function()
    {
        /// <summary>
        /// nulls all references contained by this handler
        /// </summary>
        /// <returns type="null">for use in destroy pattern</returns>

        this.fn = null;
        this.token = null;
        this.context = null;
        return null;
    };


    var Delegate = function(name)
    {
        /// <summary>
        /// A simple event Delegate implementation
        /// </summary>
        /// <param name="name" type="String">The name of the event.</param>
        /// <returns type="salient.delegate"/>

        if (name instanceof salient.Delegate)
        {
            return name;
        }

        this.name = name;
        this.id = delegate_guid++;
        this.handlers = [];
    };


    Delegate.prototype.addHandler = function(fn, context)
    {
        /// <summary>
        /// Adds supplied function reference to the list of functions to be called upon raise of 
        /// this event and returns a token that can be used to remove the DelegateContext
        /// </summary>
        /// <param name="fn" type="function">The function to be called by this delegate.</param>
        /// <param name="context" type="Object">
        /// The context in which the handler should be called. (the this).
        /// If ommited the context will be that of the delegateContext. Probably not the desired effect.
        /// </param>
        /// <returns type="String">
        /// The unique identifier of this handler. To be used to remove this handler in the
        /// event that the function is not available for identification.
        /// </returns>
        var handler = this.find(fn);
        if (!handler)
        {
            delegate_guid++;
            var token = this.name + "_" + this.id + "_" + delegate_guid;
            handler = new DelegateContext(fn, context, token);
            this.handlers.push(handler);
        }
        return handler.token;
    };

    Delegate.prototype.removeHandler = function(handle)
    {
        /// <summary>
        /// Removes a function reference from the list of handlers to this event.
        /// You can pass the numeric token issued when the DelegateContext was
        /// attached or the DelegateContext itself. Closures obviously cannot be passed.
        /// Be sure to retain the handle if needed.
        /// </summary>
        /// <param name="handle" type="Object">
        /// the handle token issued or the function reference it was assigned to
        /// </param>

        var i = this.indexOf(handle);
        if (i < 0)
        {
            throw new Error("handle does not exists");
        }

        var handler = this.handlers[i];
        handler = handler.destroy();
        this.handlers[i] = null;
        //this.handlers.splice(i, 1);

    };

    Delegate.prototype.invoke = function()
    {
        /// <summary>
        /// calls the function ref for each DelegateContext that subscribed to
        /// this event with supplied arguments.
        ///
        /// if the function returns the static DelegateCancellation the handler chain is terminated.
        /// any other return values are returned to the caller. If multiple handlers return values they 
        /// will be returned as an array.
        /// </summary>
        /// <returns type="Object">the return value(s) of the handler(s). If multiple an array is returned.</returns>
        var results = [];
        for (var i = 0; i < this.handlers.length; i++)
        {
            var handler = this.handlers[i];

            if (handler !== null)
            {
                handler.args = arguments;

                currentHandler = handler;

                dispatchFakeEvent();

                if (handler.result instanceof DelegateCancellation)
                {
                    break; // cancel the execution chain
                }
                if (typeof (handler.result) !== "undefined")
                {
                    results.push(handler.result);
                }
            }
        }

        return results.length === 0 ? undefined : (results.length === 1 ? results[0] : results);
    };

    Delegate.prototype.destroy = function()
    {
        /// <summary>
        /// detaches all handlers from this event.
        /// </summary>
        /// <returns type="null">for use in destroy pattern</returns>

        for (var i = 0; i < this.handlers.length; i++)
        {
            var handler = this.handlers[i];
            handler = handler.destroy();
        }

        this.handlers = [];
        return null;
    };

    Delegate.prototype.count = function()
    {
        /// <summary>
        /// Handler count
        /// </summary>
        /// <returns type="Number">handler count</returns>

        return this.handlers.length;
    };

    Delegate.prototype.indexOf = function(handle)
    {
        /// <summary>
        /// Returns the index of the handle specified. Returns -1 if not found.
        /// </summary>
        /// <param name="handle" type="Object" mayBeNull="false" optional="false">the handle token issued or the function reference it was assigned to</param>
        /// <returns type="Number"></returns>

        for (var i = 0; i < this.handlers.length; i++)
        {
            if (this.handlers[i] === null)
            {
                continue;
            }
            if (this.handlers[i].token === handle || this.handlers[i].fn === handle)
            {
                return i;
            }
        }
        return -1;
    };

    Delegate.prototype.find = function(handle)
    {
        /// <summary>
        /// Find a handler by token or function reference
        /// </summary>
        /// <param name="handle" type="Object" mayBeNull="false" optional="false">the handle token issued or the function reference it was assigned to</param>
        /// <returns type="salient.DelegateContext"></returns>

        var i = this.indexOf(handle);
        if (i > -1)
        {
            return this.handlers[i];
        }
    };

    var delegate_guid = 1;
    var DelegateCancellation = function()
    {
        // return from handler to cancel event
    }

    // todone - implement http://dean.edwards.name/weblog/2009/03/callbacks-vs-events/

    var currentHandler; // staging so event dispatch can pick up current

    // initialize an event dispatch to wrap our callbacks

    if (document.addEventListener)
    {
        document.addEventListener("___salient_delegate", function()
        {
            var handler = currentHandler; // scoping for safety. need some tests
            handler.result = handler.fn.apply(handler.context, handler.args);
        }, false);

        var dispatchFakeEvent = function()
        {
            var fakeEvent = document.createEvent("UIEvents");
            fakeEvent.initEvent("___salient_delegate", false, false);
            document.dispatchEvent(fakeEvent);
        };
    }
    else
    {
        function initEvents()
        {
            document.documentElement.___salient_delegate = 0;
            document.documentElement.attachEvent("onpropertychange", function()
            {
                if (event.propertyName == "___salient_delegate")
                {
                    var handler = currentHandler; // scoping for safety. need some tests
                    handler.result = handler.fn.apply(handler.context, handler.args);
                }
            });
            dispatchFakeEvent = function(handler)
            {
                // fire the propertychange event    
                document.documentElement.___salient_delegate++;
            };
        }
        // hiding from background compiler. no graceful way to do this.

        try { initEvents(); } catch (ex) { }
    }




    var Commands = function()
    {
        /// <summary>
        /// A simple event dispatcher. Useful for implementing a command patter.
        /// Sole purpose is to maintain a hash of unbound delegates that can be 
        /// attached to and invokeed by anyone using string keys.
        /// </summary>
        /// <returns type="salient.Commands"></returns>

        this._events = {}; // private static. leave me alone
    }

    Commands.prototype.publish = function(name)
    {
        /// <summary>
        /// Creates a named event if not exists
        /// </summary>
        /// <param name="name" type="String">The command key. Unique to the command manager</param>

        if (!this._events[name])
        {
            this._events[name] = new salient.Delegate(name);
        }
    }

    Commands.prototype.unpublish = function(name)
    {
        /// <summary>
        /// Detaches and removes the named command, if present.
        /// </summary>
        /// <param name="name" type="String">The command key. Unique to the command manager</param>
        if (this._events[name])
        {
            this._events[name] = this._events[name].destroy();
        }
    }

    Commands.prototype.subscribe = function(fn, context, name)
    {
        /// <summary>
        /// Registers function as an eventHandler for named command. The command is published if necessary
        /// </summary>
        /// <param name="fn" type="function">The function to be called on this event</param>
        /// <param name="context" type="Object">The context in which the handler should be called. (the this).</param>
        /// <param name="name" type="String">The command key. Unique to the command manager</param>
        /// <returns type="String">event handle token</returns>

        if (!this._events[name])
        {
            this.publish(name);
        }

        return this._events[name].addHandler(fn, context);
    }


    Commands.prototype.unsubscribe = function(name, handle)
    {
        /// <summary>
        /// Detaches eventHandler from command. If handle is null all eventHandlers for named command are detached.
        /// </summary>
        /// <param name="name" type="String">The command key. Unique to the command manager</param>
        /// <param name="handle" type="Object" optional="true">
        /// Optional Function or token. If empty the named command is detached from all eventHandlers
        /// </param>

        if (this._events[name])
        {
            if (handle)
            {
                this._events[name].removeHandler(handle);
            }
            else
            {
                this._events[name].destroy();
            }
        }

    }


    Commands.prototype.invoke = function(name, sender, args)
    {
        /// <summary>
        /// Signals all eventHandlers to this command with the source (sender) and args
        /// </summary>
        /// <param name="name" type="String">The command key. Unique to the command manager</param>
        /// <param name="sender" type="Object" optional="true">Optional. </param>
        /// <param name="args" type="Object" optional="true">Optional. </param>
        /// <exception cref="Error">
        /// If command is not registered.
        /// </exception>
        var cmd = this._events[name];
        if (!cmd)
        {
            throw new Error(0, name + " is not a registered command");
        }
        cmd.invoke(sender, args);
    }



    Commands.prototype.clear = function()
    {
        /// <summary>
        /// Detaches and clears all events
        /// </summary>

        for (var name in this._events)
        {
            if (this._events.hasOwnProperty(name))
            {
                this._events[name] = this._events[name].destroy();
            }
        }
    }


    // public API
    this.DelegateCancel = new DelegateCancellation();
    this.Delegate = Delegate;
    this.DelegateContext = DelegateContext;
    this.Commands = Commands;


    // sugar properties that enhance the visual studio design time experience    
    Commands.__class = true;
    Commands.__typeName = "salient.Commands";
    Delegate.__class = true;
    Delegate.__typeName = "salient.Delegate";
    DelegateContext.__class = true;
    DelegateContext.__typeName = "salient.DelegateContext";
    this.__namespace = true;
    this.__typename = "salient";

}).call(salient);

 

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) Salient Solutions
United States United States
My name is Sky Sanders and I am an end-to-end, front-to-back software solutions architect with more than 20 years experience in IT infrastructure and software development, the last 10 years being focused primarily on the Microsoft .NET platform.

My motto is 'I solve problems.' and I am currently available for hire.

I can be contacted at sky.sanders@gmail.com

Comments and Discussions

 
-- There are no messages in this forum --