skip to Main Content

problem

I have an element $(elt_Main) (<span id="a">)

I call $(elt_Main).find(selector); on it

With a selector selector = ':not(span span)';

But jquery is not giving out all the child elements I want.

ex [test1.html]

  • say the content in html is::

    <body>
    <span id="a">The first <span class="red">signature <span class="green">for the .find() <span class="blue">method accepts</span> a selector</span> <strong>expression</strong> of the <span class="green">same type that we can pass to the</span> $() function</span>. The <span class="red">elements</span> will be filtered by testing whether they match this selector; all parts of the selector <strong>must lie inside of an element</strong> on which .find() is called.</span>
    </body>
    
  • the javascript is::

      let elt_Main = document.getElementById('a');
      // ...
      html_ResultAppend += hr_3;
      selector = ':not(span span)';         // <<< watch
      jqElt = $(elt_Main).find(selector);   // <<< watch
    
  • the js that provides output for debug is::

      for (let o of jqElt) {
        html_ResultAppend += hr_4;
        html_ResultAppend += o.outerHTML;
      }  
    
  • the result of jqElt will be::

    <strong>expression</strong>
    <strong>must lie inside of an element</strong>
    
  • but the expecting result should be::

    <span class="red">signature <span class="green">for the .find() <span class="blue">method accepts</span> a selector</span> <strong>expression</strong> of the <span class="green">same type that we can pass to the</span> $() function</span>
    <span class="red">elements</span>
    <strong>expression</strong>
    <strong>must lie inside of an element</strong>
    
  • the pb & the cause is::

    these 2 elements are missing

    <span class="red">signature <span class="green">for the .find() <span class="blue">method accepts</span> a selector</span> <strong>expression</strong> of the <span class="green">same type that we can pass to the</span> $() function</span>
    <span class="red">elements</span>
    
  • (I think) the logic of jquery is that (which is wrong)::

    <span id="a"> <span class="red">signature ...

    -> <span class="red">signature ... is inside a span <span id="a">

    -> we said :not(span span)

    -> so this should not be selected

    (^ same as above for <span id="a"> <span class="red">elements</span>)

  • my logic is that::

    <span id="a"> ($(elt_Main)) is the element that invoke .find()

    <span id="a"> ($(elt_Main)) itself should never be taken into account / play any effect when we use css selectors inside .find()

    • otherwise, it wont make any sense in following examples::

      ex, say we do this: $(elt_Main).find('span');

      this should only find child element of elt_Main that is a span — this is correct.

      if we take <span id="a"> ($(elt_Main)) into account when use use selector, this will select the <span id="a"> ($(elt_Main)) itself — this is wrong.

    • in another words —

      when .find() is invoked by <span id="a"> ($(elt_Main)),

      the selector should treat this (the element <span id="a"> ($(elt_Main)) to search on) as if the tag <span id="a"> doesnt exist (– it should not play any effect), and only search on the innerHtml.

to prove my logic — example [test1.html test2.html]

  • plunker code ex: jquery .find() use with :not selector is missing some elements

    the js selects the elements, and write to the html body.

    plunker code image compare

  • test1 uses .find() + :not()

    test2 uses only :not()

    test2 removed the <span id="a"> tag in $(elt_Main)

    • (– just as I said above — we should treat it as if the tag <span id="a"> doesnt exist)

    • (if you think Im wrong on this, and you say: do not remove the <span id="a"> makes test1 & test2 produce same result -> check out test1 vs test3 — its even worse)

  • we clearly see that

    test1 output is missing elements

    test2 output is correct

(back to the question)

"jquery .find() use with :not selector is missing some elements (the selector is affected by the .find() invoking element, but it shouldnt)"

So, Is jQuery wrong? or am I wrong? Why?



Update

discussions on "different semantic of .find() bt jQuery vs native Javascript"?

  1. x

    as another answer points out,

    the native Javascript querySelectorAll does consider property from the global html file scope

    not from a local sandbox scope – starting from & limited to the inside of the invoking (parent) element AA, which was how I understood it.

    • [[ my understanding was due to

      1. my inexperience with (native) Javascript

      2. my personal preference on how the rule should be applied when using selector from an element AA is that:

        I prefer to select thing inside AA, ignoring all the effects from the outside scope — so, treat those selections in AA as a local sandbox scope

        (again, this is just a subjective personal preference thing, but if rules are not designed in this way, of course then, I wont use it in this way)

      ]]

    -> so, I was wrong on the understanding in native Javascript

  2. x

    though, another problem arise:

    so, the problem is not there is something wrong with $(elt_Main).find(':not(span span)')

    instead, the problem is something is wrong with $(elt_Main).find('span span') //doubt?

    $(elt_Main).find('span span') selects less than what normally expected

    — according to the semantic in native Javascript

    -> so, jQuery is wrong on this //doubt?

  3. x

    however, it seems the problem is actual that:

    jQuery is intended to use a semantic of find() that is different than the semantic of querySelectorAll() in native Javascript //doubt?

    (– see the examples below for compare)

    — which mean, yes, there is a local sandbox scope

  4. x

    the problem remain is then:

    if "jQuery is intended to use a different semantic than its in native Javascript"

    then there is actually a bug in $(elt_Main).find(':not(span span)')

    or if not

    then $(elt_Main).find('span span') is bugged

example



Update

  • Added comparison with "native Javascript with :scope"
    [[((forgot to mention))]]

    Turns out, it seems the native Javascript with :scope has the same issue then?

    or :not() was meant to view from a "global html file scope"?

    — which means, so, to let the effect of "local sandbox scope" play in, we need to specify :scope :not(:scope span span), instead of simply :scope :not(span span)

    • [[this does output the desire output I intended to have (all the way up to) initially.

      if :not() behaves in this way, this does clarifies something.

      so, with such rule (such :not() behaves) native Javascript has no issue;

      as for jQuery… may still need to fix this //doubt?]]

  • jsfiddle ex: different semantic of .find() bt jQuery vs native Javascript (updated with :scope)

different semantic of .find() bt jQuery vs native Javascript (updated with :scope)

2

Answers


  1. Okay I have a speculation here so I’m just going to post it as an answer.

    As I’ve written in my comments, essentially $(selector).find(selector) works like a two pass filter. It is not supposed to pose any sandbox-ish mechanism that blocks the the second pass of selection from "detecting" any fact / property of the candidates, including but not limited to their parent.

    HOWEVER, what I suspect about $(A).find(A B) is this does NOT (but IMO should) work behind the scene like this:

    1. select all A’s descendants
    2. select those from the selection returned by step 1 that are B and have an A ancestor (something like foreach c in S: if c is B and c.hasAncestor(A): nS.add(c))

    INSTEAD, it probably work behind the scene like this:

    1. select all A’s descendants
    2. select those from the selection returned by step 1 that are A
    3. select the descendants of the selection returned by step 2 that are B

    which is probably equivalent to:

    1. select the descendants of A (pass 1) that are themselves A (pass 2)
    2. select the descendants of the selection returned by step 1 that are B (pass 3)

    As you can see, the logic is twisted in the (speculated) approach behind the scene. What you get became a "three pass filter".

    Here’s an example which might give you a clearer idea on what I mean:

    const a = $("span").find("span span");
    console.log(a.length);
    console.log(a.get(0).textContent);
    
    const b = $("span").find("span").find("span");
    console.log(b.length);
    console.log(b.get(0).textContent);
    <script src="https://code.jquery.com/jquery-3.6.3.min.js"></script>
    <span>A<span>B<span>C</span></span></span>

    In other words, what $("span").find("span span") should (IMO) do is:

    Among the descendants of "span" selection, select spans that are inside a span

    But what it (seem to) really does:

    Among the descendants of "span" selection, select spans inside those that are spans

    As noted in the other answer, when :not() is involved, such problem does not occur. I suspect (again) that it is because the negation makes it harder to look right when you twist the logic as I stated.

    I don’t consider the current way that the descendant selector is implemented correct. I do wonder how the jQuery devs would argue about it though. (But this is really just my speculation. I haven’t check any source code or so to make sure it is 100% the case.)

    Login or Signup to reply.
  2. Your selector asks for any element that is not a <span> descendant of any other <span>. The descendant test does not apply only to the subtree beneath the starting point in the DOM (the element where the .find() is rooted); it involves the entire DOM. It has to work that way in general. Your claim that "it shouldn’t" is a misunderstanding of how selectors work.

    Because your starting element is itself a <span>, all the <span> elements in the subtree are descendants of a <span>, so none of them match the selector.

    You can try your test with the native .querySelectorAll():

    let elt_Main = document.getElementById("a");
    let targets = eltMain.querySelectorAll(":not(span span)");
    

    and see what you get.

    Here is a clearer example, using a :not() selector that involves a parent element of the starting point:

    const start = document.getElementById("start");
    let found = start.querySelectorAll(":not(.foo span)");
    console.log("Found " + found.length + " elements");
    <div class="foo">
      <span id="start">
        <span>Hello world</span> <b>a bold tag</b> <span>goodbye world</span>
      </span>
    </div>

    Starting from the outer span, that selector (:not(.foo span)) asks for any element that is not a <span> that descends from an element with class "foo". All the <span> tags beneath the starting point do match that selector in the :not because they all descend from the parent <div>. Thus the query finds only one element, the <b>.

    As a general rule, finding a profound bug in a platform that’s been in use for almost 20 years is fairly unlikely. Not impossible, but unlikely.

    edit — It’s pointed out in a comment that the simpler case of the selector "span span", that is, a positive search for any <span> that is a descendant of a <span> results in different behavior between jQuery’s selector engine and the browser’s native querySelectorAll():

    $(function() {
      console.log("jQuery finds " + $("#start").find(".foo span").length + " spans that descend from .foo");
      console.log("browser finds " + document.getElementById("start").querySelectorAll(".foo span").length + " spans that descend from .foo");
    });
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
    <div class=foo>
      <span id=start>
        <span>Hello world</span> <b>bold</b> <span>goodbye world</span>
      </span>
    </div>

    I find this very surprising. It should be noted that jQuery predates the introduction of the native DOM search methods by many years, so while this could be considered wrong (I certainly do) it must be understood that there may be millions of applications that depend on the old behavior, so changing it is probably not an option for the jQuery maintainers.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search