Search code examples
javascriptjqueryhtmldomsiblings

How to detect strictly adjacent siblings


Consider the following HTML where IDs #p1, #p2 and #p3 are siblings (see fiddle):

<div id="container">
    <span id="p1">Paragraph 1</span>
    <span id="p2">Paragraph 2</span>
    This is just loose text.
    <p id="p3">Paragraph 3</p>
</div>

These are my definitions of strict and loose siblings:

  • I consider ID #p1 and #p2 strict siblings (because there is no text which is not encapsulated in any kind of html element between them.

  • And ID #p2 and #p3 to be loose siblings.

Given any element with a next sibling is it possible to know if the next sibling is strict or loose?

Edit: The siblings may be of different type and I updated the fiddle to reflect that.


Solution

  • Answering your question of

    Given any element with a next sibling is it possible to know if the next sibling is strict or loose?

    rather than following your lead from the fiddle, yes it's possible.

    function isMyNextSiblingStrict(element)
    {
        return ("nodeType" in element && element.nodeType === 1 && element.nextSibling !== null && element.nextSibling.nodeType === 1);
    }
    

    This will return true when an element's next sibling is another element. However, be careful with your example as it is incorrect by your own definition, the two spans have a text node between them made up of white space, so #1 and #2 are not "strict siblings".

    <div id="container">
        <span id="p1">Paragraph 1</span><!-- WHITESPACE
     --><span id="p2">Paragraph 2</span>
        This is just loose text.
        <p id="p3">Paragraph 3</p>
    </div>
    

    These would be "strict siblings".

    <div id="container">
        <span id="p1">Paragraph 1</span><span id="p2">Paragraph 2</span>
        This is just loose text.
        <p id="p3">Paragraph 3</p>
    </div>
    

    edit - just for fun I've created a jQuery extension that should do what you want - you can see it in your updated jsFiddle, I've not tested it much but it seems to work as you describe

    $.fn.extend({
        siblingsStrict: function( until, selector ) {
            var ret = jQuery.map( this, function( elem ) {
                var n = ( elem.parentNode || {} ).firstChild,
                    r = [],
                    contig = false;
    
            for ( ; n; n = n.nextSibling ) {
                    if ( n === elem ) {
                        contig = true;
                    }
                    else if ( n.nodeType === 1 && n !== elem ) {
                        r.push( n );
                    }
                    else if ( n.nodeType === 3 && n.textContent.replace(/\s/g, "") === "" ) {
                        continue;
                    }
                    else if ( n.nodeType === 3 && contig === true ) {
                        break;
                    }
                    else if ( n.nodeType === 3 && contig === false) {
                        r = [];
                    }
            }
    
            return r;
        }, until );
    
            if ( name.slice( -5 ) !== "Until" ) {
                selector = until;
            }
    
            if ( selector && typeof selector === "string" ) {
                ret = jQuery.filter( selector, ret );
            }
    
            if ( this.length > 1 ) {
                // Remove duplicates
                if ( !guaranteedUnique[ name ] ) {
                    ret = jQuery.unique( ret );
                }
    
                // Reverse order for parents* and prev-derivatives
                if ( rparentsprev.test( name ) ) {
                    ret = ret.reverse();
                }
            }
    
            return this.pushStack( ret );
        }
    });