addEventListener on custom object

Viewed 91664

I've created an object that has several methods. Some of these methods are asynchronous and thus I want to use events to be able to perform actions when the methods are done. To do this I tried to add the addEventListener to the object.

jsfiddle

var iSubmit = {
    addEventListener: document.addEventListener || document.attachEvent,
    dispatchEvent: document.dispatchEvent,
    fireEvent: document.fireEvent,   


    //the method below is added for completeness, but is not causing the problem.

    test: function(memo) {
        var name = "test";
        var event;
        if (document.createEvent) {
            event = document.createEvent("HTMLEvents");
            event.initEvent(name, true, true);
        } else {
            event = document.createEventObject();
            event.eventType = name;
        }
        event.eventName = name;
        event.memo = memo || { };

        if (document.createEvent) {
            try {
                document.dispatchEvent(event);
            } catch (ex) {
                iAlert.debug(ex, 'iPushError');
            }
        } else {
            document.fireEvent("on" + event.eventType, event);
        }
    }
}

iSubmit.addEventListener("test", function(e) { console.log(e); }, false);


//This call is added to have a complete test. The errors are already triggered with the line before this one.

iSubmit.test();

This will return an error: Failed to add eventlisterens: TypeError: 'addEventListener' called on an object that does not implement interface EventTarget."

Now this code will be used in a phonegap app and when I do, it is working on android/ios. During testing, however, it would be nice if I could get it to work in at least a single browser.

PS> I know I could enable bubbling and then listen to the document root, but I would like to have just a little bit OOP where each object can work on its own.

13 Answers

Here's a simple event emitter:

class EventEmitter {
    on(name, callback) {
        var callbacks = this[name];
        if (!callbacks) this[name] = [callback];
        else callbacks.push(callback);
    }

    dispatch(name, event) {
        var callbacks = this[name];
        if (callbacks) callbacks.forEach(callback => callback(event));
    }
}

Usage:

var emitter = new EventEmitter();

emitter.on('test', event => {
    console.log(event);
});

emitter.dispatch('test', 'hello world');

I have been able to achieve this by wrapping an element in javascript class. Important point is that the element does not have to exist in dom. Also, the element tag name can be anything such as the custom class name.

'''

class MyClass
{   
    
    constructor(options ) 
    {     
    
  
        this.el =  document.createElement("MyClass");//dummy element to manage events.    
        this.el.obj= this; //So that it is accessible via event.target.obj
      
    }

    addEventListener()
    {
 
        this.el.addEventListener(arguments[0],arguments[1]);

    }
    
 

     raiseEvent()
         {
//call this function or write code below when the event needs to be raised.

            var event = new Event('dataFound');
            event.data = messageData;
            this.el.dispatchEvent(event);
        }
}


let obj = new MyClass();
obj.addEventListener('dataFound',onDataFound);

function onDataFound()
{ 
console.log('onDataFound Handler called');
}

'''

Here is how you do this with Node.js style syntax in the browser.

The Events class:

  • stores callbacks in a hash associated with event keys
  • triggers the callbacks with the provided parameters

To add the behavior to your own custom classes just extend the Events object (example below).

class Events {
  constructor () {
    this._callbacks = {}
  }

  on (key, callback) {
    // create an empty array for the event key
    if (this._callbacks[key] === undefined) { this._callbacks[key] = [] }
    // save the callback in the array for the event key
    this._callbacks[key].push(callback)
  }

  emit (key, ...params) {
    // if the key exists
    if (this._callbacks[key] !== undefined) {
      // iterate through the callbacks for the event key
      for (let i=0; i<this._callbacks[key].length; i++) {
        // trigger the callbacks with all provided params
        this._callbacks[key][i](...params)
      }
    }
  }
}


// EXAMPLE USAGE

class Thing extends Events {
  constructor () {
    super()
    setInterval(() => {
      this.emit('hello', 'world')
    }, 1000)
  }
}

const thing = new Thing()

thing.on('hello', (data) => {
  console.log(`hello ${data}`)
})

Here is a link a github gist with this code: https://gist.github.com/alextaujenis/0dc81cf4d56513657f685a22bf74893d

For anyone that's looking for an easy answer that works. I visited this document, only to learn that most browser doesn't support it. But at the bottom of the page, there was a link to this GitHub page that basically does what the Object.watch() and Object.unwatch() would have done, and it works for me!

Here's how you can watch for changes

/*
 * object.watch polyfill
 *
 * 2012-04-03
 *
 * By Eli Grey, http://eligrey.com
 * Public Domain.
 * NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
 * https://gist.github.com/eligrey/384583
 */

// object.watch
if (!Object.prototype.watch) {
    Object.defineProperty(Object.prototype, "watch", {
          enumerable: false
        , configurable: true
        , writable: false
        , value: function (prop, handler) {
            var
              oldval = this[prop]
            , newval = oldval
            , getter = function () {
                return newval;
            }
            , setter = function (val) {
                oldval = newval;
                return newval = handler.call(this, prop, oldval, val);
            }
            ;

            if (delete this[prop]) { // can't watch constants
                Object.defineProperty(this, prop, {
                      get: getter
                    , set: setter
                    , enumerable: true
                    , configurable: true
                });
            }
        }
    });
}

// object.unwatch
if (!Object.prototype.unwatch) {
    Object.defineProperty(Object.prototype, "unwatch", {
          enumerable: false
        , configurable: true
        , writable: false
        , value: function (prop) {
            var val = this[prop];
            delete this[prop]; // remove accessors
            this[prop] = val;
        }
    });
}

And this should be your code:

var object = {
    value: null,
    changeValue: function(newValue) {
        this.value = newValue;
    },
    onChange: function(callback) {
        this.watch('value', function(obj, oldVal, newVal) {
            // obj will return the object that received a change
            // oldVal is the old value from the object
            // newVal is the new value from the object

            callback();
            console.log("Object "+obj+"'s value got updated from '"+oldValue+"' to '"+newValue+"'");
            // object.changeValue("hello world");
            // returns "Object object.value's value got updated from 'null' to 'hello world'";

            // and if you want the function to stop checking for
            // changes you can always unwatch it with:
            this.unwatch('value');

            // you can retrieve information such as old value, new value
            // and the object with the .watch() method, learn more here:
            // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/watch
        })
    }
};

or as short as:

var object = { user: null };

// add a watch to 'user' value from object
object.watch('user', function() {
    // object user value changed
});
Related