Target one element inside another

With HTML like this:

<a href="#">Wikipedia [COLOR="#FF0000"]<span>Some extra text here</span>[/COLOR]</a>

I’m wondering how best to check for the presence of that <span> inside the <a>. If the <span> were after the <a>, I know I could use something like if(links[i].nextSibling), but it doesn’t seem to be as simple when the span is a child of the <a>.

I tried things like if(links[i].lastChild.nodeType == 1) etc. but I’m just stabbing in the dark, as you can see.

Why don’t you just use id=“”?

Of course this is easy with CSS, but this example is extracted from a script where the <span> is inserted dynamically, and I was wondering how to check for the existence of that span without having to add an ID to it. It’s really just a question about how JS works, rather than an attempt to solve an actual problem.

You can add a class to your <a> like <a class=“parent”> then do something like $(‘.parent span’) with jquery. I’m assuming you want to use Javascript or jQuery and not CSS based where you posted the q.

Hi Ralph,

To check for the presence of the <span> inside the <a> tag, there are a number of possibilities.

The easiest way would be to get a reference to the links in the document, for example:

var links = document.getElementsByTagName("a");

then call get elementsByTagName on the link in question:

var span = links[0].getElementsByTagName("span");
if (span.length > 0){
  console.log("The might span exists and contains '" + span[0].innerHTML + "'");
}

If you want to go the lastChild route, then there is a nice method defined in DOM3 Element Traversal, namely lastElementChild, which will only relate to element nodes, effectively ignoring all other types (i.e. the annoying whitespace text-nodes).

So, then you can do this:

var lastChild = links[0].lastElementChild
if (lastChild.nodeName == "SPAN"){
  console.log("The might span exists and contains '" + lastChild.innerHTML + "'");
}

Example:

<!DOCTYPE HTML>
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <title>Child elements example</title>
  </head>
  
  <body>
    <a href="#">Wikipedia <span>Some extra text here</span></a>
    
    <script>
      var links = document.getElementsByTagName("a");
      var span = links[0].getElementsByTagName("span");
      if (span.length > 0){
        console.log("The might span exists and contains '" + span[0].innerHTML + "'");
      }
      
      var lastChild = links[0].lastElementChild
      if (lastChild.nodeName == "SPAN"){
        console.log("The might span exists and contains '" + lastChild.innerHTML + "'");
      }
    </script>
  </body>
</html>

Brothercake did quite a nice article on this recently. Maybe you’ll find it useful.

Thanks a lot for the detailed reply, Dave. Very interesting.

This arose from an experiment (for learning purposes). I’m following Kevin Yank’s JS course at Learnable, and one of the exercises is to create a fancy tooltip with JS. In his example, he wraps a span around the <a> and then places another <span> after the <a>. To test myself, I thought I’d see if I could create a variation, where the tooltip is made simply with a span (absolutely positioned) within the <a> itself.

It all worked fine, except for one thing. There are two event listeners on the <a>, one for hover and one for focus, and the script makes sure that these two don’t conflict (e.g. the focus event doesn’t trigger the tooltip if the element is already being hovered). It does this easily by checking if there is already a span after the <a>—using if(link.nextSibling).

In my case, I have to check if there’s a span inside the <a>, and I was wondering if there were a similar shortcut, rather than having to pick through all the <a>s and looking for spans inside them, as you have done. If that’s the only way to do it, that’s fine, but I thought I’d ask. As least lastElementChild provides some assistance, and I hadn’t heard about that one. :slight_smile:

Thanks again. I’m now off to check out Brothercake’s article. I do find JS hard to get into, but I’m starting to enjoy it, all the same. Really enjoying event listeners, for some reason.

Thanks jackleaves. I’m trying to make sure I learn how to do things without jQuery, though it sure does save a lot of work in places (not least with event listeners, if you want IE support). Because I wanted to just check if there was a span there, I wonder if I could use jQuery to make this simple check. Something like

if ($('.parent span')) {}

That looks a bit messy, but I’ll test it out. Feel free to suggest an improvement. :slight_smile:

[edit]After some trial and error, this worked:

if (!($('.parent span').length>0))

Looks pretty messy, though, so perhaps it can be improved?[/edit]

There probably is a short cut.
Could you post a bit of code (i.e. JS, CSS, HTML), so that I can have a look.

Event listeners rock :slight_smile:

if($(".parent span").length){
  console.log("Span exists");
}

Sure. Here is my working example, prior to your and jackleaves’ examples:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Tooltip JS</title>
<style>
a {position: relative;}
a span {position: absolute; left:0; top: 100%; display: block; padding: 10px; background: black; color: white; width: 200px;}
</style>
</head>
<body>
<h1>Tooltip JS</h1>

<p>This is a paragraph with links to sites like <a href="" title="Wikipedia is a free encyclopedia maintained by the public.">Wikipedia</a>, the magnificent <a href="" title="The SitePoint networks includes articles, learning resources and awesome forums">SitePoint</a>, and our favorite site <a href="" title="A List Apart posts regular articles for people who makes websites.">A List Apart</a>.</p>

<p>More text more text more text more text more text more text more text more text more text more text more text more text more text more text more text more text more text more text more text more text more text more text more text more text more text more text more text more text more text more text more text more text more text more text more text more text more text more text more text.</p>



<script type="text/javascript">


var Tooltips =
{
	init: function() // event listener function
	{
		var links = document.getElementsByTagName("a");
		for (i=0, ii=links.length; i<ii; i++) 
		{
			if(links[i].title && links[i].title.length > 0)
			{
				links[i].addEventListener("mouseover",Tooltips.showTipListener, false);
				links[i].addEventListener("focus",Tooltips.showTipListener, false);
				links[i].addEventListener("mouseout",Tooltips.hideTipListener, false);
				links[i].addEventListener("blur",Tooltips.hideTipListener, false);
			}
		}
	},
	
	showTipListener: function(e)
	{
		Tooltips.showTip(this);
		e.preventDefault();
	},
	
	hideTipListener: function(e)
	{
		Tooltips.hideTip(this);
		e.preventDefault();
	},
	
	showTip: function(link) // what happens when the listener detects mouseover or focus event
	{
		if (!link.title == "")
		{
			var span = document.createElement("span");
			var tipText = document.createTextNode(link.title);
			span.appendChild(tipText);
			link.appendChild(span);
			link.title = "";
		}
	},
	
	hideTip: function(link) // what happens when the listener detects mouseout or blur event
	{
		if (link.title == "")
		{
			var tip = link.lastChild; // really not sure if that will work to target the span
			link.title = tip.firstChild.nodeValue;
			link.removeChild(tip);
		}
	}

};

Tooltips.init();

</script>
</body>
</html>

You’ll see that I started with a workaround for the issue in this thread. Instead of checking for the presence of a <span>, I just check to see if the <a>'s title element is empty:

if (!link.title == "")

I’m pleased I found a way to make it work, but I’d prefer to check if the span is there or not.

You can see the need for this by hovering over a link and then tabbing to it. Without the code above, an extra, empty <span> is created when the link has focus. (It’s a small point, but an interesting issue. It could be solved with CSS, of course—by hiding span + span—but as I said, this is just a JS learning exercise for me.)

Ok, then why don’t you change this:

if (!link.title == "")

into this:

if (!link.getElementsByTagName("span").length)

which will check to ensure that the link which is currently being hovered over or which has focus, doesn’t already contain a <span> element.

Does that help any?

Hah, nice one Pullo. That does the trick, and is much more elegant. Thanks a lot—that’s the sort of thing I was after.

I’m interested to explore why it works with .length at the end, but not without it. Can you only test truth or falsity on a property (.length) and not a method (which I assume is what getElementsByTagName() would be called)?

Hi Ralph,

Not quite. Whenever JavaScript expects a boolean value (e.g. for the condition of an if statement), any value can be used. It will be interpreted as either true or false. The following values are interpreted as false:

undefined, null
Boolean: false
Number: -0, +0, NaN
String: ''

All other values are considered true.

getElementsByTagName() returns a NodeList containing all the matched elements. If no elements are found this will be a list with zero members. If you pass this NodeList to the conditional, it will always evaluate as true no matter what it contains.

To get around this, you need to pass the length of the NodeList (obtained via its length property) to the conditional instead. If no matches were encountered, then the length will be zero, which evaluates to false, everything else will evaluate to true.

Does that make sense?

Here’s a couple of links that you might find interesting:

Makes perfect sense. Thanks for the explanation, and nicely explained. :slight_smile:

It has taken me a while to get used to using true and false for conditions, but it is certainly sinking in now. :slight_smile:

If things ever do get to be confusing, you can make things clearer and potentially easier to understand by explicitly showing the condition that you need. For example:


if (link.getElementsByTagName("span").length === 0) {
    ...
}

Hey Paul,

I should probably know this, but why would you need a triple equals and not a double one?

I don’t know how closely JS issues parallel those of PHP, but here’s an interesting thread about the hazards of == in PHP:

http://www.sitepoint.com/forums/showthread.php?1036497-Put-the-on-the-ground-back-away-from-the-keyboard!

Using the triple equals is a best practice, which helps to avoid many problems and issues that can arise with the double equals.

Douglas Crockford has a very good piece about this in his Progamming Style & Your Brain talk, which starts from [URL=“http://www.slideshare.net/jaxconf/section-8-programming-style-and-your-brain-douglas-crockford”]Slide #41 of his talk, or from [URL=“http://www.youtube.com/watch?v=taaEzHI9xyY&t=37m19s”]37:19 of the video.

Thanks for that.
I just watched the talk, which was quite entertaining (not to mention informative).

The fact if if (a == 0) could/should be written if (a === 0) wasn’t new to me, as I use JSLint and of course it complains when you use the first version.
However, I had never really looked into why.

I did a little more research and found the following in Crockford’s JavaScript: The Good Parts, which is the essence of what he is saying in his talk:

JavaScript has two sets of equality operators: === and !==, and their evil twins == and !=. The good ones work the way you would expect. If the two operands are of the same type and have the same value, then === produces true and !== produces false. The evil twins do the right thing when the operands are of the same type, but if they are of different types, they attempt to coerce the values. The rules by which they do that are complicated and unmemorable. These are some of the interesting cases:

‘’ == ‘0’ // false
0 == ‘’ // true
0 == ‘0’ // true

false == ‘false’ // false
false == ‘0’ // true

false == undefined // false
false == null // false
null == undefined // true

’ \ \r
’ == 0 // true
The lack of transitivity is alarming. My advice is to never use the evil twins. Instead, always use === and !==. All of the comparisons just shown produce false with the === operator.

As a side note, it is interesting that different languages have different implementations of the triple equals.
For example in ruby it is used to mean “if a described a set, would b be a member of that set?”

(1..5) === 3           # => true
(1..5) === 6           # => false

Integer === 42          # => true
Integer === 'fourtytwo' # => false

/ell/ === 'Hello'     # => true
/ell/ === 'Foobar'    # => false

Anyway, thanks for pointing me towards that talk and thanks for showing me how to link to a specific point in a YouTube video. That’s a cool trick :slight_smile:

Ref.

http://stackoverflow.com/questions/4467538/what-does-the-operator-do-in-ruby

A note to Ralph: testing focus on your current links may be difficult with an empty href, depending on browser.

== is nice and useful if you honestly really only want to see if two things are equal. It turns out that most of the time, you really want to know if two things are actually the same.

I once saw someone put up an example like this:

"if a person is good enough, and you don’t care if it’s a man or a woman:

if (man == woman) {} //true

but if you must have a man:

if man === woman) {} //false"

It was an interesting analogy. Equality doesn’t mean identity.