Reputation: 47995
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
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.
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};
}
}
}
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
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
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
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>
Upvotes: 4
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
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;
Upvotes: 1