I am facing a cross browser issue while trying to pass an event object from an onclick event.
Currently, I am doing the following
for (var i = 0; i < list.length; i++)
{
var link= list[i];
link.onclick = function(el) { return function () {LinkManager.HandleOnClick(window.event, el); }}(link);
}
Firefox doesn't respect window.event. However, how else can I pass it?
I resorted to a "not" clean solution:
for (var i = 0; i < list.length; i++)
{
var link= list[i];
link.onclick = SomeClickHandler;
}
function SomeClickHandler(e)
{
e = e || window.event;
if (typeof (e) !== 'undefined')
{
var element = e.target || e.srcElement;
LinkManager.HandleOnClick(e, element);
}
}
Can anyone recommend a better way of solving it? I really didn't like the workaround that I did.
If I understand correctly, you want your handler to have access to the el variable and the event object when it's called. That's possible and not that hard, but you might want to read up on closures:
link.onclick = (function(el)
{
return function (e)//this function will receive the event object
{
e = e || window.event;//for ie
LinkManager.HandleOnClick(e, el);
}
})(link);
but, to be honest, this is overkill, try this (pun intended):
link.onclick = function(e)
{//this refers to `link`, the clicked element
e = e || window.event;//for ie
LinkManager.HandleOnClick(e, this);
};
or even less code:
link.onclick = function(e)
{//this refers to `link`, the clicked element
e = e || window.event;//for ie
LinkManager.HandleOnClick.apply(this,[e]);
//LinkManager.HandleOnClick will receive 1 argument, the event object, and 'this' will point to 'link'
};
and even less:
link.onclick = LinkManager.HandleOnClick;//this === link, 1 argument: the event object
That said, you're iterating a list, and attaching an event listener to each individual child element. I'd strongly advise you to use delegaion:
if (list.addEventListener)
{
list.addEventListener('click',LinkManager.HandleOnClick,false);
}
else if (list.attachEvent)
{
list.attachEvent('onclick',LinkManager.HandleOnClick);
}
else
{
//use the loop you have now as a last resort
}
//LinkManager.HandleOnClick should start like so:
LinkManager.HandleOnClick = function(e)
{
e = e || window.event;//<- you have the event object here
var target = e.target || e.srcElement;//=== list item: your link variables, your element
console.log(this);//<-should reference the list element (<ul> || <ol>)
};
Anyway, that's how I'd tackle this
var handler = function (evt) {
// do something here
}
if(window.addEventListener) {
link.addEventListener("click", handler);
} else if(document.attachEvent) {
link.attachEvent("onclick", handler)
}
Related
I have this function check(e) that I'd like to be able to pass parameters from test() when I add it to the eventListener. Is this possible? Like say to get the mainlink variable to pass through the parameters. Is this even good to do?
I put the javascript below, I also have it on jsbin: http://jsbin.com/ujahe3/9/edit
function test() {
if (!document.getElementById('myid')) {
var mainlink = document.getElementById('mainlink');
var newElem = document.createElement('span');
mainlink.appendChild(newElem);
var linkElemAttrib = document.createAttribute('id');
linkElemAttrib.value = "myid";
newElem.setAttributeNode(linkElemAttrib);
var linkElem = document.createElement('a');
newElem.appendChild(linkElem);
var linkElemAttrib = document.createAttribute('href');
linkElemAttrib.value = "jsbin.com";
linkElem.setAttributeNode(linkElemAttrib);
var linkElemText = document.createTextNode('new click me');
linkElem.appendChild(linkElemText);
if (document.addEventListener) {
document.addEventListener('click', check/*(WOULD LIKE TO PASS PARAMETERS HERE)*/, false);
};
};
};
function check(e) {
if (document.getElementById('myid')) {
if (document.getElementById('myid').parentNode === document.getElementById('mainlink')) {
var target = (e && e.target) || (event && event.srcElement);
var obj = document.getElementById('mainlink');
if (target!= obj) {
obj.removeChild(obj.lastChild);
};
};
};
};
Wrap your event listener into a function:
document.addEventListener(
'click',
function(e,[params]){
check(e,[params]);
}
);
One solution would be to move the "check" function up inside your test() function. As an inner function, it would automatically be able to refer to variables in its outer scope. Like this:
function test() {
if (!document.getElementById('myid')) {
var mainlink = document.getElementById('mainlink');
var newElem = document.createElement('span');
mainlink.appendChild(newElem);
var linkElemAttrib = document.createAttribute('id');
linkElemAttrib.value = "myid";
newElem.setAttributeNode(linkElemAttrib);
var linkElem = document.createElement('a');
newElem.appendChild(linkElem);
var linkElemAttrib = document.createAttribute('href');
linkElemAttrib.value = "jsbin.com";
linkElem.setAttributeNode(linkElemAttrib);
var linkElemText = document.createTextNode('new click me');
linkElem.appendChild(linkElemText);
if (document.addEventListener) {
document.addEventListener('click', function(e) {
if (document.getElementById('myid')) {
if (document.getElementById('myid').parentNode === mainlink) {
var target = (e && e.target) || (event && event.srcElement);
if (target!= mainlink) {
mainlink.removeChild(mainlink.lastChild);
};
};
};
});
};
What I typically do in this situation is save arguments to the object (whenever it's convenient), and then retrieve them in the function, like this:
// Listener function receives e (the event object) by default.
function eventReceiver(e) {
var obj;
// Find object which triggered the event
e.srcElement ? obj = e.srcElement : obj = e.target;
// obj.someProperty has been set elsewhere, replacing a function parameter
alert(obj.someProperty);
}
This is cross browser, and allows you to pass objects and values through the properties of the event target.
I initially started with the this keyword, but that behaves differently cross-browser. In FF, it's the object that the event was triggered on. In IE, it's the event itself. Thus, the srcElement / target solution was born. I'm interested to see the other solutions though - have a +1.
I would like to trigger an events without duplicating code. So I decided to create a constructor function (class) and then a new object with a variable that connects to an event handler. Im just testing it but I cant get the if statement to trigger the alert() using this.link it works like this: if(el) but not like this: if(el === this.link)
var faqOne = document.getElementById("faqOne");
var hiddenOne = document.querySelector("p.faqOneHidden");
faqOne.addEventListener("click", function (e) {
showFaqOne.showClickedFaq(e);
}, false);
function DisplayQFaqs(link, faq) {
this.link = link;
this.faq = faq;
}
DisplayQFaqs.prototype.showClickedFaq = function (e) {
var el = e.target;
if (el === this.link) {
alert('hi');
}
};
var showFaqOne = new DisplayQFaqs(faqOne, hiddenOne);
I want to use a closure as an eventlistener (im working on a routing library), so i created a class that does exactly that
'use strict';
var RouteManager = RouteManager || {};
RouteManager.OnClickDelegate = function(UrlHandler)
{
var handler = UrlHandler;
return function delegate(e)
{
e = e || window.event;
var element = e.target || e.srcElement;
if (element.tagName == 'A') {
console.log("link click");
window.history.pushState({},"",e.href);
//this.handler.handle(element.href);
return false;
}
};
};
assignment:
document.onclick = RouteManager.OnClickDelegate(new UrlHandler());
However, although assigning the closure as an event listener works ("link click" is logged correctly) the state isnt pushed
to make matters even stranger, this works correctly:
document.onclick = (function(UrlHandler){
var uh = UrlHandler;
return function delegate(e)
{
e = e || window.event;
var element = e.target || e.srcElement;
if (element.tagName == 'A') {
console.log("link click");
window.history.pushState({},"",e.href);
//this.handler.handle(element.href);
return false;
}
};
})(new UrlHandler());
Any ideas?
Also, please dont suggest a working routing library, its not what im asking for
the state isnt pushed
Probably because e.href is undefined. You want element.href.
Notice that in your closure you can use the parameter variable directly, you don't need that local variable declaration. Just use this:
RouteManager.OnClickDelegate = function(handler) {
return function delegate(e) {
e = e || window.event;
var element = e.target || e.srcElement;
if (element.tagName == 'A') {
console.log("link click");
window.history.pushState({}, "", element.href);
handler.handle(element.href);
return false;
}
};
};
I have to call another function before the original onclick event fires, I've tried a lot of different paths before I've come to following solution:
function bindEnableFieldToAllLinks() {
var links = document.getElementsByTagName('a');
for (var i = 0; i < links.length; i++) {
var link = links[i];
var onclick = link.getAttribute('onclick');
link.onclick = new Function("if(linkClickHandler()){"+onclick+"}");
console.log(link.getAttribute('onclick'));
}
}
This does the trick in Firefox and Chrome but IE8 is acting strange, it seems that the function that's in the onclick variable isn't executed.
I've already added console.log messages that get fired after the if statement is true and if I print out the onclick attribute I get following:
LOG: function anonymous() {
if(linkClickHandler()){function onclick()
{
if(typeof jsfcljs == 'function'){jsfcljs(document.getElementById('hoedanigheidForm'), {'hoedanigheidForm:j_id_jsp_443872799_27':'hoedanigheidForm:j_id_jsp_443872799_27'},'');}return false
}}
}
So it seems that the function is on the onclick of the link and the old onclick function is on it as well.
Can anyone help me out with this please?
Say you have an onclick attribute on a HTMLElement..
<span id="foo" onclick="bar"></span>
Now,
var node = document.getElementById('foo');
node.getAttribute('onclick'); // String "bar"
node.onclick; // function onclick(event) {bar}
The latter looks more useful to what you're trying to achieve as using it still has it's original scope and you don't have to re-evaluate code with Function.
function bindEnableFieldToAllLinks() {
var links = document.getElementsByTagName('a'),
i;
for (i = 0; i < links.length; i++) function (link, click) { // scope these
link.onclick = function () { // this function literal has access to
if (linkClickHandler()) // variables in scope so you can re-
return click.apply(this, arguments); // invoke in context
};
}(links[i], links[i].onclick); // pass link and function to scope
}
Further, setting a named function inside an onclick attribute (i.e. as a String) doesn't achieve anything; the function doesn't invoke or even enter the global namespace because it gets wrapped.
Setting an anonymous one is worse and will throw a SyntaxError when onclick tries to execute.
This will do what you want, executing what is inside linkClickHandler first, and then executing the onclick event. I put in a basic cross browser event subscribing function for your reuse.
bindEnableFieldToAllLinks();
function bindEnableFieldToAllLinks() {
var links = document.getElementsByTagName('a');
for (var i = 0; i < links.length; i++) {
var link = links[i];
var onclick = link.getAttribute('onclick');
onEvent(link, 'click', function() {
linkClickHandler(onclick);
});
link.onclick = undefined;
}
}
function onEvent(obj, name, func) {
if (obj.attachEvent) obj.attachEvent('on' + name, func);
else if (obj.addEventListener) obj.addEventListener(name, func);
}
function linkClickHandler(funcText) {
alert('before');
var f = Function(funcText);
f();
return true;
}
jsFiddle
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();