Custom events in client side javaScript

In client side javaScript there is the custom event constructor that can be used to create my own events that can be attached to html elements. I then in my own code define the conditions that will be used to trigger these kinds of custom events by calling the dispatch event method of the element that I attached a handler for the custom event.

There are a number of other ways of creating custom events when it comes to using a framework like phaser and threejs, often such frameworks have a system for creating user defined events for various kinds of things that happen in such frameworks, as well as additional user space code that will run on top of them. There are also ways of doing this in a nodejs environment when it comes to the events module which would be the node built in way of how to go about making user define events in a sever side javaScript environment. However there is also the idea of making a core JavaScript solution that will work in the browser as well as node also.

In this post I will be mainly focusing on the ways to go about making custom events in just plain old vanilla client side javaScript in a web browser. This will also include some examples where I am making my own system in core javaScript which can then be used in any javaScript environment. The basic process is similar to what needs to happen when simulating a built in event such as a click event on a div element for example. Make sure there is an event handler attached to the element in question for the event, create an event object for the event, and then call the dispatch event method of the element and pass the event object to that method.

1 - What to know before getting into making custom events

This is a bit more of an advanced post compared to the basics of getting started with events. When it comes to the very basics of events in client side javaScript I have wrote a post on event listeners, and other post on event objects. It might be a good idea to get a solid grasp on how to work with built in events first before getting into learning how to go about defining custom events. In my post on event objects I have an example that involves how to go about simulating events by following a process that is not so simulate from what it is that I am writing about here actually.

I also assume that you have at least some background when it comes to the very basics of html and javaScript. If you are still fairly new to javaScript you might want to start out with some getting started type posts on javaScript in general.

1.1 - The source code for this post, and many others is on my test vjs github repository

The source code for the examples in this post can be found in the test vjs Github repository found here. I do get around to editing this content once in a while, but it would be best to make a pull request there, or leave a comment this post if there is something you think needs to change here. I have a whole lot of other posts that I need to edit also, and the squeaky wheel gets the greasing.

2 - Custom Event constructor basic example

In this section I will be touching base on just a basic example of the CustomEvent constructor, that can be used to create a custom user defined event object. The process just involves creating an event with the custom event constructor, and then passing that object to the dispatch event method of a DOM element reference. Once the dispatch event method is called any event handlers that are attached for that custom event will fire.

So lets start out with just some basic html like this and attach to an external javaScript file.

1
2
3
4
5
6
7
8
<html>
<head>
<title>custom event in client side javaScript</title>
</head>
<body>
<script src="basic.js"></script>
</body>
</html>

In the basic.js file that I am linking to in the html I am creating a custom event object with the Custom Event constructor, attaching a handler to the body element of the html document for it, and then dispatching the event for the body element like so.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var myEvent = new CustomEvent(
'my-event', {
detail: {
message: 'custom event!',
time: new Date(),
n: 42
},
bubbles: true,
cancelable: true
});
// define a handler for the event
document.body.addEventListener('my-event', function (e) {
console.log(e.detail.message); // 'custom event!'
console.log(e.detail.n); // 42
});
// dispatch it
document.body.dispatchEvent(myEvent)

This might not be the best example of why creating my own events is a good idea, but you get the basic idea of the process. Just call the Custom event constructor with the new keyword just like any other constructor function in javaScript. When doing so pass the name of the custom event as the first argument, followed by and object. This object should have at least a detail property that contains data about the nature of the event.

2 - Making a core javaScript event system that will work in the browser as well as in nodejs

So I covered the basics of how to work with built in client side javaScript features when it comes to making custom events that are tired closely to html elements, and thus client side javaScript only. However what if I want to use some kind of system that is not tied closely to client side, or sever side javaScript, but core javScript. As such such a system should be able to be used quickly when making client side javaScrit as well as sever side javaScript solutions.

In this section then I will be going over the source code of a module that will work in a browser, but I can also use in a nodejs script also. This helps to make my code more portable between a sever, and a client system which can have many benefits for a wide range of reasons.

2.1 - The event system module

Here I have the event system mode that I world out for this. Te module follows a pattern that I found out about a while back that is one way to go about making these kinds of modules that will work in a client as well as a sever. I wrote a post on this topic with one of my javaScript examples series posts which was the one where I am writing about this kind of module pattern involving the use of an IIFE, and feature testing for nodejs features when creating the value to append to inside the body of the IIFE.

So then as I see it thus far this kind of user event module will need to have at least three public methods. One method will be used to set up and event for an object, another will be used to add a listener for the event of an object, and the final one will be used to dispatch the event.

So First off lets get to the public method that will be used to create what the event is, and add it to an object. When it comes to this method alone there is a lot to cover when it comes to the various details such as if this kind of property should be attached to the own properties of an object, of if it should be a part of an objects prototype. However for now I do not want to get to far off topic when it comes to this so for now I am thinking that this add event method will just create an event for a single object, so I pass that as one argument with an additional object that contains properties that define what the event is. This includes a method that will fire each time the event is dispatched as well as the key name of the event. The result of calling this method will just set up and event for object. To use the event I need to add at least one listener for the event, and then dispatch the event elsewhere in my code. When defining what the for dispatch method is for an event the return value should be whatever it is that I want for an event object, and the arguments passed to this for dispatch methods are a reference to the object, and the options that where passed when the event was dispatched.

Next I have a add listener method that is what I will be using to define one of more event handlers, or listeners of you prefer that will be called each time an event is dispatched. Inside the body of this pubic method I am getting a reference to the listeners array that should be there after calling the add event method for the object. Once I have a reference to the listeners array it is just a matter of pushing the given callback function to this array of listeners.

The third public method that comes to mind is the method that I will call in my code that will be used to dispatch a given event. When calling this method I given an object with an event set up, and with at least one listener attached as the first argument. Then an event key as the second argument followed by an options object for the event. The method then just gets a reference to the listeners array, and then loops over the array and calls each event listener. When doing so it calls the for dispatch function for the event to create and pass alone the event object for the callback function of the attached listener.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
(function (api) {
// add an event for an object
api.addEvent = function (obj, opt) {
opt = opt || {};
// user Event Object
var userEvent = {};
// MUST GIVE AN EVENT KEY
userEvent.eventKey = opt.eventKey;
// need a forDispatch method that will be called for each dispatch of an event
userEvent.forDispatch = opt.forDispatch || function (obj, dispatchOpt) {};
// need an array of listeners
userEvent.listeners = [];
// attach to the objects own properties
obj.ue = obj.ue || {};
obj.ue[userEvent.eventKey] = userEvent;
return obj;
};
// attach a listener for the object that will fire
// when the event happens
api.addListener = function (obj, eventKey, callBack) {
// get listeners for the eventKey
var listeners = obj.ue[eventKey].listeners;
// if we have listeners push the callback
if (listeners) {
listeners.push(callBack);
}
return obj;
};
// dispatch an event for the given object, passing the event key, and options
api.dispatch = function (obj, eventKey, dispatchOpt) {
var eventObj = obj.ue[eventKey];
// loop listeners array
eventObj.listeners.forEach(function (cb) {
// call the listener
cb.call(eventObj, eventObj.forDispatch.call(eventObj, obj, dispatchOpt));
});
return obj;
};
// this module should work well in nodejs, or client javaScript
}
(typeof module === 'undefined' ? this['eventMod'] = {}
: module.exports));

2.2 - Client side javaScript demo

So then here is a very simple client side javaScript example of the event module. For this demo of the events module above I am just creating a very simple player object that just has an hp property thus far. The basic idea I have here is to just create a hit event that will be called within later game logic each time the player is hit by some kind of enemy attack.

After I create the player object then I will want to call the add even pubic method of the event module passing the player object as the first argument along with an additional object that will define what the event is. When doing so I set the event key property of this event info object to hit, and define what the for dispatch method should be for this hit event. Each time the hit event is dispatched in the game code I will pass an options object that should contain a damage value. So in the body of the for dispatch method I use this damage value to deduct from the hp value of the player object. I then return an event object that contains a reference to the player object, along with a damage value, and a boolean that will be ture if the player is dead.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
<html>
<head>
<title>custom event in client side javaScript</title>
</head>
<body>
<textarea id="game-console" rows="20" cols="80"></textarea>
<script src="./event-system.js"></script>
<script>
var player = {
hp: 10
};
// CREATE AND ADD A 'HIT' EVENT FOR THE PLAYER OBJECT
let eventObj = {
eventKey: 'hit',
forDispatch: function (obj, dispatchOpt) {
obj.hp -= dispatchOpt.damage;
obj.hp = obj.hp < 0 ? 0 : obj.hp;
// return an event object that will be in the listener
return {
target: obj, // ref to the object
damage: dispatchOpt.damage,
dead: obj.hp === 0
};
}
};
eventMod.addEvent(player, eventObj);
// ATTACH A LISTNEER FOR THE 'HIT' EVENT
eventMod.addListener(player, 'hit', function (e) {
var el = document.querySelector('#game-console');
if(!e.dead){
el.value += 'The player was hit, taking ' + e.damage + ' damage.\n';
}else{
el.value += 'The player was hit, and has died from taking ' + e.damage + ' damage\n';
}
});
// DISPATCH THE EVENT
eventMod.dispatch(player, 'hit', {
damage: 3
});
// false 7
eventMod.dispatch(player, 'hit', {
damage: 7
});
// true 0
</script>
</body>
</html>

I will then want to attach at least one event listener for this hit event, and then dispatch the event. Because this is a client side javaScript example I can use some kind of html element as a way to display results such as a canvas element. However for the sake of keeping this use example very simple I will just be using a text area element as a way to display the result of of this.

2.3 - Sever side javaScript demo

I can then do more or less the same thing in nodejs with the same module. The main difference is just how I go about importing the module into the script. Sense this is a nodejs script now I have to use require as a way to being it in because there are no script tags in thus kind of javaScript environment. The other node worthing difference is that when it comes to creating my listener I can not use text area elements as a way to display what is going on. So in this demo I am just using the console log method to spit things out to the standard output.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
let path = require('path'),
eventMod = require(path.join(__dirname, 'event-system.js'));
var player = {
hp: 10
};
// CREATE AND ADD A 'HIT' EVENT FOR THE PLAYER OBJECT
let eventObj = {
eventKey: 'hit',
forDispatch: function (obj, dispatchOpt) {
obj.hp -= dispatchOpt.damage;
obj.hp = obj.hp < 0 ? 0 : obj.hp;
// return an event object that will be in the listener
return {
target: obj, // ref to the object
damage: dispatchOpt.damage,
dead: obj.hp === 0
};
}
};
eventMod.addEvent(player, eventObj);
// ATTACH A LISTNEER FOR THE 'HIT' EVENT
eventMod.addListener(player, 'hit', function (e) {
console.log(e.dead, e.target.hp);
});
// DISPATCH THE EVENT
eventMod.dispatch(player, 'hit', {
damage: 3
});
// false 7
eventMod.dispatch(player, 'hit', {
damage: 7
});
// true 0

So then I can use the same system on client side systems as a way to create user events, but then I can also use it in web worker as well as nodejs scripts. So Then it is possible to create some kind of game module that should work well in a browser window, a worker instance, or I can also run the game in a nodejs script. In some cases I might want to have a game state object run in a sever side environment, for examples if it is some kine of network game I would want the game to update on a sever, not a client system. That is unless I want to run the game module in a web worked as a way to off set some work to the clients rather than the sever. SO making modules like this helps to make my code more portable.

3 - Conclusion

So now and then it become necessary to create my own events for things when it comes to working out a module for something. For example say I am making a game module and I want to provide a way to have an event that will fire each time and enemy is killed, or when the game is over. In the code that composes my state machine I can then attach event handlers for these to define some code that will run each time that at enemy is killed, or when a game is over that should not be parked in the main game module.

So the having a way to create these kinds of custom events can really come in handy in some cases. There might be alternative ways of doing so, and it can be a gray area as to the idea of doing so is a better idea or not. Ether way I am glad that there is a built in way to do this sort of thing anyway.

Well that is it for not with custom events in client side javaScipt, at some point in the future if I get some time to expand this post with some more examples I am sure I will edit this one again.