Creating events in Javascript - javascript

If I have a list of functions that are serving as event subscriptions, is there an advantage to running through them and calling them with .call() (B below), or more directly (A)? The code is below. The only difference I can see is that with .call you can control what this is set to. Is there any other difference besides that?
$(function() {
eventRaiser.subscribe(function() { alert("Hello"); });
eventRaiser.subscribe(function(dt) { alert(dt.toString()); });
eventRaiser.DoIt();
});
var eventRaiser = new (function() {
var events = [];
this.subscribe = function(func) {
events.push(func);
};
this.DoIt = function() {
var now = new Date();
alert("Doing Something Useful");
for (var i = 0; i < events.length; i++) {
events[i](now); //A
events[i].call(this, now); //B
}
};
})();

No, there is no other difference. If you follow the second approach though, you can extend subscribe so that the "clients" can specify the context to be used:
this.subscribe = function(func, context) {
events.push({func: func, context: context || this});
};
this.DoIt = function() {
var now = new Date();
alert("Doing Something Useful");
for (var i = 0; i < events.length; i++) {
events[i].func.call(events[i].context, now);
}
};

If you decide to go with method B, you can allow the function that subscribes to the event, to perform event specific functions.
For instance, you could use this.getDate() inside your function (you would have to create the method) instead of passing the date as a parameter. Another helpful method inside an Event class would be this.getTarget().

Related

Why can't I remove my event listener?

I have an issue with removeEventListener, it doesn't seem to work at all, I've seen some other questions on this site but I don't get it, can you help me?
displayImg() {
console.log('img')
for (var i = 1; i <= 4; i++) {
var line = "l"+i;
var position = 0;
var addDivLine = document.createElement('div');
addDivLine.className = 'line';
addDivLine.id = line;
document.getElementById('container').appendChild(addDivLine);
for (var j = 1; j <= 7; j++) {
var block = "b"+j;
var element = line+"-"+block;
var addDivBlock = document.createElement('div');
addDivBlock.className = 'block';
addDivBlock.id = element;
document.getElementById(line).appendChild(addDivBlock);
memory.addEvent(element);
};
};
showImage(event) {
event.preventDefault();
memory.clickedBlock++;
var block = event.target.id;
memory.removeEvent(block);
}
addEvent(id){
document.getElementById(id).addEventListener('click', function(){memory.showImage(event)});
},
removeEvent(id){
console.log("remove");
document.getElementById(id).removeEventListener('click', function(){memory.showImage(event)});
},
I am creating div elements then put an eventListener on them, I call the same function to remove the event, I use the same id, is there something that I forgot? I probably don't fully understand how it really works.
Thanks a lot!
In this two lines:
.addEventListener('click', function() { memory.showImage(event) });
and
.removeEventListener('click', function() { memory.showImage(event) });
function() { memory.showImage(event) } are two different functions. You need to provide reference to the same function in both cases in order to bind/unbind listener. Save it so some variable and use in both places:
.addEventListener('click', memory.showImage);
.removeEventListener('click', memory.showImage);
For example using directly memory.showImage will work properly as it's the same function in both cases.
The function looks like the same but its reference would be different. So, define the function in a scope where it's available for both function and use the reference in both case.
var callback = function(){memory.showImage(event)};
addEvent(id){
document.getElementById(id).addEventListener('click', callback);
}
removeEvent(id){
console.log("remove");
document.getElementById(id).removeEventListener('click', callback);
}

AngularJS: Listen to events, one after the other

is there an angular-way to listen for events that occur one after the other? For example, I want to listen on a $rootScope for the $routeChangeSuccess and the $viewContentLoaded event. When the first event occurs, and after that, the second event occurs, I want to call a callback.
Is this possible? Or do I have to write it on my own? It would also be nice to configure if the order of the events is important or not. Or any ideas, how to implement such a behaviour?
Update
Because I haven't found anything on the web, I came up with my own solution. I think it works, but I don't know if there are any drawbacks with this method.
And any suggestions how to integrate this global into an AngularJS project? Or even as a bower component? Should I attach the function to a scope, or to the rootScope? Any help is appreciated!
Here is the Plunker link and the code: http://plnkr.co/edit/slfvUlFCh7fAlE4IPt8o?p=preview
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope) {
$scope.events = [];
var successiveOn = function(events, eventScope, callback, orderImportant) {
// array for the remove listener callback
var removeListenerMethods = [];
// events array that is passed to the callback method
var eventsArr = [];
// how many events are fired
var eventCount = 0;
// how many events should be fired
var targetEventCount = events.length;
// track the next event, only for orderImportant==true
var nextEvent = events[0];
// iterate over all event strings
for (var i = 0; i < events.length; i++) {
var event = events[i];
// attach an $on listener, and store the remove listener function
var removeListener = eventScope.$on(event, function(evt) {
if (evt.name == nextEvent || !orderImportant) {
++eventCount;
nextEvent = events[eventCount];
eventsArr.push(evt);
// if all events has fired, call the callback method and reset
if (eventCount >= targetEventCount) {
callback(eventsArr);
nextEvent = events[0];
eventCount = 0;
eventsArr = [];
}
}
});
removeListenerMethods.push(removeListener);
}
// the return function is a anonymous function which calls all the removeListener methods
return function() {
for (var i = 0; i < removeListenerMethods.length; i++) {
removeListenerMethods[i]();
}
}
}
// order is unimportant
var removeListeners = successiveOn(["orderUnimportant1", "orderUnimportant2", "orderUnimportant3"], $scope, function(events) {
var str = "Events in order of trigger: ";
for (var i = 0; i < events.length; i++) {
str += events[i].name + ", ";
}
$scope.events.push(str);
}, false);
$scope.$broadcast("orderUnimportant1");
$scope.$broadcast("orderUnimportant2");
$scope.$broadcast("orderUnimportant3"); // Events were triggered 1st time
$scope.$broadcast("orderUnimportant3");
$scope.$broadcast("orderUnimportant2");
$scope.$broadcast("orderUnimportant1"); // Events were triggered 2nd time, order doesn't matter
removeListeners();
// order is important!
var removeListeners = successiveOn(["OrderImportant1", "OrderImportant2", "OrderImportant3"], $scope, function(events) {
var str = "Events in order of trigger: ";
for (var i = 0; i < events.length; i++) {
str += events[i].name + ", ";
}
$scope.events.push(str);
}, true);
$scope.$broadcast("OrderImportant1");
$scope.$broadcast("OrderImportant2");
$scope.$broadcast("OrderImportant3"); // Events were triggered
$scope.$broadcast("OrderImportant1");
$scope.$broadcast("OrderImportant3");
$scope.$broadcast("OrderImportant2"); // Events were NOT triggered
removeListeners();
});
Use the $q service aka the promise.
var routeChange = $q.defer();
var contentLoaded = $q.defer();
$rootScope.$on("$routeChangeSuccess", function() {
routeChange.resolve();
});
$rootScope.$on("$viewContentLoaded", function() {
contentLoaded.resolve();
});
$q.all([contentLoaded.promise, routeChange.promise]).then(function() {
//Fire your callback here
});
Specific Order:
routeChange.then(function() {
contentLoaded.then(function () {
//Fire callback
});
});
Without the nasty callback soup:
var routeChangeHandler = function() {
return routeChange.promise;
}
var contentLoadedHandler = function() {
return contentLoaded.promise
}
var callback = function() {
//Do cool stuff here
}
routeChangeHandler
.then(contentLoadedHandler)
.then(callback);
Thats pretty damn sexy...
More info on chained promises
I think Stens answer is the best way to tackle this if you want to do it with pure AngularJS facilities. However, often enough such cases indicate that you have to deal with more complex event stuf on a regular basis. If that's the case, I'd advice you to take a look at the Reactive Extensions and the rx.angular.js bridging library.
With the Reactive Extensions for JavaScript (RxJS) you can simplify the code to this:
Rx.Observable.combineLatest(
$rootScope.$eventToObservable('$routeChangeSuccess'),
$rootScope.$eventToObservable('$viewContentLoaded'),
Rx.helpers.noop
)
.subscribe(function(){
//do your stuff here
})

Hammer.js can't remove event listener

I create a hammer instance like so:
var el = document.getElementById("el");
var hammertime = Hammer(el);
I can then add a listener:
hammertime.on("touch", function(e) {
console.log(e.gesture);
}
However I can't remove this listener because the following does nothing:
hammertime.off("touch");
What am I doing wrong? How do I get rid of a hammer listener? The hammer.js docs are pretty poor at the moment so it explains nothing beyond the fact that .on() and .off() methods exist. I can't use the jQuery version as this is a performance critical application.
JSFiddle to showcase this: http://jsfiddle.net/LSrgh/1/
Ok, I figured it out. The source it's simple enough, it's doing:
on: function(t, e) {
for (var n = t.split(" "), i = 0; n.length > i; i++)
this.element.addEventListener(n[i], e, !1);
return this
},off: function(t, e) {
for (var n = t.split(" "), i = 0; n.length > i; i++)
this.element.removeEventListener(n[i], e, !1);
return this
}
The thing to note here (apart from a bad documentation) it's that e it's the callback function in the on event, so you're doing:
this.element.addEventListener("touch", function() {
//your function
}, !1);
But, in the remove, you don't pass a callback so you do:
this.element.removeEventListener("touch", undefined, !1);
So, the browser doesn't know witch function are you trying to unbind, you can fix this not using anonymous functions, like I did in:
Fiddle
For more info: Javascript removeEventListener not working
In order to unbind the events with OFF, you must:
1) Pass as argument to OFF the same callback function set when called ON
2) Use the same Hammer instance used to set the ON events
EXAMPLE:
var mc = new Hammer.Manager(element);
mc.add(new Hammer.Pan({ threshold: 0, pointers: 0 }));
mc.add(new Hammer.Tap());
var functionEvent = function(ev) {
ev.preventDefault();
// .. do something here
return false;
};
var eventString = 'panstart tap';
mc.on(eventString, functionEvent);
UNBIND EVENT:
mc.off(eventString, functionEvent);
HammerJS 2.0 does now support unbinding all handlers for an event:
function(events, handler) {
var handlers = this.handlers;
each(splitStr(events), function(event) {
if (!handler) {
delete handlers[event];
} else {
handlers[event].splice(inArray(handlers[event], handler), 1);
}
});
return this;
}
Here's a CodePen example of what Nico posted. I created a simple wrapper for "tap" events (though it could easily be adapted to anything else), to keep track of each Hammer Manager. I also created a kill function to painlessly stop the listening :P
var TapListener = function(callbk, el, name) {
// Ensure that "new" keyword is Used
if( !(this instanceof TapListener) ) {
return new TapListener(callbk, el, name);
}
this.callback = callbk;
this.elem = el;
this.name = name;
this.manager = new Hammer( el );
this.manager.on("tap", function(ev) {
callbk(ev, name);
});
}; // TapListener
TapListener.prototype.kill = function () {
this.manager.off( "tap", this.callback );
};
So you'd basically do something like this:
var myEl = document.getElementById("foo"),
myListener = new TapListener(function() { do stuff }, myEl, "fooName");
// And to Kill
myListener.kill();

How to properly pass argument in loop to multiple event handlers? [duplicate]

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Javascript closure inside loops - simple practical example
I add event handlers to multiple hrefs on my website with JS like this:
function addButtonListener(){
var buttons = document.getElementsByClassName("selLink");
for (var i = 0; i < buttons.length; i++)
{
button.addEventListener('click',function() { addTosel(i); },true);
}
}
}
But unfortunately to addTosel is passed the last i not the i from the loop. How to pass i accordingly to the object being processed in this moment?
You need to create a closure:
function addButtonListener(){
var buttons = document.getElementsByClassName("selLink");
for (var i = 0; i < buttons.length; i++) {
button.addEventListener('click', function(index) {
return function () {
addTosel(index);
};
}(i), true);
}
}
This way the scope of the handler is bound to the proper context of i.
See this article for more information on this subject.
You need to bind the i variable to the function when its declared. like so
for (var i = 0; i < buttons.length; i++) {
button.addEventListener('click',(function() { addTosel(this); }).bind(i) ,true);
}
Note: I just wrote the code from memory so it may not be perfect, but it is the sulution you're needing, for reference as to the proper way, ie with cross browser shims etc look at:
https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Function/bind
If you're going to take the .bind approach, do it like this.
for (var i = 0; i < buttons.length; i++) {
button.addEventListener('click', addTosel.bind(null, i), true);
}
This makes a new function with null bound as the this value since your function doesn't seem to need it, and the current i bound as the first argument.
Or make your own binder function
var _slice = Array.prototype.slice;
function _binder(func, ctx /*, arg1, argn */) {
var bound_args = _slice.call(arguments, 2);
return function() {
return func.apply(ctx, bound_args.concat(_slice.call(arguments)));
}
}
And then do this.
for (var i = 0; i < buttons.length; i++) {
button.addEventListener('click', _binder(addTosel, null, i), true);
}

JavaScript custom Event Listener

I was wondering if anyone can help me understand how exactly to create different Custom event listeners.
I don't have a specific case of an event but I want to learn just in general how it is done, so I can apply it where it is needed.
What I was looking to do, just incase some folks might need to know, was:
var position = 0;
for(var i = 0; i < 10; i++)
{
position++;
if((position + 1) % 4 == 0)
{
// do some functions
}
}
var evt = document.createEvent("Event");
evt.initEvent("myEvent",true,true);
// custom param
evt.foo = "bar";
//register
document.addEventListener("myEvent",myEventHandler,false);
//invoke
document.dispatchEvent(evt);
Here is the way to do it more locally, pinpointing listeners and publishers:
http://www.kaizou.org/2010/03/generating-custom-javascript-events/
Implementing custom events is not hard. You can implement it in many ways. Lately I'm doing it like this:
/***************************************************************
*
* Observable
*
***************************************************************/
var Observable;
(Observable = function() {
}).prototype = {
listen: function(type, method, scope, context) {
var listeners, handlers;
if (!(listeners = this.listeners)) {
listeners = this.listeners = {};
}
if (!(handlers = listeners[type])){
handlers = listeners[type] = [];
}
scope = (scope ? scope : window);
handlers.push({
method: method,
scope: scope,
context: (context ? context : scope)
});
},
fireEvent: function(type, data, context) {
var listeners, handlers, i, n, handler, scope;
if (!(listeners = this.listeners)) {
return;
}
if (!(handlers = listeners[type])){
return;
}
for (i = 0, n = handlers.length; i < n; i++){
handler = handlers[i];
if (typeof(context)!=="undefined" && context !== handler.context) continue;
if (handler.method.call(
handler.scope, this, type, data
)===false) {
return false;
}
}
return true;
}
};
The Observable object can be reused and applied by whatever constructor needs it simply by mixng the prototype of Observable with the protoype of that constructor.
To start listening, you have to register yourself to the observable object, like so:
var obs = new Observable();
obs.listen("myEvent", function(observable, eventType, data){
//handle myEvent
});
Or if your listener is a method of an object, like so:
obs.listen("myEvent", listener.handler, listener);
Where listener is an instance of an object, which implements the method "handler".
The Observable object can now call its fireEvent method whenever something happens that it wants to communicate to its listeners:
this.fireEvent("myEvent", data);
Where data is some data that the listeners my find interesting. Whatever you put in there is up to you - you know best what your custom event is made up of.
The fireEvent method simply goes through all the listeners that were registered for "myEvent", and calls the registered function. If the function returns false, then that is taken to mean that the event is canceled, and the observable will not call the other listeners. As a result the entire fireEvent method will return fasle too so the observable knows that whatever action it was notifying its listeners of should now be rolled back.
Perhaps this solution doesn't suit everybody, but I;ve had much benefit from this relatively simple piece of code.
From here:
https://developer.mozilla.org/en-US/docs/Web/Guide/Events/Creating_and_triggering_events
// create the event
const event = new Event('build');
// elem is any element
elem.dispatchEvent(event);
// later on.. binding to that event
// we'll bind to the document for the event delegation style.
document.addEventListener('build', function(e){
// e.target matches the elem from above
}, false);
Here is a really simple (TypeScript/Babelish) implementation:
const simpleEvent = <T extends Function>(context = null) => {
let cbs: T[] = [];
return {
addListener: (cb: T) => { cbs.push(cb); },
removeListener: (cb: T) => { let i = cbs.indexOf(cb); cbs.splice(i, Math.max(i, 0)); },
trigger: (<T> (((...args) => cbs.forEach(cb => cb.apply(context, args))) as any))
};
};
You use it like this:
let onMyEvent = simpleEvent();
let listener = (test) => { console.log("triggered", test); };
onMyEvent.addListener(listener);
onMyEvent.trigger("hello");
onMyEvent.removeListener(listener);
Or in classes like this
class Example {
public onMyEvent = simpleEvent(this);
}
If you want plain JavaScript you can transpile it using TypeScript playground.

Categories

Resources