8

I need to launch custom events from CLASS. I know to do this with DOM objects and jquery, using triggerHandler, like $(object)..triggerHandler("inputChange", {param:X}); The problem is when i try this with a Class, like this:

    var MyClass = (function(){

        var static_var = 1;

        var MyClass = function () {

            var privateVar;
            var privateFn = function(){ alert('Im private!'); };

            this.someProperty = 5;
            this.someFunction = function () {
                alert('Im public!');
            };
            this.say = function() {
                alert('Num ' + this.someProperty);
                $(this).triggerHandler("eventCustom");
            }
            this.alter = function() {
                this.someProperty ++;
            }
        };

        return MyClass;

    })();

    TheClass = new MyClass();

    $(TheClass).on('eventCustom', function() {
        alert('Event!');
    });

    TheClass.say();

This doesn't launch warnings or errors, but the events listener is not working (or event is not dispatched). I think the jQuery event system doesn't work with not DOM object, correct?

Any other way (I need events, not callbacks for my specific case) to launch the events?

Thanks a lot!

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Zenth
  • 769
  • 2
  • 7
  • 23
  • *"i think the jqeury event system dosent work with not DOM object"* Incorrect: http://api.jquery.com/jQuery/#working-with-plain-objects. The question is whether it works with objects created with constructor functions. – Felix Kling Jun 18 '13 at 09:22

2 Answers2

12

I wrote an ES6 event class for nowadays in under 100 lines of code without using JQuery. If you don't want to use DOM-events you can extend your class, which should deal with Events.

For listening to events, you can use on, once, onReady, onceReady. On is execute the callbackfunction every time the label is trigger. Once only one time. The "ready"-functions execute the callback, if the label had been already triggerd before.

For triggering an event, use a trigger. To remove an eventhandler, use off.

I hope the example makes it clear:

class ClassEventsES6 {
                constructor() {
                    this.listeners = new Map();
                    this.onceListeners = new Map();
                    this.triggerdLabels = new Map();
                }

                // help-function for onReady and onceReady
                // the callbackfunction will execute, 
                // if the label has already been triggerd with the last called parameters
                _fCheckPast(label, callback) {
                    if (this.triggerdLabels.has(label)) {
                        callback(this.triggerdLabels.get(label));
                        return true;
                    } else {
                        return false;
                    }
                }

                // execute the callback everytime the label is trigger
                on(label, callback, checkPast = false) {
                    this.listeners.has(label) || this.listeners.set(label, []);
                    this.listeners.get(label).push(callback);
                    if (checkPast)
                        this._fCheckPast(label, callback);
                }

                // execute the callback everytime the label is trigger
                // check if the label had been already called 
                // and if so excute the callback immediately
                onReady(label, callback) {
                    this.on(label, callback, true);
                }

                // execute the callback onetime the label is trigger
                once(label, callback, checkPast = false) {
                    this.onceListeners.has(label) || this.onceListeners.set(label, []);
                    if (!(checkPast && this._fCheckPast(label, callback))) {
                        // label wurde nocht nicht aufgerufen und 
                        // der callback in _fCheckPast nicht ausgeführt
                        this.onceListeners.get(label).push(callback);
                }
                }
                // execute the callback onetime the label is trigger
                // or execute the callback if the label had been called already
                onceReady(label, callback) {
                    this.once(label, callback, true);
                }

                // remove the callback for a label
                off(label, callback = true) {
                    if (callback === true) {
                        // remove listeners for all callbackfunctions
                        this.listeners.delete(label);
                        this.onceListeners.delete(label);
                    } else {
                        // remove listeners only with match callbackfunctions
                        let _off = (inListener) => {
                            let listeners = inListener.get(label);
                            if (listeners) {
                                inListener.set(label, listeners.filter((value) => !(value === callback)));
                            }
                        };
                        _off(this.listeners);
                        _off(this.onceListeners);
                }
                }

                // trigger the event with the label 
                trigger(label, ...args) {
                    let res = false;
                    this.triggerdLabels.set(label, ...args); // save all triggerd labels for onready and onceready
                    let _trigger = (inListener, label, ...args) => {
                        let listeners = inListener.get(label);
                        if (listeners && listeners.length) {
                            listeners.forEach((listener) => {
                                listener(...args);
                            });
                            res = true;
                        }
                    };
                    _trigger(this.onceListeners, label, ...args);
                    _trigger(this.listeners, label, ...args);
                    this.onceListeners.delete(label); // callback for once executed, so delete it.
                    return res;
                }
            }
            
// +++ here starts the example +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
class TestClassEvents extends ClassEventsES6 {
     constructor() {
        super();
        this.once('sayHallo', this.fStartToTalk);
        this.on('sayHallo', this.fSayHallo);
     }

     fStartToTalk() {
         console.log('I start to talk... ');
     }

     fSayHallo(name = 'Nobody') {
        console.log('Hallo ' + name);
     }
}

let testClassEvents = new TestClassEvents();

testClassEvents.trigger('sayHallo', 'Tony');
testClassEvents.trigger('sayHallo', 'Tim');

testClassEvents.onReady('sayHallo', e => console.log('I already said hello to ' + e));
testClassEvents.trigger('sayHallo', 'Angie');
testClassEvents.off('sayHallo');
testClassEvents.trigger('sayHallo', 'Peter');
console.log('I dont say hallo to Peter, because the event is off!')
marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Bergi
  • 323
  • 3
  • 11
8

Your understanding of how javascript works is limited since you are approaching it from a traditional OOP point of view. Take a look at this fiddle http://jsfiddle.net/9pCmh/ & you will see that you can actually pass functions as variables to other functions. There are no classes in javascript, only functions which can be closures which can be made to emulate traditional classes:

var MyClass = (function(){

    var static_var = 1;

    var MyClass = function ( callback ) {

        var privateVar;
        var privateFn = function(){ alert('Im private!'); };

        this.someProperty = 5;
        this.someFunction = function () {
            alert('Im public!');
        };
        this.say = function() {
            alert('Num ' + this.someProperty);
            callback();
        }
        this.alter = function() {
            this.someProperty ++;
        }
    };

    return MyClass;

})();

TheClass = new MyClass(function() {
    alert('Event!');
});

TheClass.say();

Alternatively you could create a function in your "class" to configure the callback/trigger instead of passing it into the constructor.

Have a look at this as a start for your further reading on this concept... How do JavaScript closures work?

Edit

To appease those critics looking for an eventQueue here is an updated jsfiddle :)

http://jsfiddle.net/Qxtnd/9/

var events = new function() {
  var _triggers = {};

  this.on = function(event,callback) {
      if(!_triggers[event])
          _triggers[event] = [];
      _triggers[event].push( callback );
    }

  this.triggerHandler = function(event,params) {
      if( _triggers[event] ) {
          for( i in _triggers[event] )
              _triggers[event][i](params);
      }
  }
};

var MyClass = (function(){

      var MyClass = function () {

          this.say = function() {
              alert('Num ' + this.someProperty);
              events.triggerHandler('eventCustom');
          }
      };

      return MyClass;

  })();

  TheClass = new MyClass();

  events.on('eventCustom', function() {
      alert('Event!');
  });
  events.on('eventCustom', function() {
      alert('Another Event!');
  });

  TheClass.say();
Community
  • 1
  • 1
Precastic
  • 3,742
  • 1
  • 24
  • 29
  • While this might work, the event system offered by jQuery is superior by far. It doesn't really answer the question IMO, the OP explicitly stated *"Any other way (i need events, not callbacks for my specific case) to launch the events?"*. And they already know about passing functions to other functions, they are doing this when calling `.on`. – Felix Kling Jun 18 '13 at 09:53
  • @FelixKling Please explain to me how jQuery's events are superior to javascript closures/callbacks? jQuery events are callbacks. In essence an event is a callback in javascript, the only difference is you can queue events which you can easily do with javascript callbacks. The OP's understanding of what an event in javascript is is flawed because he is thinking in OOP terms. I know I came from C++ & use PHP so had a huge learning curve. – Precastic Jun 18 '13 at 10:04
  • In your example you can only pass *one callback* to an instance, at the moment of its creation. With a proper event system, you can bind multiple different event handlers at any time. That's what I meant. Of course "callbacks" is just a popular term a function passed to another function. – Felix Kling Jun 18 '13 at 10:12
  • thats it, my problem is i need to "bind" some "actions" to the same event, in diferent parts and "moments" in my code. The callback its not solution for this case. I need to trigger events. – Zenth Jun 18 '13 at 10:24
  • @Zenth Have a look at the edit - I have included an example of an event queue – Precastic Jun 18 '13 at 10:27
  • PS. I have not included any error checking for valid callbacks etc. This you can do as you require. – Precastic Jun 18 '13 at 10:31
  • jajaja this is a trap, its a "fake-event" system by checking functions, but.. its resolve my problem :P I attach in any way this event-class to MyClass for use like "TheClass.on('eventname')", "TheClass.off('eventname')", works but "hurts" i cant use the jquery default event system in a class. Thanks for the solution. – Zenth Jun 18 '13 at 11:50
  • only one correction: this.triggerHandler = function(event, params) { var prms = params || {} ; if( _triggers[event] ) { for( i in _triggers[event] ) _triggers[event][i](prms); } } – Zenth Jun 18 '13 at 12:01
  • @Zenth Whoops... good point... updated! You could also use _triggers[event][i].apply( this, Array.prototype.slice.call(arguments, 1) ); if you wanted to pass multiple parameters. – Precastic Jun 18 '13 at 12:14
  • NP i pass the parameters always in object {}. Now i have another problem.. i have declared triggerHandler(), this.on() and this.off(), all work ok, buts i need in _triggers[event][i](prms); the 1º parameter need to be the SAME class (like the "e" object in regular events), im try to make in some ways, but only works set private var like var that = this; and send like _triggers[event][i](that, prms); but this return the object at initial moment NOT at the trigger moment (vars in object all null, not with the values) any idea to return the class? Thanks – Zenth Jun 18 '13 at 13:00
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/31946/discussion-between-precastic-and-zenth) – Precastic Jun 18 '13 at 13:08
  • Looks like jsfiddle is oudated, 'eventCustom' is hard coded in triggerHandler – morgar Jan 15 '18 at 19:48