One Event Bus to Rule Them All! d3 dispatch, the d3 event system, notes

Learning d3 events this afternoon. These are the notes I'm taking while I'm learning it. I hope it helps.

It's called d3.dispatch. d3.dispatch('eventname1','eventname2') returns an object that manages setting event handlers, and dispatching events. The event system isn't global (unlike in most frameworks, where the event system appears to be globally available)- it's contained entirely within the object.

var dispatch = d3.dispatch('say');

That created an object that has a method dispatch.say() that dispatches/rasies/throws/broadcasts the "say" event to the listeners.

The listeners are all stored within the dispatch object. You set the listeners like this:

dispatch.on('say', function() {
    console.log('say event');
});

A complete example, which you can run, is:

dispatch = d3.dispatch('say');
dispatch.on('say', function() {
    console.log('say event');
});
dispatch.say();

http://jsfiddle.net/ht6kb8bn/

Namespaces

dispatch events can have only one handler. This is a slight contrast with most framework event systems that allow multiple handlers or listeners for each event. (I suspect this conflicts with the pubsub pattern.)

d3 has a different feature called "namespaces". You can add a dot after the event, and add a name, like this:

dispatch.on('say.one', function() {
    console.log('say.one event');
});
dispatch.on('say.two', function() {
    console.log('say.two event');
});

This is how you can have multiple handlers for "say". The odd twist is that the part after the dot is the namespace. So ".one" is a namespace, and ".two" is a namespace. Within each namespace, you can have one handler per named event type. It feels like an inversion of the usual way people think of namespaces, but it works.

Here's a complete example of events with namespaces:

dispatch = d3.dispatch('say');
dispatch.on('say.one', function() {
    console.log('say.one event');
});
dispatch.on('say.two', function() {
    console.log('say.two event');
});
dispatch.say();

http://jsfiddle.net/johnkawakami/hstv28cj/1/

Multiple Event Types

Each dispatch can register multiple types of events. They are specified in the constructor:

dispatch = d3.dispatch('say', 'shout');

You can now add handlers that will respond to dispatch.shout():

dispatch.on('shout.one', function() {
    console.log('shout.one event');
});

Here's a complete example:

dispatch = d3.dispatch('say', 'shout');
dispatch.on('say.one', function() {
    console.log('say.one event');
});
dispatch.on('say.two', function() {
    console.log('say.two event');
});
dispatch.on('shout.one', function() {
    console.log('shout.one event');
});
dispatch.on('shout.two', function() {
    console.log('shout.two event');
});
dispatch.say();
dispatch.shout();

http://jsfiddle.net/johnkawakami/hstv28cj/2/

DOM Manipulations

D3 has some DOM manipulation features similar to jQuery's, but with a different syntax. The basic idea is the same: you select an element or elements, and alter them. The following would select the element with id="one" and then add the class "light" to the class attribute.:

d3.select('#one').classed('light', true);

Adding and removing classes is considered the best way to apply styles to an element. (It's also the basis for CSS animations.) The classed() function allows you to add and remove classes, one at a time, or in bulk. It's similar to jQuery's addClass()/removeClass() and plain Javascript's classList.add() and classList.remove().

The following CSS defines a set of classes that can style a div so that it works a little light an on/off light:

.box {
    width: 20px; 
    height: 20px;
    background-color: silver;
    border: 1px solid silver;
}
.light {
    background-color: yellow;
}
.dark {
    background-color: #003;
}

If we start with HTML like this:

<div id="one" class="box"></div>

The box is initially grey. It can be made light or dark by adding the "light" or "dark" class. The following d3 JS code does that, using events:

dispatch = d3.dispatch('light');
dispatch.on('light', function() {
	d3.select('#one').classed('light', true);
});
dispatch.light();

Play it here: http://jsfiddle.net/johnkawakami/4tz3ervn/1/

Demo of multiple listeners

The following is a simple demo of a stack of four of these "lights". Lights one and three listen only for the "light" event, and lights two and four listen only for the "dark" event.

The code uses namespaces and DOM manipulation:

dispatch = d3.dispatch('light', 'dark');
dispatch.on('light.one', function() {
	d3.select('#one').classed('light', true);
});
dispatch.on('dark.two', function() {
	d3.select('#two').classed('dark', true);
});
dispatch.on('light.three', function() {
	d3.select('#three').classed('light', true);
});
dispatch.on('dark.four', function() {
	d3.select('#four').classed('dark', true);
});
dispatch.light();
dispatch.dark();

See the jsfiddle for the html code, and to see it working: http://jsfiddle.net/johnkawakami/4tz3ervn/3/

DOM Events

DOM events are events that sent to DOM elements. These are the well known events like "click", "mouseenter", "keydown". D3 supports adding event listeners to the DOM via .on(). For example:

d3.select('#one').on("click", function () { ... });

That attaches a click listener on the element named "one".

To add a click listener to a button, make a button:

<button id="button_light">Light</button>

Then add some code. This will listen for a click, and then run dispatch.light() in the click handler.:

d3.select('#button_light').on("click", function() {
	dispatch.light();
});

Now, when you click the button, it'll run dispatch.light(), causing boxes to light. The following JSFiddle has two buttons and the four boxes. You can push the button to light up or darken the boxes: http://jsfiddle.net/johnkawakami/4tz3ervn/4/

More on adding and removing CSS styles

The last demo was kind of lame, because it didn't feel interactive. You clicked, and the grey squares got color. We can alter the code a bit and make the third box respond to both the "light" and "dark" events. We need to add a "dark.three" handler, and also need to use a different form of classed() that takes an object.

dispatch.on('light.three', function() {
	d3.select('#three').classed({'dark':false, 'light': true});
});
dispatch.on('dark.three', function() {
	d3.select('#three').classed({'dark': true, 'light':false});
});

The simpler form of classed() just adds classes to the list of class names. This second form adds and removes them. Now, the #three box will respond to both dispatch.light() and dispatch.dark(). The other three boxes work as in the previous demo.

http://jsfiddle.net/johnkawakami/4tz3ervn/5/

Using Events to Control an Object's State

This next demo takes a bit of a leap forward, and might require more careful reading. It creates a clickable box that toggles it's color from light to dark. The clicks are handled by a function that changes a global variable named "state". Then, based on the value of state, calls either dispatch.light() or dispatch.dark().

In the previous example, element #three had two handlers to respond to light and dark messages. We're going to do the same to #one:

dispatch = d3.dispatch('light', 'dark');
dispatch.on('light.one', function() {
	d3.select('#one').classed({'light':true, 'dark':false});
});
dispatch.on('dark.one', function() {
	d3.select('#one').classed({'light':false, 'dark':true});
});

Now, we have handlers for both light.one and dark.one that will set the color of #one accordingly.

Next is some new code. We create a global variable named "state" to hold our state. Then, we attach a click handler onto element #one. Clicks cause the state to be updated, and then displayed.

state = 0;
d3.select('#one').on("click", function() {
    state = (state + 1) % 2;
    console.log(state);
    if (state==0) {
	dispatch.light();
    } else {
        dispatch.dark();
    }
});

You can watch the value of state in the Javascript console.

http://jsfiddle.net/johnkawakami/q2qasr38/4/

Using Events to Manage a Multiple-State Menu

I got a little ahead of this pseudo-tutorial and combined the state-changing, style changing, and some function-generating-functions to make a selection menu that supports multiple selections.

There are many ways to do this, but I chose to use a small "database" for the list, a "model" of the menu state, and code to generate the list and attach event listeners.

http://jsfiddle.net/johnkawakami/q2qasr38/11/

Use Events across Views and Controllers

On my work project, I was implementing a more complex system with a view of data. There was a controller and two views. The best way to use d3.dispatch was to create a single dispatcher and give it to each these components. The dispatcher functioned as a common message bus across the different parts.

I had tried using multiple dispatchers, but it got complicated, and I didn't really gain anything over using regular method calls. With a single dispatcher, it was much easier to implement. Events in one part of the code could trigger execution of functions in other parts of the code.

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.

Excellent summary

Excellent summary