Thursday, March 15, 2012

Assigning onmouseover event handler to element by class in javascript to affect sub elements

Tested in IE7, Opera11, Firefox11; Chrome17.

As with my all-time most popular entry on IE7 and the inline-block style I'm going to be explaining things as I go along. If you just want some code that works scroll down to the bottom of the page.

I'm writing a webpage with a list of names; attached to the names is extra details (phone numbers etc.) I could just put all that information in; but it looks cluttered - information overload. I could put that information on another page and force the user to click to see it; but that's clumsy and irritating.

What would be much neater would be if I could have that information appear when the user hovers over the name. This is where Javascript and the onmouseover event handler makes an appearance.

My basic starting code was as follows:

<div class="listing" id="1">Name 1
<div class="popup" id="1-1">Details of Name 1</div>
</div>

with CSS .popup {display:none;}

The old-fashioned approach would be be to add an onmouseover and onmouseout to my first element find the 'popup' container using its "id" and set the display to something other than none. Like so:

<div class="listing" id="1"
onmouseover="getElementById("1-1").style.display='block';"
onmouseout="getElementById("1-1").style.display='none'";>
Name 1
<div class="popup" id="1-1">Details of Name 1</div>
</div>

That works, but from an aesthetic point of view I've got javascript all over my xhtml ;-) I can move most of it out to an external javascript page using functions:

HTML:
<div class="listing" id="1"
onmouseover="popup('1-1');"
onmouseout="popout('1-1')">
Name 1
<div class="popup" id="1-1">Details of Name 1</div>
</div>

Javascript:
function popup(PopupIdentity){
getElementById(PopupIdentity).style.display="block";
}

function popout(PopupIdentity){
getElementById(PopupIdentity).style.display="none";
}

Again that works. Imagine doing that for 100 entries though each needing their own identities and having to write all those onmouseover's into each one - urgh.

The simplest solution is removing all that and adding a couple of lines and changing the functions slightly:

div.onmouseover = popup;
div.onmouseout = popout;

function popup(PopupIdentity){
this.style.display="block";
}

function popout(PopupIdentity){
this.style.display="none";
}

The "this" keyword just returns whatever called the function. Again great except that's being applied to every div element and targets that element rather than the one with the details in it. So the functions are rewritten as follows:

function popup(){
this.getElementsByTagName("div")[0].style.display="block";
}

function popup(){
this.getElementsByTagName("div")[0].style.display="none";
}

That is look for the first ([0]) div element within the calling element and sets the style accordingly. But it's still applying to all the div elements.

Is there a way to just attach the onmouseover event handler just to those div elements with a class of "listing"? Yes there is.

First of all I need to get a list (or array) of all the div elements in the document

var mdiv = new Array();
mdiv = document.getElementsByTagName("div");

Now I need to look through that list for only those with the "listing" class:

for(var i=0;i<mdiv.length;i++){
if(mdiv[i].className.indexOf("listing")>-1)

Why use indexOf instead of a simple comparison (==)? Because if(mdiv[i].className == "listing") won't return a div element with a class of "listing VIP"; indexOf looks inside it and returns a value greater than -1 if it's found. Now add the event handler for matching elements:

{
mdiv[i].onmouseover= popover;
mdiv[i].onmouseout= popout;
}

Is that it? Almost. It's important to remember the order code is run. It's possible if this is just slapped into a document that the event handler assigner will run prior to the code and the divs being present. To make sure it's run at the right time; wrap the code up in it's own function and use another event handler:

window.onload = SetPopup;

And that's the lot. So the full HTML can have all identities stripped and the entire code looks like this:

HTML:
<div class="listing"> Name 1
<div class="popup">Details of Name 1</div>
</div>

Style:
.popup {display:none;}

Javascript:

window.onload = SetPopup;

function SetPopup(){
var mdiv = new Array();
mdiv = document.getElementsByTagName("div");
for(var i=0;i<mdiv.length;i++){
if(mdiv[i].className.indexOf("listing")>-1)
  {
   mdiv[i].onmouseover= popover;
   mdiv[i].onmouseout= popout;
  }
 }
}

function popover(){
this.getElementsByTagName("div")[0].style.display="block";
}

function popout(){
this.getElementsByTagName("div")[0].style.display="none";
}

3 comments:

Richard Yates said...

This works pretty well, except that on loading ALL of the 'Details of name...' divs appear until I mouseover and then mouseout of them. How can I get the page to start with them all cleared?

FlipC said...

You've missed a step:

.popup {display:none;}

By default all the popup div's don't display. I'll add it to the final step as a reminder for anyone else.

Richard Yates said...

Thanks, Flip.