Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Triple negation needed to retrieve text node

Tags:

jquery

Wanting to get text node from an element using some 'readable' method, i came to this strange behaviour i cannot explain.

-jsFiddle-

Using following HTML markup:

<div>text node <b>and b element</b></div>

A text node isn't a DOM element, so i was wondering if this works:

var textnode = $('div').contents().filter(':not(*)'); //returns jq empty set

It fails even if using .filter('*') and doesn't return the text node but other element (b).

So testing it, i used not() method, which i wasn't sure it works or not for text node:

var textnode = $('div').contents().not('*'); //returns jq empty set

Let admit it. But this one returns the text node:

var textnode = $('div').contents().not(':not(:not(*))'); //returns text node in set

So why .not('*') is failing while .not(':not(:not(*))') works ? I cannot explain it.

PS: .filter(':not(:not(:not(*)))') fails while i was expecting it to work exactly as .not(':not(:not(*))')

like image 455
A. Wolff Avatar asked Jan 04 '16 14:01

A. Wolff


1 Answers

Because the .not(':not(:not(*))') is not a simple selector (basically, it has more than one "part"), it is treated differently.

Simple selectors (like the .filter(':not(*)') and .not('*')) get passed to the jQuery.filter() function with a boolean indicator named not. This indicator which wraps an additional :not(...) around the selector for .not(selector).

jQuery.filter() returns nodes that filter through the selector. It has an additional check so it only ever returns nodes of nodeType 1 (elements).

For complex selectors (like .not(':not(:not(*))')), the mechanism works a bit differently; the selector gets passed to jQuery.filter() but the not boolean indicator is not set. Instead, the nodes returned from jQuery.filter() are compared to the original list of nodes and filtered in or out depending on whether you used .not() or .filter(). Since jQuery.filter() only ever returns elements, when comparing to the original list of nodes it can't find any match for the text element. In the case of your .not(':not(:not(*))'), this means that according to this logic, the text node should not be filtered out, because it's not in the list of nodes to be filtered out that was returned by jQuery.filter().

The reason for these different mechanism is probably that you can't just wrap :not(...) around any complex selector; it could change the intended meaning.

Though they do state in the .contents() documentation that text nodes are not supported by most of the rest of jQuery. It could be solved by explicitely only ever returning nodeType 1 nodes from the winnow function.

Original jQuery 1.11.3 code:

function winnow( elements, qualifier, not ) {
    //...

    return jQuery.grep( elements, function( elem ) {
        return ( jQuery.inArray( elem, qualifier ) >= 0 ) !== not;
    });
}

Changed code:

function winnow( elements, qualifier, not ) {
    //...

    return jQuery.grep( elements, function( elem ) {
        return ( jQuery.inArray( elem, qualifier ) >= 0 ) !== not && elem.nodeType === 1;
    });
}

I don't know if this qualifies as a bug, but it is atleast a solvable inconsistency in jQuery. You might want to report it to jQuery.

like image 154
Martijn Avatar answered Nov 07 '22 21:11

Martijn