How to detect strictly adjacent siblings
Asked Answered
M

5

6

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.

Meliorism answered 13/2, 2014 at 17:37 Comment(2)
You want to do something with css2 selectors ~ and + quirksmode.org/css/selectors/#t11Tacheometer
I'd say a better terminology would be "adjacent elements" or "contiguous elements" as they are all strictly node siblings, one is just a text node rather than an element node.Gadabout
G
2

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 );
    }
});
Gadabout answered 13/2, 2014 at 18:44 Comment(5)
However, be careful with your example as it is incorrect by your own definition - While commuting to work this morning I was thinking "I scr*wed up in my question..." but gladly someone was awake enough to spot it... :-) I will review and test all answers and pick one later today.Meliorism
I was testing your first solution and got a console error: Firefox says TypeError: invalid 'in' operand element, Opera says Uncaught exception: TypeError: Operator 'in' applied to non-object and Chrome also gags: Uncaught TypeError: Cannot use 'in' operator to search for 'nodeType' in #p1. See [fiddle](). Any idea of what's going on?Meliorism
I'm not sure and unfortunately I can't see your fiddle, here's a fiddle jsfiddle.net/chsck/12Gadabout
Although I am struggling to follow what is going on in the extension code it does answer the question... +1 for that. Indeed I forgot to put my fiddle. In it I was trying to invoke the function with isMyNextSiblingStrict("#p1") which was a typo from what I wanted to do. But after seeing your last fiddle, which BTW works perfectly, I also fixed my error and I tried to invoke the function with isMyNextSiblingStrict($("#p1")) but it always returns FALSE. I thought $("#p1") would be the same as document.getElementById("p1") but it doesn't seem to be the case... Stack Overflow Chat?Meliorism
$("#p1") returns a jQuery object, whereas document.getElementById("p1") returns a DOM node. You can get the same thing from jQuery by doing $("#p1")[0] or $("#p1").eq(0). I've not seen the chat before, fancy. Don't worry too much about the extension code, it's mostly just taken from jQuery's own method defintion for .siblings. The pertinent bit is the middle function in the .map.Gadabout
A
4

Is this what you meant: http://cssdeck.com/labs/0blyuslnzv

var isStrict = function(elem1, elem2) {
    "use strict";

    var e1 = document.querySelectorAll(elem1)[0],
        elemNext = document.querySelectorAll(elem1 +" + "+ elem2)[0];

    if (e1.nextSibling.textContent.trim().length) {
        return e1.nextSibling === elemNext;
    } else {
        return e1.nextElementSibling === elemNext;
    }
};

Usage eg.

isStrict("head", "body") => true

isStrict("#p1", "#p2") => true

isStrict("#p2", "#p3") => false
Annual answered 13/2, 2014 at 17:46 Comment(0)
T
2

Try this.

var $selector = $("#p1");
$selector.siblings().each(function(){
    $(this).addClass('checkSelector');
    if(document.querySelectorAll($selector.selector+' + .checkSelector').length){
        //Do Strict Action
    }else if(document.querySelectorAll($selector.selector+' ~ .checkSelector').length){
        //Do non strict but still sibling action
    }
    $(this).removeClass('checkSelector');
});

http://jsfiddle.net/chsck/5/

Tacheometer answered 13/2, 2014 at 18:1 Comment(0)
G
2

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 );
    }
});
Gadabout answered 13/2, 2014 at 18:44 Comment(5)
However, be careful with your example as it is incorrect by your own definition - While commuting to work this morning I was thinking "I scr*wed up in my question..." but gladly someone was awake enough to spot it... :-) I will review and test all answers and pick one later today.Meliorism
I was testing your first solution and got a console error: Firefox says TypeError: invalid 'in' operand element, Opera says Uncaught exception: TypeError: Operator 'in' applied to non-object and Chrome also gags: Uncaught TypeError: Cannot use 'in' operator to search for 'nodeType' in #p1. See [fiddle](). Any idea of what's going on?Meliorism
I'm not sure and unfortunately I can't see your fiddle, here's a fiddle jsfiddle.net/chsck/12Gadabout
Although I am struggling to follow what is going on in the extension code it does answer the question... +1 for that. Indeed I forgot to put my fiddle. In it I was trying to invoke the function with isMyNextSiblingStrict("#p1") which was a typo from what I wanted to do. But after seeing your last fiddle, which BTW works perfectly, I also fixed my error and I tried to invoke the function with isMyNextSiblingStrict($("#p1")) but it always returns FALSE. I thought $("#p1") would be the same as document.getElementById("p1") but it doesn't seem to be the case... Stack Overflow Chat?Meliorism
$("#p1") returns a jQuery object, whereas document.getElementById("p1") returns a DOM node. You can get the same thing from jQuery by doing $("#p1")[0] or $("#p1").eq(0). I've not seen the chat before, fancy. Don't worry too much about the extension code, it's mostly just taken from jQuery's own method defintion for .siblings. The pertinent bit is the middle function in the .map.Gadabout
M
0

Ignoring what others might have written;

function checkAdjacency(element1,element2){
    var result = isLooseOrStrict(element1,element2);
    if(result != 'not'){
        return result;
    }
    else{
        return isLooseOrStrict(element2,element1);    
    }
}

function isLooseOrStrict(elemName1, elemName2){
    var element = document.getElementById(elemName1);
    var element2 =  document.getElementById(elemName2);

    if(element.nextSibling.nodeType != 3){
        if(element.nextSibling == elem2){
            return 'strict';
        }
    }
    else if(element.nextSibling.nextSibling == element2){
        if(element.nextSibling.nodeValue.trim() != '')
            return 'loose';
        else
            return 'strict';
    }
    return 'not';
}
Mori answered 14/2, 2014 at 9:51 Comment(0)
Q
-1

you can do this using only vanilla like bellow

(jsFiddle -> http://jsfiddle.net/Castrolol/chsck/4/ )

function getSiblings(elem){    
    var siblings = [];
    var actualElem = elem;
    while(actualElem = actualElem.nextSibling ){
        if( !actualElem.textContent.trim() ) continue;
        siblings.push({
            elem: actualElem,
            isStrict: !(actualElem instanceof Text)
        });
    }
    return siblings;
}

//using the function
var element = $("#p1").get(0);
var siblings = getSiblings(element);
siblings.forEach(function(sibling){
    if( sibling.isStrict ) {
        //here is a tag
        sibling.elem.style.color = "red";
    }else{
        //here not is a tag        
    }
});
Quiet answered 13/2, 2014 at 17:58 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.