markzzz
markzzz

Reputation: 47995

How can I check if an element (h2) is really the first element inside a div?

I want to "manage" the first h2 element inside a div, only if it's really the "first element"

<div id="test">
    <h2>Try 1</h2>
    Test Test Test
    <h2>Try 2</h2>
</div>  

here only h2 with text "Try 1" must be managed

<div id="test">
    Test Test Test
    <h2>Try 1</h2>
    <h2>Try 2</h2>
</div>  

Here no (there is text before).

How can I do it with jQuery?

Upvotes: 4

Views: 2573

Answers (6)

Robin van Baalen
Robin van Baalen

Reputation: 3651

The challenge we're facing here is that javascript recognizes whitespace as a text node as well. Therefore, from a javascript point of view, this HTML:

<div id="test">
    <h2>Try 1</h2>    
    Test Test Test    
    <h2>Try 2</h2>
</div>

Is different from this HTML:

<div id="test"><h2>Try 1</h2>    
    Test Test Test    
    <h2>Try 2</h2>
</div>

In the first case, the first node inside the div is a textNode (nodeType == 3) In the second HTML example, the first node inside the div is a h2 node.

I've come up with a solution for this, a handy function that loops through all elements combining jQuery and native javascript.

Solution

var objNodes = $(".wrapper").contents().get();

  function loopNodes(objNodes, i) {
    i = (typeof i === "undefined") ? 0 : i;

    if (objNodes[i].nodeType !== 3) {
      return {"isHeader":true, "first":$(objNodes[i])};

    } else {
      var strText = objNodes[i].innerText || objNodes[i].textContent;
      if ($.trim(strText).length === 0) {
        return loopNodes(objNodes, i+1);

      } else {
        return {"isHeader":false, "first":null};

      }
    }
  }

Usage

var objResults = loopNodes(objNodes);
if (objResults.isHeader) {
    console.log("Coolness");
    objResults.first.text("AWESOME FIRST HEADER!"); 
} else {
    console.log("Less Coolness");
}

In action: http://jsbin.com/welcome/61883/edit

Edit: Added the cross-browser way of getting innerText/textContent. See Quirksmode for full reference on the matter.

Upvotes: 2

apsillers
apsillers

Reputation: 115980

You can use .contents to conditionally ignore leading nodes that are only whitespace text. Then see if the first node is an <h2> (Fiddle):

function isFirstChildH2(selector) {
    // get th efirst node, which may be a text node
    var firstNode = $(selector).contents().first();

    // if the first node is all whitespace text, ignore it and go to the next
    if(firstNode[0].nodeType == 3 && firstNode.text().match(/\S/g) == null) {
        firstNode = firstNode.next();
    }

    if(firstNode.is("h2")) {
        // it's an h2; do your magic!
        alert("h2 is the first thing on " + selector)
     } else {
        // first node is either non-whitespace text or an non-h2 element
        // don't do your magic
        alert("h2 is NOT the first thing on " + selector)
     }
}

isFirstElementH2("#test");

Upvotes: 2

Stuart Wakefield
Stuart Wakefield

Reputation: 6414

This is how you would filter to get only the header that is the first node, ignoring all blank text nodes:-

$("#test").children("h2").first().filter(function() {

    var childNodes = this.parentNode.childNodes;
    var i = 0;
    var textNode = 3;

    // No children
    if(!childNodes.length) {
        return false;
    }

    // Skip blank text node
    if(childNodes[i].nodeType === textNode && childNodes[i].textContent.trim().length === 0) {
        i ++;
    }

    // Check we have a match
    return childNodes[i] === this;

});

Here is it in action http://jsfiddle.net/nmeXw/

Upvotes: 2

Christoph
Christoph

Reputation: 51221

No jquery needed for that, just take:

document.getElementById('test').firstChild.nodeName // gives the name of the node

This will give you the name of the very first node, even if it's not a tag but just a plain text-node!

optionally you could of course use document.querySelector() if you want to be more flexible with your selectors and know that most of the clients browser support it.

To be clear: if you add a newline, this will also be considered as a text-node, so the heading needs to start on the same line or you will get #text as result for both examples!

This will fail:

<div id="test">
    <h2>Try 1</h2>
    Test Test Test
    <h2>Try 2</h2>
</div>

and this will work:

<div id="test"><h2>Try 1</h2>
    Test Test Test
    <h2>Try 2</h2>
</div>

a little demo for you

Upvotes: 4

markzzz
markzzz

Reputation: 47995

I think I found myself a solution, a bit funny :

if($.trim($('#test').html()).substr(0,4).toLowerCase() == "<h2>")
{
    $('#test h2:first').css('background-color', 'red');
}

What do you think about? :)

Upvotes: 2

Bergi
Bergi

Reputation: 665040

OK, let's mix some jQuery with plain DOM code (as jQuery is not capable of handling text nodes):

var $el = $("#test > h2:first-child");
if (!$el.length) return false;
var el = $el.get(0),
    reg = /\S/; // no whitespace
for (var prev = el; prev = prev.previousSibling; )
    if (prev.nodeType == 3 && reg.test(prev.data))
        return false;
return el;

Demo at jsfiddle.net

Upvotes: 1

Related Questions