In our previous article we built a simple event delegation system. Our intent was to develop that
into a full PubSub
system. We can use this PubSub system to create an event-driven architecture.
It will be better if we split the event delegation feature from the PubSub
part so we are clear what weʼre doing. We can begin by creating a listeners object to replace our subscriptions object. Weʼll
store our listeners here:
We will also rename our subscribe function to register. We will move the event listener callback into our register
function:
It should be obvious what this does (see our previous essay). Now letʼs change our unsubscribe function to an unregister function:
Again, this works exactly the same as our previous unsubscribe function. Now we have moved our event delegation system into itʼs
own module. Now to our PubSub system.
Note that after we unregister the last listener, we clean up after
ourselves.
As a reminder, here is our castEvent function code:
Adding the PubSub system
We copied the code above into new files. This left our previous pubsub files. So, we already have our basic PubSub module. But we will need to
update them a bit.
The subscriptions shared object remains as before:
Our unsubscribe function is also as before. But as we are no
longer dealing with browser events, we donʼt need the castEvent function anymore. Those browser events go to our event delegator system
above, not to PubSub.
We have no ID, but weʼll want a way to unsubscribe, so we generate
a
UUID and return that from our subscribe function.
This is pretty self-explanatory, I hope. Our subscribe
function takes two parameters:
the event type to listen for, e.g., EMAIL_UPDATED
the callback function to call when some component publishes
an event of this type
We will decide which events to publish and to subscribe
to. The nice thing about a PubSub system is that we can create any event we like. We are not limited to browser events.
First, we ensure that there is an object associated with this
subscription type (line #4).
If this is the first subscription of this type, then we add
a listener for this event type (lines #6 to #12). Our callback loops
through all the individual callbacks stored in the subscriptions
object. It calls each in turn, passing the current event
object.
Then we generate a random UUID as our token. We use this to assign the passed-in
callback to this topic and token (line #16). Finally, we return the
token for use by the unsubscribe function (line #18).
What about our unsubscribe function? It is simple as can be:
We take as parameters the topic and the token.
Then we delete the callback for that topic/token in the subscriptions object.
Finally, if there are no remaining subscribers for that topic, we delete
the topic.
Now we need a publish function to allow us to publish to custom
events. Here it is:
We pass the topic, which is our custom event such as EMAIL_UPDATED. And we pass a detail object. This is the data that our custom
event will carry. It could be anything. See our examples below.
On lines #2 to #8, we create a CustomEvent. We assign it
our topic as the event type. This is what our subscribers are
listening for. We also pass the detail object and set event
bubbling to false.
Then on line #10 we call dispatchEvent on the document.body element to dispatch our custom event. By dispatching it and
listening for it on the body element, we do not need to bubble
it.
Our PubSub system in action
We add the PubSub system to our window (globalThis) object.
We add a single object representing our namespace, _xx.
Then we add our various modules to that:
Now letʼs set up a simple test page. Imagine that we have a set of individually-editable fields, such as name,
email, and phone:
And we will include some temporary elements to permit us to display our
events:
Now in our script we first import the PubSub module and then register
our event listeners for submit:
On line #1, we import our PubSub system as a module. Then, on lines #3
to #20, we add a DOMContentLoaded event listener to run our
registration code after the DOM loads.
Lines #4 to #15 use the register function of our event delegation
system to register a submit handler. This listens for a submit event on the form with the passed ID of “name-editor”. On
submit, it calls the passed callback, which:
prevents the default submission (line # 8) so we donʼt reload the
page
calls the publish PubSub function to publish our custom event.
We will give it the type NAME_UPDATED. And weʼll
pass a details object with the id and value of the input.
We do the same thing for the email and phone
fields. Now, when we submit any of these mini-forms, PubSub will create a custom event and publish it to our PubSub system.
We can now subscribe to these events anywhere in our app. And when a
component publishes that event, then we can do nothing, one thing, or many things.
Our forms donʼt know who is listening to their events. Our
subscribers donʼt know (or care) who is raising these events or
why. We have decoupled our code. The PubSub system acts
as an event bus to pass around these events. And we can make up any events
we need.
Now, to show how it works, we will add subscribers. These will post the
stringified events to the pre elements we added above:
Here we call the subscribe function of our PubSub module. We
pass it the topic to which we wish to subscribe. And we pass
a callback function to call when a component raises that topic.
In our example callback function, we grab the pre element for
that event. Then we stringify the event detail and append it as a child to
that element.
Of course, this is a simple example. We can do much more with this. For
example, we can include much more detail about the event. We can also
have more generic or more specific events. We could also:
Allow handlers to delete themselves after a specific number of calls
(e.g., once)
Broadcast an event to all topics
Use BroadcastChannel to pass events between browser tabs or
windows
Use websockets (or similar) to pass events between different
devices
In part 3 of this series, weʼll extend the system a bit. Stay
tuned.
Links to related pages
Get notified form
Carbon emissions for this page
Cleaner than 99% of pages tested
0.017g →
on first visit; then on return visits
0.010g
Beacon: A+
QR Code
Scan this code to open this page on another device.