Introduction
This article is about the use of global event system in the JavaScript language. In any application which is modular in nature, there are situations when we need the modules to communicate with each other which are disconnected in nature. The modules could be anything from just a simple collection of functions to full fledged UI controls.
Whatever the case may be, whenever we are required to execute a piece of code inside a module from another module but we also have to maintain the integrity of the application so that the modules will not communicate with each other directly, we need to use an intermediate component; the most usual choice is a Global Event System.
An event system could be used to register events and their handlers. The events could be wired up at the time of component or module initialization and could be removed when the module is disposed and no longer in use.
Global event systems make even more sense when they are used with modules having their own life cycle events like ASP.NET's Ajax controls or ReactJS components. We can pinpoint the places where we need to add and remove global events and the end result will be like our expectations.
Background
Event systems - both local and global have been in use for quite a long time. With the quick emergence of React like code and view libraries, the inexperienced developers often times feel it appropriate to have the components communicate with each other but are unable to come up with a suitable solution to this problem.
Using the Code
A global event system works by acting between two disconnected modules. The module which needs to handle another module's state change adds a new event handler to the event system. When the state of the first module changes, then it can choose to raise a global event which could be handled by multiple dependent modules.
The following is a very simple scenario of two disconnected modules and a very basic event system code which could be used by the modules to communicate with each other.
In the next code sections, there are two HTML elements; one is a text input and the other one is a button. We have to disable the button based on the length of the text value in the input element. Both the input and button have been associated with a code module to handle their various functions.
The input module validates the input value and raises the global event to notify the button module that the validation result has changed after the last input change. The button module then handles the event and changes the enabled state of the button element accordingly.
So if the input is empty, the button will remain disabled, else the button will be enabled.
Now let's see the code and description of each file. Every module and the event library code will be in its own js file. At the time of building the application, the code could be concatenated and minified using Gulp, Grunt or any other tool.
Main HTML Page (index.html)
<!DOCTYPE html>
<html>
<head>
<title></title>
</head>
<body>
<input id="txtName" value="John Doe"/> <br /><br />
<button id="btnSubmit">Submit</button>
<script src="js\app\app.js"></script>
<script src="js\control-modules\button-module.js"></script>
<script src="js\control-modules\input-module.js"></script>
<script src="js\lib\event-system.js"></script>
</body>
</html>
Description: This is the main HTML page which contains the inputs and the script references. The app.js will initialize everything and we don't have to write any other code over here.
Application Module (app.js)
;(function (w, d) {
w.App = w.App || {};
window.addEventListener('DOMContentLoaded', function () {
var txtName = new App.InputModule(d.getElementById('txtName'));
txtName.setValidation('Required', 'change', function () {
var args = {};
args.IsValid = this.control.value.length;
App.EventSystem.RaiseEvent('SetButtonState', args);
})
var btnSubmit = new App.ButtonModule(d.getElementById('btnSubmit'))
});
})(window, document);
Description: The code over here will initialize the modules associated with the HTML elements. There is a validation being added for the required value of the input element. The function which would be called whenever the input is changed will raise a global event which has to be handled by every module which intends to do something when the required field validation is finished. The validation result is being passed as an argument to the SetButtonState
event while it is being raised.
Event System Object (event-system.js)
;(function (w) {
w.App = w.App || {};
var eventSystem = {
'AddEvent': function (name, handler) {
(this.Events[name])
? this.Events[name].Handlers.push(handler)
: this.Events[name] = { Handlers: [handler] };
},
'RaiseEvent': function (name, args) {
var i = 0;
if (this.Events[name]) {
for(;i<this.Events[name].Handlers.length;i++) {
this.Events[name].Handlers[i].call(null, args);
}
}
},
'RemoveEvent': function (name, handler) {
var idx = this.Events[name].Handlers.indexOf(hander);
if (idx > -1) {
this.Events[name].Handlers.splice(idx, 1);
}
},
'Events': {}
};
w.App.EventSystem = eventSystem;
})(window);
Description: This is the global event system object which could be accessed using the main App
namespace. In your application, you might decide to plant this object further down into your application namespace chain based on the naming convention that you are using.
This event object can have numerous methods but at the core, there are methods to add, raise and remove an event. More functions could be created to augment these core methods.
The AddEvent
function will simply add a new event to the event object's Events
collection. The Events
collection is also an object and that is because it is easier to access the events when they are saved as individual properties.
Each event is an object and it contains a Handler list which will contain all the handlers added to a single event. When any event is raised, then all the handlers are executed one by one and the arguments are also passed along with calling the handler function.
RemoveEvent
will simply remove the specific event handler leaving the rest of the handlers intact. This event object is very basic it might need more testing and tweaking before it could be used in a production setting.
Input Module (input-module.js)
;(function (w) {
w.App = w.App || {};
var inputModule = function (c) {
this.control = c || null;
this.Validations = [];
init();
this.setValidation = function (name, event, func) {
var handler = (func).bind(this);
this.control.addEventListener(event, handler);
this.Validations.push({ 'Name': name, 'Handler': handler });
}
function init() {
};
};
w.App.InputModule = inputModule;
})(window);
Description: This module is for all the input HTML elements. There is a setValidation
function which could be used to call a specific function when any input event is raised. The exact validation handler needs to be implemented when we are initializing the module. You could also add pre-defined validations as per your requirements as I have only coded the necessary bit to show how the global event system is being used over here.
Button Module (button-module.js)
;(function (w) {
w.App = w.App || {};
var buttonModule = function (c) {
this.control = c || null;
init();
function init() {
App.EventSystem.AddEvent('SetButtonState', eventSetState)
};
var setState = (function (d) {
this.control.disabled = d;
}).bind(this);
function eventSetState(args) {
setState(!args.IsValid);
}
};
w.App.ButtonModule = buttonModule;
})(window);
Description: This is the module for a button
element. In this code, there is an init()
function which gets called as soon as the module
object is initialized using its constructor function. Inside the init()
function, the event SetButtonState
is being added to the global event collection. This same event is also getting raised from the input element module's required validation handler function which we saw previously.
The function setState
which is being assigned as the event handler is simply setting the enable state of the button depending on the validation result.
You might have also noticed that the setState
function is created using function.Bind;
this is important because if we want to use the 'this
' keyword to access the control reference as well as other functions, we need to set a fixed scope of the handler function irrespective of where it is being called from.
It's time to run the application in the attached code. I would suggest to place breakpoints at several places to see how the code execution jumps from module to event object and then back to another module when any global event is raised.
Points of Interest
A global event system is a must when we have disconnected components or modules in our application. I would suggest not to be shy in using it and take leverage of this powerful design pattern which is very useful in the JavaScript language more than any other language that I could think of.
History
- 19th August, 2016: Initial version