How to change event handlers dynamically with JavaScript?
I'm pretty new to anything AJAX and ran into the following problem. I hope someone here is able to help me out. I'm sure it's an easy fix but I just don't see it :(
What I'm trying to do is change the onmouseover and onmouseout events dynamically for the element, here are the relevant bits of code:
The HTML Element (notice there are multiple of these, hence the dynamic part of their id)
<?
if (ownsgame($id, $userid)) {?>
<a><img src="images/collection/got.gif" id="ownedimage<?=$id?>" title="<?=$itemtitle?> (<?=$platformdisplay?>) is in your collection" alt="You own this game" align="center" width="62" height="22" onclick="changeOwned('<?=$id?>')" onmouseover="changeImageSrc('ownedimage<?=$id?>', 'images/collection/del.gif')" onmouseout="changeImageSrc('ownedimage<?=$id?>', 'images/collection/got.gif')"/></a>
<? } else { ?>
<a><img src="images/collection/add.gif" id="ownedimage<?=$id?>" title="Add <?=$itemtitle?> (<?=$platformdisplay?>) to your collection" alt="You do not own this game" align="center" width="62" height="22" onclick="changeOwned('<?=$id?>')" onmouseover="changeImageSrc('ownedimage<?=$id?>', 'images/collection/add.gif')" onmouseout="changeImageSrc('ownedimage<?=$id?>', 'images/collection/add.gif')"/></a>
<?} ?>
The JavaScript function:
function changeImageSrc(id, src) {
document.getElementById(id).src = src;
}
The (relevant) AJAX code:
var http = createRequestObject();
var jsid = "";
function changeOwned(id) {
http.open('get', 'changeowned.php?id=' + id + '&user=<?=$userid?>');
jsid = id;
http.onreadystatechange = processResponse;
http.send(null);
}
function processResponse() {
if((http.readyState == 4) && (http.status == 200)){
var response = http.responseText;
var elementid = 'ownedimage' + jsid;
var element = document.getElementById(elementid);
if (response == "1") {
image = "images/collection/got.gif";
alt = "you own this game";
mouseoverstr = 'images/collection/del.gif';
mouseoutstr = 'images/collection/got.gif';
element.removeEventListener('mouseout', function(){changeImageSrc('ownedimage' + jsid, 'images/collection/add.gif')}, false);
element.removeEventListener('mouseover', function(){changeImageSrc('ownedimage' + jsid, 'images/collection/add.gif')}, false);
} else {
image = "images/collection/add.gif";
alt = "add this game to your collection";
mouseoverstr = 'images/collection/add.gif';
mouseoutstr = 'images/collection/add.gif';
element.removeEventListener('mouseout', function(){changeImageSrc('ownedimage' + jsid, 'images/collection/got.gif')}, false);
element.removeEventListener('mouseover', function(){changeImageSrc('ownedimage' + jsid, 'images/collection/del.gif')}, false);
}
element.src = image;
element.alt = alt;
element.addEventListener('mouseover', function(){changeImageSrc('ownedimage' + jsid, mouseoverstr)}, false);
element.addEventListener('mouseout', function(){changeImageSrc('ownedimage' + jsid, mouseoutstr)}, false);
}
}
It seems to work fine at first but produces some weird behaviour. The referenced PHP works fine and produces the correct response. The srcand alt of the image get changed as well. In fact, it first looks 开发者_开发问答like the new mouseover/out work too. But when you click on more than one image on the site (which have different IDs) they suddenly start influencing each other. When you mouseover over one, the other changes its image aswell. Why is this happening? I really am clueless as the jsid part is fine and I don't understand why the mouseover suddenly changes two images. It looks as if multiple eventhandlers for different IDs are assigned to the same image element. No idea why that is though. I hope some of you with more AJAX knowledge can help me here, quite frustrated :(
A couple of things there: :-)
1) addEventListener
and removeEventListener
work with the newer DOM2 handler chain, which is completely separate from the older "DOM0" style (the onXYZ
attributes). So you can't remove a handler via removeEventListener
that you've originally assigned via an onXYZ
attriubte. To do that, assign "" to the attribute's reflected property.
element.onmouseover = "";
2) When you use removeEventListener
, you must pass in the same function reference that you used originally. So this will never remove anything:
element.removeEventListener('mouseout', function(){changeImageSrc('ownedimage' + jsid, 'images/collection/got.gif')}, false);
...because it creates a brand new function to pass into removeEventListener
, and since that function isn't on the event handler chain, the call is ignored.
I'd probably approach the problem by having a single event handler, but then changing the data it works with. You can do that by storing the alternate image URLs on the actual element itself, using a data-xyz
attribute (say, data-oversrc
and data-stdsrc
or some such). Then your two functions (one for mouseover, one for mouseout) that change the URL based on the image:
function handleMouseOver() {
this.src = this.getAttribute('data-oversrc');
}
function handleMouseOut() {
this.src = this.getAttribute('data-stdsrc');
}
I'd drop the onXYZ
handlers from the elements entirely, and replace them with a one-time addEventListener
(attachEvent
on IE) that assigns both of those.
data-xyz
attributes, like all custom attributes, are invalid in HTML4 and earlier. As of HTML5, they validate, and since no major browser has a problem with invalid attributes anyway, you can start using them right away.
Off-topic #1: These days, I usually recommend using a JavaScript library to smooth over browser differences and implement useful functionality for you. For instance, your code using addEventListener
and removeEventListener
will fail on IE prior to IE9 because it simply doesn't have those methods. If you use a library like jQuery, Prototype, YUI, Closure, or any of several others, they'll deal with that stuff for you so you can focus on your own value-add.
Off-topic #2: The mouseover
event happens repeatedly when the mouse is passing over the element, and both it and mouseout
bubbles up the DOM. This means that for hover effects like yours, if you can't use CSS (and you can't on IE6), you're better off using the mouseenter
and mouseleave
events, most of the time. But those are IE-specific events not supported by most other browsers. Enter any decent JavaScript library. :-) They'll emulate mouseenter
and mouseleave
on browsers that don't support them directly. Of the list above, I know for certain that jQuery and Prototype do that; I'd be surprised if the others don't have similar functionality.
The issue is the removeEventListener
, it's not working like you think it is. What's happening is when you do this:
element.removeEventListener('mouseout',function(){/* handlers */},false);
That function() { }
is a new anonymous function you just created, not the existing one on the element as a listener...so when it goes to remove that listener, it just isn't there, because that was a different anonymous function (even if it had the exact same code, it's a different reference) that you assigned via addEventListener
.
There are a few work-arounds for this, given that you're making AJAX requests I'm assuming you're not making hundreds/thousands here, so you could just store the handlers you assign for removing them later, like this:
var http = createRequestObject();
var jsid = "";
var handlers = {};
function changeOwned(id) {
http.open('get', 'changeowned.php?id=' + id + '&user=<?=$userid?>');
jsid = id;
http.onreadystatechange = processResponse;
http.send(null);
}
function processResponse() {
if((http.readyState == 4) && (http.status == 200)){
var response = http.responseText,
elementid = 'ownedimage' + jsid,
element = document.getElementById(elementid),
image, alt, mouseoverstr, mouseoutstr;
if (response == "1") {
element.src = "images/collection/got.gif";
element.alt = "you own this game";
mouseoverstr = 'images/collection/del.gif';
mouseoutstr = 'images/collection/got.gif';
} else {
element.src = "images/collection/add.gif";
element.alt = "add this game to your collection";
mouseoverstr = 'images/collection/add.gif';
mouseoutstr = 'images/collection/add.gif';
}
//Create a holder if this is the first time for this elementid
if(!handlers[elementid]) handlers[elementid] = {};
//Remove old handlers
element.removeEventListener('mouseover', handers[elementid].mouseover, false);
element.removeEventListener('mouseout', handers[elementid].mouseout, false);
//Store new handlers
handlers[elementid].mouseover = function(){changeImageSrc('ownedimage' + jsid, mouseoverstr)};
handlers[elementid].mouseout = function(){changeImageSrc('ownedimage' + jsid, mouseoutstr)};
//Add hew handlers as listeners
element.addEventListener('mouseover', handers[elementid].mouseover, false);
element.addEventListener('mouseout', handers[elementid].mouseout, false);
}
}
精彩评论