0

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 :(

7
  • Please study and apply unobtrusive javascript before you drive the web back to 1998. Thank you. Commented Jan 2, 2011 at 10:37
  • @aaronasterling - ...what do you think addEventListener is? Commented Jan 2, 2011 at 10:40
  • @Nick Craver onclick="changeOwned('<?=$id?>')" onmouseover="changeImageSrc('ownedimage<?=$id?>', 'images/collection/del.gif')" onmouseout="changeImageSrc('ownedimage<?=$id?>', 'images/collection/got.gif') Commented Jan 2, 2011 at 10:48
  • Look, I'm pretty new to this (not that you can't tell) and usually coding real Java and not for the web. I still want to do this. I'd like to move from inline to programmatically on load but I struggled doing this with the dynamic IDs. No use trying to learn and fix two things at the same time so I went with inline and tried to get that running first. I can always look into that problem once this one is fixed. Feel free to let me know how to handle those IDs on load. Commented Jan 2, 2011 at 10:56
  • @aaronasterling - Yes, I was commenting on your snide remark implying that the user isn't aware of what unobtrusive JavaScript is...when that's exactly what's being applied in the question, though not 100% I agree, it is used for the most part. Commented Jan 2, 2011 at 10:56

2 Answers 2

2

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.

Sign up to request clarification or add additional context in comments.

2 Comments

thanks a lot, this really helped, both in understanding the concepts a bit better aswell as solving my problem. I dropped the inline events and did functions like the ones you suggested with some minor adjustments. Works like a charm now :)
@janb: Good deal! Glad that helped.
2

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

1 Comment

thanks, your reply was very helpful too even if I didn't go with the handlers array solution ;)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.