2

How can I add event listeners to and dispatch events from my objects of my own classes in JavaScript?

In ActionScript 3 I can simply inherit from Sprite/DisplayObject and use the methods available there. Like this:

// ActionScript-3:
// I can add event listeners to all kinds of events
var mySprite: Sprite = new MySprite();
mySprite.addEventListener("my_menu_item_click", onMenuItemClick);

// later I can dispatch an event from one of my objects
mySprite.dispatchEvent(new Event("my_menu_item_click", ...));

I would like to have the same in JavaScript. Until now I know about window.addEventListener(...) and document.addEventListener(...). I have my own Sprite class in JavaScript so far and I want to use it to dispatch my own events.

// JavaScipt:
function Sprite()
{
    this.x = 0;
    this.y = 0;
    // ...
}

Since both languages seem so "alike" with events I guess I need to inherit from some class? Or do I have to use some of the global variables like window and/or document?

I'm working with HTML5 here. I have only the 1 canvas and a bunch of sprites that are drawn to it and react to user input. I would like one sprite A to subscribe to events of another sprite B. But not to the third sprite C.

Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
Bitterblue
  • 13,162
  • 17
  • 86
  • 124

4 Answers4

5

I tried to add the methods into my Sprite class and succeeded.
Of course not finished yet, but at least it works.
It's what I was looking for.

function Sprite()
{
    // ...
    this.eventListeners = new Array();

    this.addEventListener = function(type, eventHandler)
    {
        var listener = new Object();
        listener.type = type;
        listener.eventHandler = eventHandler;
        this.eventListeners.push(listener);
    }

    this.dispatchEvent = function(event)
    {
        for (var i = 0; i < this.eventListeners.length; i++)
            if (event.type == this.eventListeners[i].type)
                this.eventListeners[i].eventHandler(event);
    }
}
Bitterblue
  • 13,162
  • 17
  • 86
  • 124
1

This is some kind of "functional" approach, I don't like using "new" and "this" in javascript. I like plain, peer or extended objects.

// Some helper functions, should be imported from a different file
const partial = fn => (...pargs) => (...args) => fn.apply(null, [...pargs, ...args]);
const partialRight = fn => (...pargs) => (...args) => fn.apply(null, [...args, ...pargs.reverse()]);

// Module starts here
const on = (listeners, type, listener, once) => {
  if (!listeners[type]) {
    listeners[type] = [];
  }
  if (listeners[type].indexOf(listener) < 0) {
    listener.once = once;
    listeners[type].push(listener);
  }
};

const once = partialRight(on)(true);

const off = (listeners, type, listener) => {
  const listenerType = listeners[type];
  if (listenerType && listenerType.length) {
    const index = listenerType.indexOf(listener);
    if (index !== -1) {
      listenerType.splice(index, 1);
    }
  }
  if ((listenerType && !listenerType.length) || !listener) {
    delete listeners[type];
  }
};

const emit = (listeners, type, ...data) => {
  if (listeners[type]) {
    listeners[type].forEach(listener => {
      listener.apply(null, data);
      if (listener.once) {
        off(listeners, type, listener);
      }
    });
  }
};

// you could use "export default () => {}" to make it an importable module
const eventEmitter = () => {
  const listeners = {};
  return {
    on: partial(on)(listeners),
    once: partial(once)(listeners),
    off: partial(off)(listeners),
    emit: partial(emit)(listeners),
  };
};

const myObj = Object.create(Object.assign({}, eventEmitter()));

myObj.on('hello', name => console.log(name));

setTimeout(() => {
  myObj.emit('hello', 'Csaba')
}, 2000)
cstuncsik
  • 2,698
  • 2
  • 16
  • 20
0

You can do pretty much the same in JS with the Event and CustomEvent object :

var event = new Event('build');

// Listen for the event.
elem.addEventListener('build', function (e) { ... }, false);

// Dispatch the event.
elem.dispatchEvent(event);

Source : MDN (https://developer.mozilla.org/en-US/docs/Web/Guide/Events/Creating_and_triggering_events)

Remy Grandin
  • 1,638
  • 1
  • 14
  • 34
  • 2
    Ok. But what is elem here? A Html-Element? ALl I have is a canvas and a bunch of sprites. – Bitterblue Jun 29 '15 at 08:15
  • elem is any js object. You don't need to implement yourself the addEventListener and dispatchEvent, they will be added by the js engine itself – Remy Grandin Jun 29 '15 at 10:51
  • I admit I'm not a JavaScript expert but I don't see how that would happen. Besides this test throws an error: `try { new Object().addEventListener("click", onClick); } catch (error) { alert(error.message); }` – Bitterblue Jun 29 '15 at 11:20
  • 2
    My bad, it's integrated in all DOM object and not all js object – Remy Grandin Jun 29 '15 at 11:45
  • 1
    In AS3, EventDispatcher implements event dispatching independent of the DisplayObject class. DisplayObjects all have EventDispatcher in their inheritance chain, but since EventDispatcher is a separate class, you can also inherit from it directly, so you can dispatch events from objects that are not DisplayObjects. In JavaScript, what is the equivalent of EventDispatcher? Or is the functionality just mishmashed into the DOM object, rather than have a clean, independent implementation like AS3? JavaScript is soooooo far behind AS3 it's sickening. – Triynko Dec 01 '15 at 15:39
0

You can make your own, this one is generic and very simple. I actually use it myself :D

This is the functionality you'll get.

  • addEventListener()
  • removeEventlistener()
  • dispatchEvent() (Or what ever you wish to call it)

This is the function that will add the needed methods to your object and at the same time return a function that can dispatch events.

// Snippet  =========================================
function createEventDispatcher(o) {
  var L = o.__listeners = {};
  o.addEventListener = function(n, fn) { L[n] = L[n] || []; L[n].push(fn); };
  o.removeEventListener = function(n, fn) { var a = L[n]; for (var i = 0; i < a.length; i++) if (a[i] === fn) a.splice(i, 1);};
  return function() { var a = Array.prototype.slice.call(arguments); var l = L[a.shift()]; if (l)  for (var i = 0; i < l.length; i++) l[i].apply(l[i], a)};
}

:

// Simplified example of usage =========================================
function App(){
    // Add functionality
    var dispatchEvent = createEventDispatcher(this); // Call snippet

    // Use functionality
    var count = 0;
    setInterval(function(){ 
        dispatchEvent("something",{msg:"hello",count:count++});
    },100);
}();

// Somewhere outside your App    
    function onSomething(event){
        console.log(event.msg + "["+event.count+"]");
        if(event.count >= 10){ 
            // Remove eventlistener 
            myApp.removeEventListener("something",onSomething);
        } 
   }

    var myApp = new App();    
    myApp.addEventListener("something",onSomething);

// Easy

https://jsfiddle.net/squadjot/0n2nby7k/

If you want to remove all listeners from your object, you can simply do something like.

myApp.__listeners = {};
Jakob Sternberg
  • 1,758
  • 14
  • 12