Search code examples
javascripteventscustom-events

How to work with events in pure JavaScript?


This is possibly duplicated, however, things are changing fast in JavaScript world with new browsers. I'm using some resources that used in AS3 a long time ago and now it's finally popular in JavaScript. However, I still can't make events work in my classes. I did this simple example using Mozilla MDN as guide:

var endEvent = new Event("end");

function MyClass(text){
    this.text = text;
    this.show = function(){
        console.log("MyText: "+this.text);
        this.dispatchEvent(endEvent);
    }
}

function End(evt){
    console.log("Event dispatched: "+evt);
}

function run(){
    var MyInstance = new MyClass("I have something to say...");
    MyInstance.addEventListener("end", End, false);
    MyInstance.show();
}

At the first line, Safari returns: "TypeError: '[object EventConstructor]' is not a constructor (evaluating 'new Event("end")')".

It means won't work? Is there a way to create and dispatch custom events in PURE JavaScript (won't use jQuery or anything like)?


Solution

  • I had this same question a bit ago

    Here's the solution I came up with

    requires ecmascript >= 5


    function Emitter() {
      var eventTarget = document.createDocumentFragment();
    
      function delegate (method) {
        this[method] = eventTarget[method].bind(eventTarget);
      }
    
      [
        "addEventListener",
        "dispatchEvent",
        "removeEventListener"
      ].forEach(delegate, this);
    }
    

    Now a "class" that uses it

    function Example() {
      Emitter.call(this);
    }
    

    Let's try it out now!

    var e = new Example();
    
    e.addEventListener("something", function(event) {
      alert("something happened! check the console too!");
      console.log(event);
    });
    
    e.dispatchEvent(new Event("something"));
    

    Cool!


    Let's see it working with your code now. Here's a demo.

    // include function Emitter from above
    
    function MyClass(text){
    
      Emitter.call(this);
    
      function show() {
        console.log("MyText:", text);
        this.dispatchEvent(new Event("end"));
      }
    
      this.show = show;
    }
    
    function onEnd(event){
      console.log("Event dispatched:", event);
    }
    
    function run(){
      var myInstance = new MyClass("I have something to say...");
      myInstance.addEventListener("end", onEnd, false);
      myInstance.show();
    }
    
    run();
    

    Output

    MyText: I have something to say... 
    Event dispatched: Event {
      bubbles: false
      cancelBubble: false
      cancelable: false
      clipboardData: undefined
      currentTarget: null
      defaultPrevented: false
      eventPhase: 0
      path: NodeList[0]
      returnValue: true
      srcElement: null
      target: null
      timeStamp: 1406332794168
      type: "end"
      __proto__: Event
    }
    

    Lastly, here's a version of Emitter that's compatible with ecmascript < 5

    // IE < 9 compatible
    function Emitter() {
      var eventTarget = document.createDocumentFragment();
    
      function addEventListener(type, listener, useCapture, wantsUntrusted) {
        return eventTarget.addEventListener(type, listener, useCapture, wantsUntrusted);
      }
    
      function dispatchEvent(event) {
        return eventTarget.dispatchEvent(event);
      }
    
      function removeEventListener(type, listener, useCapture) {
        return eventTarget.removeEventListener(type, listener, useCapture);
      }
    
      this.addEventListener = addEventListener;
      this.dispatchEvent = dispatchEvent;
      this.removeEventListener = removeEventListener;
    }
    

    See document.createEvent for firing events in legacy browsers

    You could make a polyfill like this (untested)

    if (typeof Event !== "function") {
      function Event(type) {
        var e = document.createEvent("Event");
        e.initEvent(type, true, true);
        return e;
      }
    }