Custom Events Model without using DOM events in JavaScript

Solution 1:

If you want to make a completely stand alone event system without relying on DOM events you can have something like this using reactor pattern

function Event(name){
  this.name = name;
  this.callbacks = [];
}
Event.prototype.registerCallback = function(callback){
  this.callbacks.push(callback);
}

function Reactor(){
  this.events = {};
}

Reactor.prototype.registerEvent = function(eventName){
  var event = new Event(eventName);
  this.events[eventName] = event;
};

Reactor.prototype.dispatchEvent = function(eventName, eventArgs){
  this.events[eventName].callbacks.forEach(function(callback){
    callback(eventArgs);
  });
};

Reactor.prototype.addEventListener = function(eventName, callback){
  this.events[eventName].registerCallback(callback);
};

Use it like DOM events model

var reactor = new Reactor();

reactor.registerEvent('big bang');

reactor.addEventListener('big bang', function(){
  console.log('This is big bang listener yo!');
});

reactor.addEventListener('big bang', function(){
  console.log('This is another big bang listener yo!');
});

reactor.dispatchEvent('big bang');

Live at JSBin

Solution 2:

If you don't want to implement your own event handling mechanisms, you might like my approach. You'll get all the features you know from usual DOM Events (preventDefault() for example) and I think it's more lightweight, because it uses the already implemented DOM event handling capabilities of the browser.

Just create a normal DOM EventTarget object in the constructor of your object and pass all EventTarget interface calls to the DOM EventTarget object:

var MyEventTarget = function(options) {
    // Create a DOM EventTarget object
    var target = document.createTextNode(null);

    // Pass EventTarget interface calls to DOM EventTarget object
    this.addEventListener = target.addEventListener.bind(target);
    this.removeEventListener = target.removeEventListener.bind(target);
    this.dispatchEvent = target.dispatchEvent.bind(target);

    // Room your your constructor code 
}

// Create an instance of your event target
myTarget = new MyEventTarget();
// Add an event listener to your event target
myTarget.addEventListener("myevent", function(){alert("hello")});
// Dispatch an event from your event target
var evt = new Event('myevent');
myTarget.dispatchEvent(evt);

There is also a JSFiddle snippet to test it with your browser.

Solution 3:

You can simply create a new EventTarget instance like some have suggested without having to create a DOM object, like so:

const target = new EventTarget();
target.addEventListener('customEvent', console.log);
target.dispatchEvent(new Event('customEvent'));

This provides all the functionality you're used to with DOM events and doesn't require an empty document element or node to be created.

See the Mozilla Developer Guide for more information: https://developer.mozilla.org/en-US/docs/Web/API/EventTarget

Solution 4:

Necroposting a little here, but I just wrote something like this last night - super simple, and based off of Backbone.js Events module:

EventDispatcher = {

    events: {},

    on: function(event, callback) {
        var handlers = this.events[event] || [];
        handlers.push(callback);
        this.events[event] = handlers;
    },

    trigger: function(event, data) {
        var handlers = this.events[event];

        if (!handlers || handlers.length < 1)
            return;

        [].forEach.call(handlers, function(handler){
            handler(data);
        });
    }
};

This approach is incredibly simple and extensible, allowing you to build a more sophisticated event system on top of it if you need.

Using the EventDispatcher is as simple as:

function initializeListeners() {
    EventDispatcher.on('fire', fire); // fire.bind(this) -- if necessary
}

function fire(x) {
    console.log(x);
}

function thingHappened(thing) {
    EventDispatcher.trigger('fire', thing);
}

With some simple namespacing, you'll be able to pass basic events between modules with ease!

Solution 5:

You can do it using JQuery.

For subscribing to your custom event:

$(computer.keyboard).on('keyEscape', function(e){
    //Handler code
});

For throwing your custom event:

$(computer.keyboard).trigger('keyEscape', {keyCode:'Blah blah'});

Might be not the nicest way to do this, but you also can create functions in your method (addEventListener, dispatchEvent,...) that will wrap JQuery logic, to support both native looking api and JQuery.