Home

Awesome

event-when Build Status

This is an event library, but one in which events and listeners are coordinated through a single object. The emphasis throughout is on coordinating the global flow of the program.

It addresses what I find to be the pain points of JavaScript programming: when does code execute and how does it have access to the objects it needs? Most event libraries handle the first well enough for linear sequences of event firing, but they fail when multiple events need to happen, in any order, before triggering a response. It can also require a lot of closures or globals to handle manipulating state from event calls. This library is designed to address those needs.

Most event libraries suggest making objects (such as a button) into emitters; this is to promote separation of concerns, a good goal. But we want to coordinate events from multiple sources. So to do this, event-when is designed to allow you to attach the object to the event/handler/emit. It also allows you to listen for events before the corresponding object exists. This is more like having listeners on a form element responding to button clicks in the form.

There are several noteworthy features of this library:

Please note that no particular effort at efficiency has been made. This is about making it easier to develop the flow of an application. If you need something that handles large number of events quickly, this may not be the right library. Benchmarking a simple emit can be found in benchmark.js.

Using

In the browser, include index.js. It will store the constructor as EventWhen in the global space.

For node, use npm install index.js or, better, add it to the package.json file with --save appended.

Then require and instantiate an emitter:

var EventWhen = require('event-when');
emitter = new EventWhen();

Object Types

Method specification

These are methods on the emitter object.


<a name="emit"></a>

emit(str ev, obj data) --> emitter

Emit the event.

arguments

return

The emitter for chaining. The events may or may not be already handled by their handlers (generally will be, but if it hits the loop max, then it clocks over.

scope

Note that if ev contains the event separator, :, then the part after the colon is the scope. Handlers can react to the full string or to just the first part. The scope comes along into the handler to be called. The order of handling is from the most specific to the general (bubbling up).

In what follows, it is important to know that the handler signature is (data, scope, context, event). Note that scope is string while the context may or may not, depending on how the handler was setup. The function is called with the this pointing to the handler which contains a reference to emitter as well as other items.

As an example, if the event a:b:c is emitted, then a:b:c handlers fire, followed by handlers for a. The scope for both is b:c. emitter.scope('b:c') will call up the associated scope though generally the context of the handler is more appropriate.

Once an emit happens, all currently associated handlers will be called and those with limited time handling will be decremented. There are no supported methods for stopping the handling.

example

emitter.emit("text filled", someData);
emitter.emit("general event:scope", data);
emitter.on("general event", "respond with scoped target~");
emitter.emit("general event:scope", data);

<a name="storeEvent"></a>

storeEvent(str ev, off) --> emitter

This will setup a handler for the event ev which will cache its data and event object. This will be stored in the scope _cache value.

arguments

Same as emit.

return

The emitter for chaining.

example

emitter.storeEvent("text filled");
emitter.once("text filled", function (data) {
    //do something
});

<a name="monitor"></a>

monitor(listener arr/filter, listener) --> [filt, listener]

If you want to react to events on a more coarse grain level, then you can use the monitor method.

arguments

returns

Listener array of filter, function when assigning. Use this to remove the monitoring. The returned array also has a .orig property containing the original filter type.

example

emitter.monitor(/bob/, function(ev, data, emitter) {
    console.log("bob in ", ev, " with ", data);
    emitter.emit("mischief managed");
});

Note if you were to emit "bob" in the above monitor, then we would have an infinite loop.


<a name="when"></a>

when(arr/str events, str ev ) --> tracker

This is how to do some action after several different events have all fired. Firing order is irrelevant. The order is determined by how it was added.

arguments

return

Tracker object. This is what one can use to manipulate the sequence of events. See Tracker type

The tracker is listed under the scope of the event to emit.

string arguments

The events and the event to emit both have a mini-dsl that one can use to modify the output. For both, the dsl is initiated with a !. The last exclamation point is the one to be used. If you want a ! in the string other than that, you can end the string with a period and nothing after it to avoid accidental invocations.

The syntax is basically as follows: !timing Pipes Mode Spacing is optional.

For incoming messages, the timing refers to how many times to listen for the event before allowing the when to be emitted. It can have a second number which says how many more times it will listen and record the data, but it will not stop the emitting. The timing should be either an integer or the string oo representing infinity. A leading infinity does not make sense as the .when would never be emitted. So if there is a single oo, then it is assumed to be a non-blocking event. One can have 0 as the leading time which means no blocking as well. The default is euivalent to 1 0 and oo is equivalent to 0oo. Spaces are optional between a number and oo.

The pipes should be of the form =>pipename and basically feeds the data into a data cleaning/formatting function, registered in the .pipes command. These should ideally be simple, functional units. The signature of the pipe is (incoming, existing value) --> value. The value is used based on the default last character, which is to simply replace it.

The mode is a single character. For incoming events, these should be:

For the toEmit event, there is only a single number which is relevant. The number indicates how many times to reset the .when. oo will mean the .when always resets.

The pipe portion of the syntax is an opportunity to work on the data before emitting.

The last character has the following implications:

scope

If an incoming event is listed with : at the end (before the last ! if present), then it will have a scope added to it. If the third argument of .when is present, then that becomes the scope. If not present, then the scope is that of the event to be emitted's scope, if that is present. If neither is present, then there is no scope.

note

If an event fires more times than is counted and later the when is reset, those extra times do not get counted.

example

emitter.on("data gathered", function (data) {
    data.forEach(function (el) {
        switch (el[0]) {
            case "file read" :
                // reports fileobj
                console.log("file", el[1]);
            break;
            case "db returned" : 
                // reports dbobj
                console.log("db", el[1]);
            break;
        }
});
emitter.when(["file read", "db returned"], "data gathered");
emitter.when("something more", "data gathered");
emitter.emit("db returned", dbobj);
emitter.emit("file read:some", fileobj);
emitter.emit("something more");

emitter will automatically emit "data gathered" after third emit with data [ fileobj, dbobj, null ]

Notice that if the event is a parent event of what was emitted, then the full event name is placed in the third slot.


<a name="on"></a>

on(str ev, str action, function f, obj context ) --> Handler

Associates action with event ev for firing when ev is emitted. The optionals are detected based on kind so the order and relevance is optional.

arguments

return

The Handler which can be further manipulated, such as by once. It can be used to remove the handler though the action string is preferred.

The Handler has a variety of properties that can be used to understand the action to be taken:

example

This takes in some json,

emitter.on("json received", "parse json", function(data) {
  this.json = JSON.parse(data);
}, record, {});

<a name="off"></a>

off(str/array/fun/reg events, str action, bool nowhen) --> emitter

This removes handlers.

arguments

This function behavior changes based on the number of arguments

return

Emitter for chaining.

example

// removes action `empty trash` from "full:bob"
emitter.off("full trash:bob", "empty trash");
// removes all handlers to all events with :bob as last 
emitter.off(/\:bob$/);
// removes all listed events that have action "whatever"
emitter.off(["first", "second"], "whatever");
// function filter
emitter.off(function (ev) { return (ev === "what");}, "action now");

<a name="once"></a>

once(str event, action, int n, f, context) --> handler h

This attaches the actopm to fire when event is emitted. But it is tracked to be removed after firing n times. Given its name, the default n is 1.

arguments

return

The handler produced by the underlying 'on' call; it has the additional property of a count.

example

// talk with bob just once when event "bob" happens
// this will be the object brief
emitter.once("bob", "talk with bob", brief);
// talk with jack three times, using brief each time
emitter.once("jack", "talk with jack", 3, brief);

<a name="stop"></a>

stop(filter toRemove, bool neg) --> emitter

This is a general purpose maintainer of the queue. It will remove the events that match the first argument in some appropriate way.

arguments

returns

Emitter for chaining.

example

// stop crazy from acting
emitter.stop("crazy");
// stop all events
emitter.stop();
// stop next event on queue
emitter.stop(true);
// stop all events with button in title
emitter.stop(/^button/);

<a name="cache"></a>

cache(str request/arr [ev, data, timing], str returned, fun process/str emit, str emit) --> emitter

This is how to cache an event request. This will ensure that the given event will only be called once. The event string should be unique and the assumption is that the same data would be used. If not, one will have problems.

arguments


<a name="action"></a>

action(str name, f function, obj context ) --> depends

This allows one to associate a string with handler primitive for easier naming. It should be active voice to distinguish from event strings. Action with function is the primary use, but one can also have contexts associated with it. Most of the time it is better to associate a context with the on operation.

arguments

return

example

This example demonstrates that an action should be an action sentence followed by something that does that action. Here the emit event sends a doc string to be compiled. It does so, gets stored, and then the emitter emits it when all done. Note files is the context that the handler is called in.

emitter.action("compile document", function (data, scope, emitter) {
    var files = this;
    var doneDoc = compile(data);
    files.push(doneDoc);
    emitter.emit("document compiled", doneDoc);
}, files);

<a name="actions"></a>

actions(arr/bool/fun/reg/str filter, bool neg) --> obj

This returns an object with keys of actions and values of their handlers.

arguments

return

An object whose keys match the selection and values are the corresponding actions's value. If the value is an object, then that object is the same object and modifications on one will reflect on the other.

example

The following are various ways to return all actions that contain the word bob.

emitter.actions("bob"); 
emitter.actions(/bob/);
emitter.actions(function (str) {
    return str.indexOf("bob") !== -1;
});

In contrast, the following only returns the action with bob as the exact name.

emitter.actions(["bob"]);
emitter.action("bob");

The first one returns an object of the form {bob: handler} while the second returns the handler.


<a name="scope"></a>

scope(str key, obj) --> scope keys/ scope obj / emitter

This manages associated data and other stuff for the scoped event ev.

arguments

return

example

emitter.scope("bob", {bd: "1/1"});

emitter.scope("bob") === {bd:"1/1"};

emitter.scope() === ["bob"];

<a name="scopes"></a>

scopes(arr/bool/fun/reg/str filter, bool neg) --> obj

This returns an object with keys of scopes and values of their contexts.

arguments

return

An object whose keys match the selection and values are the corresponding scope's value. If the value is an object, then the returned object values are live and modifications on one will reflect on the other.

example

Following the example of bob in scope...

emitter.scopes("bob") === {bob: {bd :"1/1"} }

emitter.scopes("bob", true) == {}

<a name="events"></a>

events( arr/fun/reg/str partial, bool negate) --> arr keys

This returns a list of defined events that match the passed in partial condition.

arguments

The behavior depends on the nature of the first argument:

The second argument negates the match conditions.

returns

An array of event strings that match the passed in criteria.

example

// will match events gre, great, green...
emitter.events("gre");
// will match events ending with :bob
emitter.events(/\:bob$/);
// will match if only great. Could also pass in ["great"] instead.
emitter.events(function (ev) {
    return ev === "great";
});
// grab events from emitter2 and match those in emitter1
keys = emitter2.events();
emitter1.events(keys);

<a name="handlers"></a>

handlers(arr/fun/reg/str events, bool empty) --> obj evt:handlers

Get listing of handlers per event.

arguments

return

Object with keys of events and values of arrays of Handlers.

example

Let's say we have handlers for the events "bob wakes up" and "bob sleeps".

emitter.handlers("bob") === {
    "bob wakes up" : [handler1],
    "bob sleeps" : [handler2, handler3]
    }

<a name="error"></a>

error()

This is where errors can be dealt with when executing handlers. It is passed in the error object as well as the handler value, emit data, event object, and executing context. The current full handler can be found in the second entry of the cur array in the event object.

If you terminate the flow by throwing an error, be sure to set emitter._looping to false.

This is a method to be overwritten, not called.

example

emitter.error = function (e, handler, data, evObj, context) {
    console.log( "Found error: " + e + 
        " while executing " + handler + 
        " with data " + data + 
        " in executing the event " + evObj.cur[0] + 
        " with context " + context ); 
    emitter._looping = false; 
    throw Error(e); 
};

<a name="queueempty"></a> The function emitter.queueEmpty() fires when all events that are waiting have been called. The default is a noop, but one can attach a function to the emitter that does whatever it wants.


<a name="log"></a>

makeLog() --> fun

This creates a log function. It is a convenient form, but the log property should often be overwritten. If this is not invoked, then the log is a noop for performance/memory.

emitter.log expects a description as a first argument and then whatever else varies.

The log has various properties/methods of interest:

example

You should run the example in the example directory to get a feeling for what the logs produce.

emitter.makeLog();

...

emitter.log.logs(["emitted", "Executing"]);

<a name="filt"></a>

filter(filter type) --> function

This takes in something of filter type and outputs a function that accepts a string and returns a boolean whose value depends on whether a matching has occurred.


<a name="serial"></a>

serial(obj) --> string

This takes in an object, or objects, and prints out a string suitable for inspecting them. Functions are denoted by tick marks around the name, if there is one. Multiple arguments are output as if they were all encapsulated in an array.

<a name="emitter"></a>

Emitter Instance Properties

Each instance has, in addition to the prototype methods, the following public properties:

It also has "private" variables that are best manipulated by the methods.


<a name="handler"></a>

Handler

Handlers are the objects that respond to emitted events. They consist of an action string that describes and names the handler, a function to execute, and a context in which to execute. Handlers may also have various properties added to them, such as once handlers having a count.

In defining a handler, neither a function nor context is strictly required. If a function is not provided, it is assumed that the action string names a function in the action table to use. This is a dynamic lookup at the time of the emit.

A useful setup is to have a function in the action table, but to create a handler to respond to specific events with a given context. The context can either be a string, in which case it is taken to be a scope object in the emitter, or an object which will store the data.

new Handler (str action, fun f, str/obj context, emitter) -> handler

Handler methods

These are largely internally used, but they can be used externally.


<a name="execute"></a>

execute(data, str scope) --> nothing

This executes the handler. this === handler

arguments

note

The handler will find a function to call or emit an error. This is either from when the on event was created or from an action item.

The function is called with this === handler, and signature data, scope, context Context and scope may be undefined. The emitter can be obtained from the handler.

return

Nothing.


<a name="removal"></a>

removal(ev, emitter) -->

This removes the handlers from .when trackers. Used by .off.

arguments

return

Nothing.

example

handler.removal("whened", emitter); 

<a name="off"></a>

off(str/array/fun/reg events, str action, bool nowhen) --> emitter

This removes handlers.

arguments

This function behavior changes based on the number of arguments

return

Emitter for chaining.

example

// removes action `empty trash` from "full:bob"
emitter.off("full trash:bob", "empty trash");
// removes all handlers to all events with :bob as last 
emitter.off(/\:bob$/);
// removes all listed events that have action "whatever"
emitter.off(["first", "second"], "whatever");
// function filter
emitter.off(function (ev) { return (ev === "what");}, "action now");

Tracker

<a name="tracker"></a> Trackers are responsible for tracking the state of a .when call. It is fine to set one up and ignore it. But if you need it to be a bit more dynamic, this is what you can modify.

Tracker Properties

These are the instance properties

Tracker Methods

They all return tracker for chainability.

<a name="tracker-add" /> #### add(arr/str events)

Add events to tracking list.

arguments

This is the same form as the events option of .when. It should be an array, namely [event, event, ...] where event is either a string or an array of the form [event, n, pipe, initial]. A string is interpreted as an event to be tracked; a number indicates how many times to wait for. A pipe is a function that can act on the incoming data and an initial value allows the pipe to act as a reduce.

example

t.add(["neat]");
t.add(["neat", "some"]);
t.add([["some", 4]]);
<a name="tracker-remove" /> #### remove(arr/str events)

Removes event from tracking list.

arguments

Same as add events, except the numbers represent subtraction of the counting.

alias

rem

example

t.remove("neat");
t.remove(["neat", "some"]);
t.remove([["some", 4]]);
<a name="tracker-go" /> #### go()

Checks to see whether tracking list is empty; if so, the waiting event is emitted. No arguments. This is automatically called by the other methods/event changes.

<a name="tracker-cancel" /> #### cancel()

Cancel the tracking and abort with no event emitted. No arguments.

<a name="tracker-reinitialize" /> #### reinitialize()

Reinitializes the tracker. The existing waiting events get cleared and replaced with the original events array. All data is wiped. No arguments.

<a name="tracker-silence" /> #### silence()

This silences the passed in events or the last one added. In other words, it will not appear in the list of events. If an event is applied multiple times and silenced, it will be silent for the


Filter Type

<a name="#filter"></a> Several of the methods accept something of filter type. This could be a string, an array of strings, a regex, or a function. All of them are being used to filter strings based on matching. Most of the methods also allow for a negation boolean that will reverse the matching results.