views:

1099

answers:

4

I'm trying to write javascript to find page elements relative to a given element by using parentNode, firstChild, nextSibling, childNodes[], and so on. Firefox messes this up by inserting text nodes between each html element. I've read that I can defeat this by removing all whitespace between elements but I've tried that and it doesn't doesn't work. Is there a way to write code that works on all modern browsers?

For example:

<div id="parent"><p id="child">Hello world</p></div>

In IE parent.firstChild is child but in Firefix it's a phantom Text element.

A: 

you could use tagName to check the name of the tag. If undefined then this is your 'phantom' text node.

e.g.

function getFirstTag(node) {
  return ((node.firstChild.tagName) ? node.firstChild : node.firstChild.nextSibling);
}

Ady
+1  A: 

I have a workaround. You can insert the two methods below:

Element.prototype.fChild = function(){
    var firstChild = this.firstChild;
    while(firstChild != null && firstChild.nodeType === 3){
     firstChild = firstChild.nextSibling;
    }
    return firstChild;
 }
 Element.prototype.nSibling = function(){
    var nextSibling = this.nextSibling;
    while(nextSibling != null && nextSibling.nodeType === 3){
     nextSibling = nextSibling.nextSibling;
    }
    return nextSibling;
 }

and you can now use:

document.getElementById("parent").fChild();
document.getElementById("parent").nSibling();

instead of:

 document.getElementById("parent").firstChild;
 document.getElementById("parent").nextSibling;
Jader Dias
Unfortunately, this will fall apart on IE, ruining any hope of a cross-plaform solution. Of course, the methods could be used as stand-alone functions.
Shog9
With some more work we can make those functions to work in any browser.
Jader Dias
The nodeType should be 1, not 3. 1 is for an element, 2 or undefined is for an attribute and 3 is for a text node.
David Brockman
@David Brockman - You´re right about the numbers meanings, but the code is right because it avoids unwanted nodeType (3).
Jader Dias
A: 

Check the DOM level 2 core reference to see the various possible types of nodes, so you can filter out the undesired ones with a simple snippet of JavaScript. One solution is to monkey patch the object (see the answer of Vernicht) or if you do not like monkey patching, then you can add these methods to your own library, or an even better solution might be to use a fancy library like jQuery or prototype.

bandi
+1  A: 

You have to check that the nodeType == 1.

if (el.nodeType === 1) {
    return el;
}

I wrote a small DOM traversing class for ya (mostly copied from MooTools). Download here: http://gist.github.com/41440

DOM = function () {

    function get(id) {
        if (id && typeof id === 'string') {
            id = document.getElementById(id);
        }
        return id || null;
    }

    function walk(element, tag, walk, start, all) {
        var el = get(element)[start || walk], elements = all ? [] : null;
        while (el) {
            if (el.nodeType === 1 && (!tag || el.tagName.toLowerCase() === tag)) {
                if (!all) {
                    return el;
                }
                elements.push(el);
            }
            el = el[walk];
        }
        return elements;
    }

    return {

        // Get the element by its id
        get: get,

        walk: walk,

        // Returns the previousSibling of the Element (excluding text nodes).
        getPrevious: function (el, tag) {
            return walk(el, tag, 'previousSibling');
        },

        // Like getPrevious, but returns a collection of all the matched previousSiblings.
        getAllPrevious: function (el, tag) {
            return walk(el, tag, 'previousSibling', null, true);
        },

        // As getPrevious, but tries to find the nextSibling (excluding text nodes).
        getNext: function (el, tag) {
            return walk(el, tag, 'nextSibling');
        },

        // Like getNext, but returns a collection of all the matched nextSiblings.
        getAllNext: function (el, tag) {
            return walk(el, tag, 'nextSibling', null, true);
        },

        // Works as getPrevious, but tries to find the firstChild (excluding text nodes).
        getFirst: function (el, tag) {
            return walk(el, tag, 'nextSibling', 'firstChild');
        },

        // Works as getPrevious, but tries to find the lastChild.
        getLast: function (el, tag) {
            return walk(el, tag, 'previousSibling', 'lastChild');
        },

        // Works as getPrevious, but tries to find the parentNode.
        getParent: function (el, tag) {
            return walk(el, tag, 'parentNode');
        },

        // Like getParent, but returns a collection of all the matched parentNodes up the tree.
        getParents: function (el, tag) {
            return walk(el, tag, 'parentNode', null, true);
        },

        // Returns all the Element's children (excluding text nodes).
        getChildren: function (el, tag) {
            return walk(el, tag, 'nextSibling', 'firstChild', true);
        },

        // Removes the Element from the DOM.
        dispose: function (el) {
            el = get(el);
            return (el.parentNode) ? el.parentNode.removeChild(el) : el;
        }

    };
}();



// Now you can do:
DOM.getFirst("parent") // first child
// or
DOM.getFirst("parent", "p") // first p tag child
// or
var el = DOM.get("parent") // get element by id
DOM.getFirst(el) // first child
David Brockman