Search code examples
javascriptjqueryhtmltraversal

jQuery: Selecting adjacent tags unless separated by text


I'm trying to find and combine adjacent em tags inside of paragraphs but it's proving more difficult than I expected. Consider the following examples:

<p><em>Hello</em><em> World</em></p>

<p><em>Hello</em> <em>World</em></p>

<p><em>Hello</em> to the <em>World</em></p>

In the first and second examples above, I would expect $("em + em") to properly select the second em tag. What I didn't expect was that it would also select cases like the third example where text appears between them.

Is there a way to detect the text between these tags and treat them differently?


Solution

  • What I didn't expect was that it would also select cases like the third example where text appears between them.

    The adjacent sibling combinator, +, doesn't take text into consideration. It will just select the immediately following sibling element.

    You could filter all the adjacent em elements based on whether the previous sibling is an element or whitespace and exclude the element if text appears between the tags.

    In the example below, the em in the third p isn't selected because the previous node is text.

    var $previousSiblings = $('em + em').filter(function () {
       var prev = this.previousSibling;
       return prev.nodeType === 1 ? true : !prev.nodeValue.trim();
    });
    
    $previousSiblings.css('color', '#f00');
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
    <p><em>Hello</em><em> World</em></p>
    <p><em>Hello</em> <em>World</em></p>
    <p><em>Hello</em> to the <em>World</em></p>


    If you want, you can also create a custom jQuery method.

    For instance, here is a custom .nextWithoutText() method that will select the next element supplied if there isn't text in between the elements.

    Usage: $('em').nextWithoutText('em'):

    $.fn.nextWithoutText = function(tag) {
      return $('+' + tag, this).filter(function() {
        var prev = this.previousSibling;
        return prev.nodeType === 1 ? true : !prev.nodeValue.trim();
      });
    };
    
    $('em').nextWithoutText('em').css('color', '#f00');
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
    <p><em>Hello</em><em> World</em></p>
    <p><em>Hello</em> <em>World</em></p>
    <p><em>Hello</em> to the <em>World</em></p>