A simple, dependency free Event Bus for Nuxeo Web UI / Polymer / any JavaScript project

Alex Protopopescu
Nuxeo Open Kitchen
Published in
5 min readDec 5, 2022

TL;DR

If you want to skip to the event bus code, you will find it at the end of this article.

Summary

Let’s start with a discussion on component coupling, communication, and when to an event bus. Polymer has a few communication options available, and the key is finding the optimal coupling for each situation:

  1. Direct communication by way of property binding
  2. Mediator pattern
  3. Events, and a clever way of propagating data downward in Polymer
  4. Event bus

1. Direct communication by way of property binding

The binding annotation for one-way downward data-flow is set with double square brackets as[[propertyName]]. And the binding annotation for two-way data flow is set with double curly brackets as {{propertyName}} and allows data flow both upwards and downwards.

Direct communication between components
Direct communication between components — output
Direct communication between components — output

Downward passed properties have to be set on all the components in the communication chain. See what happens with ‘propA’ which is available in ‘fs-test-b’ but not available in ‘fs-test-c’.

Upward passed properties have to be defined in each originating component as component properties and must have flag notify: true set. Without notify:true the {{propertyName}} notation only allows downward data flow.

This is commonly used when connecting parents and children, or at most grand-children.

2. Mediator pattern

The mediator pattern is found in many languages, and it basically provides a centralised point of communication and allows for loose coupling of components by way of decreasing the amount of direct relations between different components.

Direct coupling between multiple related components
Direct coupling between multiple related components
Mediator pattern
Mediator pattern

The mediator pattern should be introduced when there are multiple related components that end up having too many direct relations between themselves and would benefit from a central point of communication.

You should still use direct communication between children that are only dependencies of their direct parent, i.e. if the parent component would be removed, the child component will not be missed — see components B & G in the images above.

3. Events, and a clever way of propagating data downward in Polymer

You have components A->B->C->D->E.
Say you want to send an input text when a button is clicked from E to A, simple, for this you would use an event, it goes upward, A gets the data, all cool!

But what if you want to do work in component E when a button is clicked in component A ? There is the property binding route, but in this case this is not the optimal way because you would have to bind a property on all components A, B, C, D & E.

However, you can bind component E to component A by sending ‘this’ from component E as an event detail.

Component E

properties: {
reactToClickInA: {
type: Boolean,
},
},
observers: [
'_reactToClickInA(reactToClickInA)'
],
attached(){
// bind this component to get updates from A
this.fire('component-e-to-a-bind', this);
},
_reactToClickInA(reactToClickInA){
if(reactToClickInA !== undefined){
this.alert("Button clicked in component A");
}
},

catch it in component A, and then modify a property of component E which is observed locally in component E

Component A

properties: {
componentE: {
type: Object,
},
},
connectedCallback(){
this.addEventListener('component-e-to-a-bind', this._setComponentE);
},
disconnectedCallback(){
this.removeEventListener('component-e-to-a-bind', this._setComponentE);
},
_setComponentE(e){
this.componentE = e.detail;
},
_buttonClicked(){
if(this.componentE !== undefined){
if(this.componentE.reactToClickInA === undefined){
this.componentE.reactToClickInA = true;
}
else {
this.componentE.reactToClickInA = this.componentE.reactToClickInA ? false : true;
}
},

When the button is clicked on component A (_buttonClicked), if component A received the instance of component E already, it will toggle the reactToClickInA property of component E and component E will react with observer _reactToClickInA.

Of course you could querySelector component E from component A and emit / change directly on it, but this is much better as you keep everything in the Polymer components and do not have to deal with querySelectors / shadowDom.

4. Event bus

Take the same example from pt. 3, we have components A->B->C->D->E and want to communicate between A & E, but this time we are using an event bus.

Component A

properties: {
busAtoE: {
type: Object,
},
propA: {
type: String,
value: "AAAAA",
},
},
attached(){
this.busAtoE = new BasicEventBus('a-to-e');
this.busAtoE.on('clicked-e', this._reactToClickE)
},
_reactToClickE(e){
this.alert("Button was clicked in component E, received value " + e);
}
_clickA(){
this.busAtoE.emit('clicked-a', this.propA);
}

Component E

properties: {
busAtoE: {
type: Object,
},
propE: {
type: String,
value: "EEEEE",
},
},
attached(){
this.busAtoE = new BasicEventBus('a-to-e');
this.busAtoE.on('clicked-a', this._reactToClickA)
},
_reactToClickA(e){
this.alert("Button was clicked in component A, received value " + e);
}
_clickE(){
this.busAtoE.emit('clicked-e', this.propE);
}

In component A when _clickA is used, component E will raise an alert with the received property; and in component E when _clickE is used, component A will raise an alert with the received property.

new BasicEventBus(‘a-to-e’); either creates the event bus ‘a-to-e’ or attaches to its instance, it does not matter which of A & E components gets loaded first.

As you can see, using an event bus to which components can subscribe to, allows for simple communication between them.

Of course you could setup events on document but then imagine you have 50–100 components all using document for their events, it could become a bit of a debugging nightmare.

With event busses, you can have multiple event busses that all listen for ‘the-same-event’, and you can emit this event on only on one of the busses if it is needed. Or you may examine the event bus element (<script type=”eventbus/event-bus-name”> tag in <head>) and see which events are present.

Event bus code

There are quite a few implementation variants along these lines on the web, and this is not my original idea, but this is a clever implementation of a drop-in JavaScript event bus.

Event bus code

And here is a bit of sample code on how to use the event bus.

Few examples on how to use the Event Bus

Let me know in the comments about what use cases you have where using an event bus applies, on Nuxeo Web UI, Polymer or JavaScript in general.

Follow & stay tuned for more!

--

--