Finding HTML elements using Javascript nextSibling and previousSibling

Jun 13 2006

Updated 13/06: See below for update on IE compatibility.

Quite often when writing Javascript functions you will want to get a reference to the element immediately before or after the one you’re working with; for example, when validating a form you may want to toggle the display of a <span> containing an error message that appears immediately before the input being validated.

The built-in Javascript properties nextSibling and previousSibling will return a reference to the element after/before your chosen element respectively, but they have a major drawback when it comes to whitespace in your code – any spaces or line-breaks in the source code will be interpreted as DOM nodes, and your nextSibling/previousSibling call will return a blank text node instead of the object you were expecting.

To get around this, I’ve extended the Object type and added two new methods, named nextObject() and previousObject(). They perform exactly the same function as nextSibling and previousSibling, but will only return the next/previous HTML element, ignoring text nodes altogether:

  1. Object.prototype.nextObject = function() {
  2. var n = this;
  3. do n = n.nextSibling;
  4. while (n && n.nodeType != 1);
  5. return n;
  6. }
  7. Object.prototype.previousObject = function() {
  8. var p = this;
  9. do p = p.previousSibling;
  10. while (p && p.nodeType != 1);
  11. return p;
  12. }
  13. Download this code: /code/sibling.txt

Now you can just grab a reference to the next or previous element by using the new methods; they either return a reference to the element, or ‘false’ if there is no element present:

var elem = $('my_element').nextObject();

It saves having to loop through node types in your actual functions – hopefully it may be of some use to you.

Unfortunately as Internet Explorer HTML objects don’t inherit from any of the DOM objects, this doesn’t work without some tweaking. To fix it for IE, we’re going to add a .htc behaviour file, and attach it to our HTML elements using one line of CSS:

  1. <PUBLIC:COMPONENT>
  2. <PUBLIC:METHOD NAME="nextObject" INTERNALNAME="_nextObject" />
  3. <PUBLIC:METHOD NAME="previousObject" INTERNALNAME="_previousObject" />
  4. <script type="text/javascript">
  5. var el = new Object;
  6. _nextObject = el.nextObject;
  7. _previousObject = el.previousObject;
  8. </script>
  9. </PUBLIC:COMPONENT>
  10. Download this code: /code/htc.txt

Using Conditional Comments, we can deliver the CSS to IE only:

  1. <!--[if IE]>
  2. <style type="text/css">
  3. * { behavior: url(attributes.htc); }
  4. </style>
  5. <![endif]-->
  6.  
  7. Download this code: /code/cc.txt

Many thanks to Jonathan Snook for his help with fixing the IE implementation.

Filed under: Javascript.

Technorati tags:

Digg this article

Bookmark this article with del.icio.us

Previously: My slightly embarrassing musical past

Next: What the web needs now...


Comments

Matthew Pennell
2872 days ago
Mr Snook has helpfully pointed out that this particular technique doesn’t actually work in IE due to that browser’s implementation of inheritance for DOM objects.

I’m trying a few things to see if I can get it to work, but until then consider it a Firefox-only technique (and even then you might want to extend the HTMLElement object rather than Object).

Update: I've updated the code to provide a fix for IE now - leave a comment if you find any other problems.
#1
Simon Willison
2867 days ago
Extending Object.prototype is bad because it breaks for..in iteration over objects. If you extract your code out in to a couple of regular functions (getNextSibling(el) and getPreviousSibling(el) for example) you’ll fix that and get rid of the need for the nasty IE hack at the same time.
#2
Matthew Pennell
2867 days ago
Hi Simon – yeah, there is that; I should have mentioned it in the article really. I wanted to try extending prototypes for a change, though. ;)
#3
Mark Dalgleish
2782 days ago

I wrote this bit of code for a project here at work which gave me identical results across IE, Opera and Firefox.

elem = XXXX.nextSibling;
while(elem.innerHTML == null)
{
elem = elem.nextSibling;
}

I’m far from a Javascript expert, so let me know if there’s any problems with this.

#4
Matthew Pennell
2781 days ago

That’s a good idea, Mark – although obviously it would break if the innerHTML was legitimately empty.

#5
Mark Dalgleish
2765 days ago

Matthew: I tried my code on another small project and found that if the innerHTML is empty, it returns a blank string rather than a null. The code I posted above still manages to detect a button in IE7, Opera 9 and Firefox 1.5 even though the input tag is self-closing and thus has no innerHTML. I guess this means it works in all cases.

#6
Matthew Pennell
2764 days ago

Cool – good work. :)

#7