Strongly Typed Events in TypeScript using an event list (Part 3)

In a previous tutorial I explained how events can be implemented as properties on a class using Strongly Typed Events for TypeScript. In this tutorial I explore how an event list can be used to support scenarios with classes with a multitude of events. There is a way to decrease the number of private backing variables.

The EventList

So let's inspect the small EventList class that will create and store named events using a map. When an event is requested by name, it will be automatically created and stored.

/** Storage class for multiple events that are accessible by name.
 * Events are automatically created. */
class EventList<TSender, TArgs> {

    private _events: { [name: string]: EventDispatcher<TSender, TArgs>; } = {};

    get(name: string): EventDispatcher<TSender, TArgs> {

        let event = this._events[name];

        if (event) {
            return event;
        }

        event = new EventDispatcher<TSender, TArgs>();
        this._events[name] = event;
        return event;
    }

    remove(name: string): void {
        this._events[name] = null;
    }
}

The Stopwatch

A stopwatch is an object that has multiple events in real live: startpause, reset. I've modeled a simple stopwatch in a class and implemented the events using an EventList.

class Stopwatch {

    private _events: EventList<Stopwatch, StopwatchEventArgs> = new EventList<Stopwatch, StopwatchEventArgs>();
    private _ticks: number = 0;
    private _timer: number;

    get onStart(): IEvent<Stopwatch, StopwatchEventArgs> {
        return this._events.get('onStart');
    }

    get onPause(): IEvent<Stopwatch, StopwatchEventArgs> {
        return this._events.get('onPause');
    }

    get onReset(): IEvent<Stopwatch, StopwatchEventArgs> {
        return this._events.get('onReset');
    }

    private dispatch(name: string) {
        this._events.get(name).dispatch(
            this,
            new StopwatchEventArgs(this._ticks, this.display())
        );
    }

    start(): void {

        if (this._timer == null) {
            this._timer = Date.now();
            this.dispatch('onStart');
        }
    }

    pause(): void {

        if (this._timer) {
            this._ticks = this.getTicks();
            this._timer = null;
            this.dispatch('onPause');
        }
    }

    reset(): void {
        this._ticks = 0;
        this._timer = Date.now();
        this.dispatch('onReset');
    }

    getTicks(): number {
        if (this._timer) {
            return (Date.now() - this._timer) + this._ticks;
        }

        return this._ticks;
    }

    display(): string {

        var ticks = this.getTicks();

        //get seconds from ticks
        var ts = ticks / 1000;

        //conversion based on seconds
        let hh: any = Math.floor(ts / 3600);
        let mm: any = Math.floor((ts % 3600) / 60);
        let ss: any = (ts % 3600) % 60;

        //prepend '0' when needed
        hh = hh < 10 ? '0' + hh : hh;
        mm = mm < 10 ? '0' + mm : mm;
        ss = ss < 10 ? '0' + ss : ss;

        //use it
        var str = hh + ":" + mm + ":" + ss;

        return str;
    }
}

class StopwatchEventArgs {
    private _ticks: number;
    private _display: string;

    get ticks(): number {
        return this._ticks;
    }

    get display(): string {
        return this._display;
    }

    constructor(ticks: number, display: string) {
        this._ticks = ticks;
        this._display = display;
    }
}

To the outside, events are exposed as one expects:

var sw = new Stopwatch();

sw.onStart.subscribe((sender, args) => {
    alert('Stopwatch started after ' + args.display + '.');    
});

sw.onPause.subscribe((sender, args) => {
    alert('Paused after ' + args.display + '.');
});

sw.start();

window.setTimeout(function () {
    sw.pause();
}, 3000);

window.setTimeout(function () {
    sw.start();
}, 4000);

See it in action in JS Fiddle: https://jsfiddle.net/KeesCBakker/5qbj06rk/

More

Check out the following:

expand_less