Custom Events in CLASS - javascript

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!

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!')

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

Related

how to define eventEmitter properly in javascript

I need to define a helper object in which I need a function that will execute on each "orientationchange" event of window.
My code as below and it is not in correct form. Can you please help me how can I define onRotate properly so that I can use it globally.
<script type="text/javascript">
'use strict';
var GlobalHelper = (function () {
var me = {
onRotate: onRotate // this is where I am struggling
}
function onRotate() {
window.addEventListener("orientationchange", function (event) {
console.log(event.target.screen.orientation.angle);
});
}
return me;
})();
GlobalHelper.onRotate = function (e) {
console.log(e);
}
</script>
I found an answer to my own question. It was actually pretty easy.
<script type="text/javascript">
'use strict';
var GlobalHelper = (function () {
var me = {
onRotate: function (e) { }
}
function _init() {
window.addEventListener("orientationchange", function (event) {
me.onRotate(event);
});
}
_init();
return me;
})();
GlobalHelper.onRotate = function (e) {
console.log(e);
}
</script>
Another option is to add custom programmable method for creating listeners and bonded functions. You can test it by Running code snippet and pressing keyboard keys while focused on results window.
// Set app object
const bindFn = e => {
// Set object to hold your functions
let fns = {};
// Set event listener to run all functions in fns object
window.addEventListener(e, event => {
for(const f in fns) fns[f](event);
});
// Return method to set new functions
// You can extend it with another function for
// deleting or altering, as you wish...
return {
add: (name, fn) => {
if(!fns[name]) fns[name] = fn;
else console.log(`Function with name ${name} already exist, skipping...`);
}
};
};
// Set binder with proper event
// here we do orientation change
const OrCh = bindFn("orientationchange");
// Add some function to execute on this event
OrCh.add('log', event => console.log(event.target.screen.orientation.angle));
//
// Test with another event that easy to trigger on stackoverflow
const KeyP = bindFn("keypress");
// Add logger
KeyP.add('log', event => console.log(`key pressed: ${event.code}`));
// Add something else
KeyP.add('logAlt', event => console.log(`Alternative function: ${event.code}`));

Send event when module was executed

I'm really stuck on this.. I need to send an event when both Load module and Hide module code was executed, and only then send the event. Ideas on how to achieve this?
// Load module
(
function() {
var s=document.createElement('script');
s.type='text/javascript';
s.async=true;
s.src='https://example.com/bundles.js';
var x = document.getElementsByTagName('script')[0];
x.parentNode.insertBefore(s, x);
}
)();
// Hide module
var inverval = setInterval(hideClass, 100);
function hideClass () {
if ($(".class").hide().length > 0) clearInterval(inverval);
}
// When both happend = Send a event to Google Analytics
DigitalData.push({'event':Module, 'eventLabel':'Page'});
If this is your only option, then perhaps there's something you are going about wrongly. Anyway, let's see ... Only when both events have taken place.
var HandleTwoEvents = function (key1, key2) {
this.count = 0;
this.pack = [];
$self = this;
this.startListening = function(fn) {
fn = fn || function () {}
window.addEventListener(key1, function (ev) {
if ($self.pack.indexOf(key1) < 0) {
$self.pack.push(key1);
$self.count++;
if ($self.count == 2) {
fn();
$self.count = 0;
}
}
console.log(key1, ev);
});
window.addEventListener(key2, function (ev) {
if ($self.pack.indexOf(key2) < 0) {
$self.pack.push(key2);
$self.count++;
if ($self.count == 2) {
fn();
$self.count = 0;
}
}
console.log(key2, ev);
});
}
}
Forgive me, i always use this function to create events
function createEvent(name, obj) {
var evt = document.createEvent("Event");
evt.initEvent(name, true, true);
evt.data = obj;
dispatchEvent(evt);
}
Now, to log both events ...
var both = new HandleTwoEvents("EventKeyOne", "EventKeyTwo");
both.startListening(function () {console.log("This means that both Events have taken place")});
Now, let's test ...
createEvent("EventKeyOne", {});
//key, data are the arguments ... function defined in startListening above does not execute, and upon inspection, both.count is seen to be 1
createEvent("EventKeyTwo", {});
//Now, function executes.
//It also works if "EventKeyTwo" is raised before "EventKeyOne"
Happy Coding!
PS: I'm sure there's a better way to handle the use of the $self variable, with some function binding, i guess. I've never been able to learn it.

Trouble with setInterval in an object's method

I can't figure out why when I call the reset method of the object, the timer is still null. I simplified version of my object is below, followed by the jQuery that constructs a new object and runs the code. See UPPERCASE comments for my specific question points. Thanks!
var countdownTimer = {
// Default vars
milliseconds: 120000,
interval: 1000,
timer: false,
/* ... stuff ... */
countdown: function () {
var root = this;
var originalTime = root.milliseconds;
/* ... stuff */
// IN MY MIND THIS NEXT LINE SETS THE INSTANCE OF THIS OBJECT'S TIMER PROPERTY TO THE setIterval's ID. BUT DOESN'T SEEM TO BE CORRECT. WHY?
root.timer = setInterval(function () {
if (root.milliseconds < 1) {
clearInterval(root.timer); // THIS LINE SEEMS TO WORK
root.countdownComplete(); // callback function
return false;
}
root.milliseconds = root.milliseconds - root.interval;
/* .... stuff ... */
}, root.interval);
},
start: function (ms) {
if (ms) {
this.milliseconds = ms;
}
if(this.timer) {
clearInterval(this.timer); // NOT SURE IF THIS WORKS OR NOT
}
this.countdown();
},
reset: function (ms) {
var root = this;
if(root.timer) {
clearInterval(root.timer); // THIS DOES NOT WORK
} else {
console.log('timer not exist!!!!'); // ALWAYS END UP HERE. WHY?
}
/* .... stuff ... */
},
countdownComplete: function() { }
};
// Setting up click events to create instances of the countdownTimer
$(function () {
var thisPageCountdown = 4000;
$('[data-countdown]').on('click', '[data-countdown-start], [data-countdown-reset]', function () {
var $this = $(this);
var $wrap = $this.closest('[data-countdown]');
// create instance of countdownTimer
var myCountdown = Object.create(countdownTimer);
if ($this.is('[data-countdown-start]')) {
$this.hide();
$('[data-countdown-reset]', $wrap).css('display', 'block');
myCountdown.$wrap = $wrap;
myCountdown.start(thisPageCountdown);
// myCountdown.countdownComplete = function() {
// alert("Updated Callback!");
// };
}
if ($this.is('[data-countdown-reset')) {
$this.hide();
$('[data-countdown-start]', $wrap).css('display', 'block');
// RESET CALLED HERE BUT DOESN'T WORK RIGHT. SAYS myCountdown.timer IS STILL null. WHY?
myCountdown.reset(thisPageCountdown);
}
});
});
When you use var myCountdown = Object.create(countdownTimer); inside of your click function callback you are scoping it only to that callback and once the callback has executed it is garbage collected. You need to only create one instance of the countdownTimer, and it should be outside of your click event handler.
var thisPageCountdown = 4000;
// create instance of countdownTimer
var myCountdown = Object.create(countdownTimer);
$('[data-countdown]').on('click', '[data-countdown-start], [data-countdown-reset]', function () {
var $this = $(this);
var $wrap = $this.closest('[data-countdown]');
TL;DR You can fix your issue by avoiding use of the keyword this in static methods.
When you use the keyword this in a static javascript method, it refers to the item before the last dot from the call point. Example:
foo.bar(); // inside, this will refer to foo
foo.bar.foobar(); //inside, this will refer to foo.bar
var a = foo.bar.foobar();
a(); //this will refer to either null or window - oops
To prevent this behavior, you should always use the fully qualified name reference in static methods instead of relying on the this keyword.
Example from above:
reset: function (ms) {
//var root = this; // don't do this
if(countdownTimer.timer) {
clearInterval(countdownTimer.timer);
} else {
console.log('timer not exist!!!!');
}
/* .... stuff ... */
}

Prototype custom event NOT on a DOM element

Same as this question, but Prototype library specific:
I've got a Browser class, and I want to fire and observe custom events for this class. Prototype's custom event system only lets me bind to and fire events on DOM elements. Here's my first idea as to an alternative:
function Browser() {
this.event = new Element('span');
}
Browser.prototype.render = function() {
this.event.fire('browser:render');
}
var browser = new Browser();
browser.event.observe('browser:render', function() { ... });
Is there a better way to do this?
Thanks in part to Frits van Campen's advice, I made my own that serves my needs, a little more sophisticated than Frits' sample.
function EventManager(target) {
var target = target || window,
events = {};
this.observe = function(eventName, cb) {
if (events[eventName]) events[eventName].push(cb);
else events[eventName] = new Array(cb);
return target;
};
this.stopObserving = function(eventName, cb) {
if (events[eventName]) {
var i = events[eventName].indexOf(cb);
if (i > -1) events[eventName].splice(i, 1);
else return false;
return true;
}
else return false;
};
this.fire = function(eventName) {
if (!events[eventName]) return false;
for (var i = 0; i < events[eventName].length; i++) {
events[eventName][i].apply(target, Array.prototype.slice.call(arguments, 1));
}
};
}
Then I can do:
Function Browser() {
this.event = new EventManager(this);
}
Browser.prototype.render = function() {
this.event.fire("render");
}
browser = new Browser();
browser.event.observe("render", function() { alert("Rendered"); });
Why not just build your own event handling system? It really doesn't take much.
function MyClass() {
this.handlers = {};
}
MyClass.prototype.registerHandler = function(event, callback) {
this.handlers[event] = callback;
};
MyClass.prototype.fire = function(event) {
this.handlers[event]();
};
var instance = new MyClass();
instance.registerHandler('an event', function () {
alert('hi!');
});
instance.fire('an event');
I use the document to fire all my custom events. Works great.
document.on("customEvent:blah", this.doCustomEvent.bind(this));
document.fire("customEvent:blah");

How to call public method from a event handler

I have the function below.
Everything works fine except for the Push, Pop and Remove method. These method should be called by the event-handler. This event is fired by the Google Maps API.
The problem is that when the event is fired, these method are not found. I have a "Push is not defined" error message.
I tried with this but that's not working.
How do I call the public method from the event handler?
function Track(mapContainer) {
var map = mapContainer;
var points = new Array();
var isEditMode = false;
var clickListener;
this.Push = function(point) { ... }
this.Pop = function() { ... }
this.Remove = function(point) { ... }
//Enable / disable the marker placements
this.PlaceWaypoint = function(isPlacing) {
if (isPlacing != true) {
if (clickListener != null) {
google.maps.event.removeListener(clickListener);
clickListener = null;
}
} else {
clickListener = map.AddEvent("click", function(event) {
if (!IsDoubleClick()) {
var point = map.PlaceMarker(new WayPoint(event.latLng))
point.RemoveListener(function() { Remove(point); });
Push(point);
} else {
Pop();
}
});
}
}
}
You've got a closure/binding problem. One convention that is frequently used it to assign a variable called self of that, which can later be used in place of this, thanks to the closure properties of JS.
function Track(mapContainer) {
var map = mapContainer,
points = new Array(),
isEditMode = false,
clickListener,
// Make a variable self that points to this, that can be used inside closures
// where the original context is lost
self = this;
this.Push = function(point) { ... }
this.Pop = function() { ... }
this.Remove = function(point) { ... }
//Enable / disable the marker placements
this.PlaceWaypoint =
function(isPlacing) {
if (isPlacing != true) {
if (clickListener != null) {
google.maps.event.removeListener(clickListener);
clickListener = null;
}
} else {
clickListener = map.AddEvent("click", function(event) {
if (!IsDoubleClick()) {
var point = map.PlaceMarker(new WayPoint(event.latLng))
point.RemoveListener(function() { Remove(point); });
// Use the closure reference self instead of this
self.Push(point);
} else {
// Use the closure reference self instead of this
self.Pop();
}
});
};
}
this always refers to the context of the current function, so if you use this in your event handler it refers to that function calls this, not the this in your Track function.
To create a closure that accesses the this of an outer scope, you need to assign that this to a new variable which can be accessed from the inner function:
var self = this;
this.PlaceWaypoint = function(isPlacing) {
// ...
self.Pop();
// ...
}
First of all Pop and Push is not global, second this in the inner scope has another meaning. So you can use closure and rename the "this" to variable of more global scope.
function Track(mapContainer) {
//....
var $this = this;
//Enable / disable the marker placements
this.PlaceWaypoint = function(isPlacing) {
if (isPlacing != true) {
if (clickListener != null) {
google.maps.event.removeListener(clickListener);
clickListener = null;
}
} else {
clickListener = map.AddEvent("click", function(event) {
if (!IsDoubleClick()) {
var point = map.PlaceMarker(new WayPoint(event.latLng))
point.RemoveListener(function() { $this.Remove(point); });
$this.Push(point);
} else {
$this.Pop();
}
});
}
}
}

Categories

Resources