Creating a customized event model in JavaScript without relying on standard DOM events is a unique

Embarking on my learning journey with JavaScript and programming in general, I find myself pondering over objects and events.

Imagine this object:

var computer = {
    keyboard: {}
}

I'm keen on finding a method to attach events to the keyboard object:

computer.keyboard.registerEvent( "keyEscape" );

To trigger the event:

computer.keyboard.dispatchEvent( "keyEscape" );

And establish event handlers:

computer.keyboard.addEventListener( "keyEscape", function() {...} );

While I understand how to achieve this with DOM elements, working out a solution with objects poses a challenge. Is it feasible in JavaScript (possibly with JQuery)?

Any form of guidance, no matter how small, would be greatly appreciated.

Answer №1

If you're looking to create an independent event system that doesn't rely on DOM events, you can implement a reactor pattern like the one shown below

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);
};

Utilize it as you would with DOM events model

var reactor = new Reactor();

reactor.registerEvent('explosion');

reactor.addEventListener('explosion', function(){
  console.log('This is an explosion listener!');
});

reactor.addEventListener('explosion', function(){
  console.log('Another explosion listener here!');
});

reactor.dispatchEvent('explosion');

Check it out live on JSBin

Answer №2

If you want to avoid creating a DOM object, you can easily instantiate a new EventTarget following some suggestions:

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

By using this method, you still have access to all the features of DOM events without needing to create an empty document element or node.

To learn more about this topic, check out the Mozilla Developer Guide at: https://developer.mozilla.org/en-US/docs/Web/API/EventTarget

Answer №3

Update: It has come to my attention that this method may only work with older browsers. For newer browsers, it is recommended to utilize the EventTarget class instead. Check out Nadeem Douba's response for more information.

If you're looking for a simpler way to handle events without creating your own mechanisms, you might find my approach useful. You can still access all the familiar features of standard DOM Events (such as preventDefault()) and it is more lightweight since it leverages the built-in capabilities of the browser for event handling.

To implement this method, simply create a regular DOM EventTarget object within your object constructor and delegate all EventTarget interface calls to it:

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

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

    // Add any additional constructor code here
}

// Instantiate your custom event target
myTarget = new MyEventTarget();
// Attach an event listener to your custom event target
myTarget.addEventListener("myevent", function(){alert("hello")});
// Trigger an event from your custom event target
var evt = new Event('myevent');
myTarget.dispatchEvent(evt);

You can also try out a JSFiddle demo to test this method in action on your browser.

Answer №4

Reviving an old discussion here, but I recently created something similar to this concept - very straightforward and inspired by the 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 method is incredibly easy to use and flexible, allowing you to develop a more advanced event system based on it if necessary.

Implementing the EventDispatcher is as simple as:

function setupListeners() {
    EventDispatcher.on('ignite', ignite); // ignite.bind(this) -- if needed
}

function ignite(y) {
    console.log(y);
}

function actionOccurred(action) {
    EventDispatcher.trigger('ignite', action);
}

By utilizing some basic namespacing, you can effortlessly communicate basic events between different modules!

Answer №5

You have the ability to achieve this using JQuery.

To subscribe to a custom event:

$(device.keyboard).on('keyEscape', function(e){
    //Code for handling the event
});

To trigger your custom event:

$(device.keyboard).trigger('keyEscape', {keyCode:'Example value'});

While it may not be the most elegant solution, you can also create functions within your method (addEventListener, dispatchEvent,...) that encapsulate JQuery functionality, catering to both native and JQuery API preferences.

Answer №6

If you find yourself in a situation where multiple objects need to communicate with each other, an event mechanism can be the solution.

Here is a way to implement this:

/**
 * EventfulObject constructor/base.
 * @type EventfulObject_L7.EventfulObjectConstructor|Function
 */
var EventfulObject = function() {
  /**
   * Map from event name to a list of subscribers.
   * @type Object
   */
  var event = {};
  /**
   * List of all instances of the EventfulObject type.
   * @type Array
   */
  var instances = [];
  /**
   * @returns {EventfulObject_L1.EventfulObjectConstructor} An `EventfulObject`.
   */
  var EventfulObjectConstructor = function() {
    instances.push(this);
  };
  EventfulObjectConstructor.prototype = {
    /**
     * Broadcasts an event of the given name.
     * All instances that wish to receive a broadcast must implement the `receiveBroadcast` method, the event that is being broadcast will be passed to the implementation.
     * @param {String} name Event name.
     * @returns {undefined}
     */
    broadcast: function(name) {
      instances.forEach(function(instance) {
        (instance.hasOwnProperty("receiveBroadcast") && typeof instance["receiveBroadcast"] === "function") &&
        instance["receiveBroadcast"](name);
      });
    },
    /**
     * Emits an event of the given name only to instances that are subscribed to it.
     * @param {String} name Event name.
     * @returns {undefined}
     */
    emit: function(name) {
      event.hasOwnProperty(name) && event[name].forEach(function(subscription) {
        subscription.process.call(subscription.context);
      });
    },
    /**
     * Registers the given action as a listener to the named event.
     * This method will first create an event identified by the given name if one does not exist already.
     * @param {String} name Event name.
     * @param {Function} action Listener.
     * @returns {Function} A deregistration function for this listener.
     */
    on: function(name, action) {
      event.hasOwnProperty(name) || (event[name] = []);
      event[name].push({
        context: this,
        process: action
      });

      var subscriptionIndex = event[name].length - 1;

      return function() {
        event[name].splice(subscriptionIndex, 1);
      };
    }
  };

  return EventfulObjectConstructor;
}();

var Model = function(id) {
  EventfulObject.call(this);
  this.id = id;
  this.receiveBroadcast = function(name) {
    console.log("I smell another " + name + "; and I'm model " + this.id);
  };
};
Model.prototype = Object.create(EventfulObject.prototype);
Model.prototype.constructor = Model;

// ---------- TEST AND USAGE (hopefully it's clear enough...)
// ---------- note: I'm not testing event deregistration.

var ob1 = new EventfulObject();
ob1.on("crap", function() {
  console.log("Speaking about craps on a broadcast? - Count me out!");
});

var model1 = new Model(1);

var model2 = new Model(2);
model2.on("bust", function() {
  console.log("I'm model2 and I'm busting!");
});

var ob2 = new EventfulObject();
ob2.on("bust", function() {
  console.log("I'm ob2 - busted!!!");
});
ob2.receiveBroadcast = function() {
  console.log("If it zips, I'll catch it. - That's me ob2.");
};

console.log("start:BROADCAST\n---------------");
model1.broadcast("crap");
console.log("end  :BROADCAST\n---------------\n-\n-\n");
console.log("start:EMIT\n---------------");
ob1.emit("bust");
console.log("end:EMIT\n---------------");
<h1>...THE SHOW IS ON YOUR CONSOLE!</h1>

Answer №7

After stumbling upon this question nearly a decade later, I've noticed significant changes in the realm of browser and JavaScript compared to when most responses were provided.

Here are my insights for those utilizing JavaScript module imports/exports:

// eventbus.js

class EventBus {
    constructor() {
        this.events = {};
    }

    on(type, callback) {
        if (!this.events[type]) {
            this.events[type] = [];
        }

        this.events[type].push(callback);
    }

    off(type, callback) {
        if (!this.events[type]) {
            return;
        }

        this.events[type] = this.events[type].filter(listener => listener !== callback);
    }

    dispatch(type, data) {
        if (!this.events[type]) {
            return;
        }

        this.events[type].forEach(listener => listener(data));
    }
}

export const eventbus = new EventBus();
// somefile.js
import {eventbus} from './eventbus';

// Somewhere in a method/click callback/etc..
eventbus.dispatch('fire', {message: 'Fire in the hole!'});
// otherfile.js
import {eventbus} from './eventbus';

eventbus.on('fire', data => {
    console.log(data.message); // logs 'Fire in the hole!'
});

Answer №8

Check out this straightforward enhancement of Mohsen's solution, demonstrated through a concise and lucid example.

All the React functions mentioned are consolidated within a single entity called React(). An extra function named removeEventListener() is included, and the entire demonstration is encapsulated in one HTML file (or view it on JSFiddle).

<!DOCTYPE html>
<html>

<head>
    <meta charset=utf-8 />
    <title>JS Bin</title>
    <!--https://jsfiddle.net/romleon/qs26o3p8/-->
</head>

<body>
    <script>
        function Reactor() {
            function Event(name) {
                this.name = name;
                this.callbacks = [];
            }
            Event.prototype.registerCallback = function(callback) {
                this.callbacks.push(callback);
            };
            Event.prototype.unregisterCallback = function(callback) {
                var arr = this.callbacks,
                    idx = arr.indexOf(callback);
                if (idx > -1)
                    arr.splice(idx, 1);
            }
            this.events = {};

            this.registerEvent = function(eventName) {
                var event = new Event(eventName);
                this.events[eventName] = event;
            };
            this.dispatchEvent = function(eventName, eventArgs) {
                var events = this.events
                if (events[eventName]) {
                    events[eventName].callbacks.forEach(function(callback) {
                        callback(eventArgs);
                    });
                }
                else
                    console.error("WARNING: can't dispatch " + '"' + eventName + '"')
            };
            this.addEventListener = function(eventName, callback) {
                this.events[eventName].registerCallback(callback);
            };

            this.removeEventListener = function(eventName, callback) {
                var events = this.events
                if (events[eventName]) {
                    events[eventName].unregisterCallback(callback);
                    delete events[eventName];
                }
                else
                    console.error("ERROR: can't delete " + '"' + eventName + '"')
            };
        }
/*
    demo of creating
*/
        var reactor = new Reactor();

        reactor.registerEvent('big bang');
        reactor.registerEvent('second bang');

/*
    demo of using
*/
        log("-- add 2 event's listeners for 'big bang' and 1 for 'second bang'")
        var cb1 = function() {
            log('This is big bang listener')
        }
        reactor.addEventListener('big bang', cb1);

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

        reactor.addEventListener('second bang', function() {
            log('This is second bang!')
        });

        log("-- dipatch 'big bang' and 'second bang'")
        reactor.dispatchEvent('big bang');
        reactor.dispatchEvent('second bang');

        log("-- remove first listener (with cb1)")
        reactor.removeEventListener('big bang', cb1);

        log("-- dipatch 'big bang' and 'second bang' again")
        reactor.dispatchEvent('big bang');
        reactor.dispatchEvent('second bang');

        function log(txt) {
            document.body.innerHTML += txt + '<br/>'
            console.log(txt)
        }
    </script>
</body>

</html>

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Invoke a handler from one function within another function

I am unsure of the best approach for achieving this, or if it is even feasible, but I have two components: a main navigation bar (NavBar) that spans across the top of the page, and a sidebar drawer. NavBar: export default function NavBar() { const c ...

Issues with implementing KendoUI's datepicker in conjunction with Angular

My index.html file contains the following imports: <script src="content/js/angular.js"></script> <link href="content/js/kendo.common-material.min.css" rel="stylesheet" /> <link href="content/js/kendo.material.min.css" rel="styleshe ...

Exclude specific outcomes within a nested document in MongoDB

I have a situation where I need to query a list of items and only retrieve the ones that correspond to a specific ID in the provider_cost_dict. Essentially, if I input providerId = 10001, then only the items with a matching entry in the provider_cost_dict ...

Tips on adjusting the point size in ChartJS

I am looking to minimize the size of the data points on my chart. https://i.sstatic.net/CKtQX.png I have attempted to achieve this by using pointHoverRadius: 1 However, this method does not seem to be effective. Thank you for your assistance, ...

Exploring the world of BDD with Node.js and Angular in an actual web

Currently, I am in the midst of developing an application using nodeJs + yo angular-fullstck, starting with BDD and TDD practices. After searching through multiple resources, I have decided to utilize cucumber-js and Jasmin with the karma runner for testi ...

Is there a similar effect in jQuery? This is achieved using prototype

Simply click on the comments link. Observe how the comments smoothly appear, as if sliding down. Also, try clicking on 'hide' and see what happens. ...

How can I accurately determine the Content-length header when using node.js?

How accurate is my current approach to determining the content-length? What potential impacts are there when relying on string.length() for this calculation? And does specifying the charset as utf-8 have any significance in a node.js environment? payloa ...

Tips for extracting specific values from JavaScript using jQuery in ASP to be utilized in the C# code behind section

I want to extract the selected values from a multiselect jQuery and use them in the code behind in C#. The script sources can be found at the following link: https://github.com/nobleclem/jQuery-MultiSelect Below is the ASP code I am using: <div class ...

Are Javascript's JSON.stringify and PHP's json_encode equivalent functions?

When attempting to hash stringified data using HMAC SHA256 in both JavaScript (with CryptoJS Libraries) and PHP (using the built-in HMAC function), I am concerned about potential inconsistencies between JavaScript's JSON.stringify and PHP's json_ ...

Incorporate a gltf-model into your a-frame environment using the power of three.js

Is there a way to import a gltf-model into an a-frame scene using the three.js loader directly with javascript, instead of using a-frame tags? Additionally, I need the model to include animation and be able to control this animation through three.js. You ...

step-by-step guide to replacing an item in an array using javascript

I am working with some HTML codes : <p class="Chords">[A] [B] [C]</p> <p> Some Text Goes Here </p> <p class="Chords">[D] [E] [F]</p> <p> Some Text Goes Here </p> <p class="Chords">[G] ...

Emails can be sent through a form without the need for refreshing

I am currently working on a webpage that utilizes parallax scrolling, and the contact box is located in the last section. However, I encountered an issue where using a simple HTML + PHP contact box would cause the page to refresh back to the first section ...

Converting JavaScript code into PHP syntax

I'm curious about code organization. The original code in this snippet is functioning properly: <?php if (isset($_POST['submit'])){ ... ... $invalidField = 2; } ?> <!DOCTYPE html PUBLIC "-//W3C//DTD ...

Unable to fetch the session variable using express-session

In my development work, I have been utilizing express-session for persistently storing sessions on a MongoDB database. While the project functioned flawlessly in a test environment, an issue arose when deploying to a production setting where the session v ...

Troubleshooting the non-functioning addEventListener in JavaScript

I am facing an issue where a function that should be triggered by a click event is not working and the console.log message does not appear <script src="e-com.js" async></script> This is how I included the javascript file in the head ...

Can images be placed on the border?

Hi there! I am trying to add an image on a border with a 100px solid width using CSS. I have experimented with positions and z-index, but I can't seem to get the desired effect. Any ideas on how to achieve this? Here is an example image: https://i.sst ...

Error: The variable usersWithSecrets has not been declared and therefore is not defined within the current scope. This issue

Despite my best efforts, I keep encountering the same error repeatedly: ReferenceError: D:\Secrets - Starting Code\views\submit.ejs:8 6| <h1 class="display-3">Secrets</h1> 7| >> 8| <%usersWith ...

Difficulty capturing emitted events from child components in Vue.js2

Currently, I'm working on developing a Bootstrap tabs component using Vuejs. The tabs component is divided into two parts - the parent tabs-list component that contains multiple tab-list-item components. Take a look at the code for both these componen ...

What is the recommended approach for utilizing props versus global state within your components when working with JS Frameworks such as Vue?

Currently, I am delving into a larger project using Vue and I find myself contemplating the best practices when it comes to utilizing props versus global Vuex states for accessing data within a component. To elaborate, let's say I have a component re ...

Attempting to retrieve data from cloud Firestore utilizing keyvalue in Angular

My database stores user information under the 'users' collection. I can access this data using the following code: In my service: users$ = this.afs.collection<Users[]>('users').valueChanges(); In my component: public users = t ...