1

When having called .before on elements that are detached from the DOM, .end behaves differently than it does with attached elements:

var $div1 = $("div");
console.log($div1.after("foo").end());  // [document]
$div1.detach();
console.log($div1.after("foo").end());  // [<div></div>]

(Fiddle: http://jsfiddle.net/R2uc7/2/)

Apparently, .before causes different behaviour to .end depending on the node being attached or detached. I don't see the logic and I'm not sure what I can rely on.

Could someone enlighten me on the defined behaviour of .end combined with .before?

pimvdb
  • 151,816
  • 78
  • 307
  • 352
  • You can't insert something before something that is not in the dom. I am surprised it even does anything, the [dom equivalent](https://developer.mozilla.org/en/DOM/element.insertAdjacentHTML#Visualization_of_position_names) would just fail silently. – Esailija Jul 17 '12 at 18:00
  • @Esailija "As of jQuery 1.4, .before() and .after() will also work on disconnected DOM nodes.". – meze Jul 17 '12 at 18:04
  • @meze In some kind of hacky way they do work yes, the elements are still completely disconnected from each other though and only give an illusion of working because they happen to be wrapped by the same jQuery object. Nothing was actually inserted before or after. – Esailija Jul 17 '12 at 18:08
  • @Esailija still, it's legal in jquery. `sibilings` works with DOM tree and we're talking about detached nodes. I don't understand why `$("
    ").after('

    ').filter('p').end()` works and `$("
    ").after('

    ').end()` does not...
    – meze Jul 17 '12 at 18:17
  • @Esailija: You're correct. As I understand it, the solution is to wrap the detached node, so that the "before" concept can be realized since there is now a reference node. That would cause the same branch for both cases in zzzzBov's extract, because there's a `parentNode`. Is that correct? – pimvdb Jul 17 '12 at 18:18
  • @pimvdb he's wrong on before/after. The docs say `before()` and `after()` can work on detached nodes. – meze Jul 17 '12 at 18:20
  • 1
    @meze: He's not saying anything wrong as far as I'm concerned. I think I'm getting it though - with detached nodes, only the set is changed, and `.filter` looks at the new stack so that works. `.siblings` doesn't look at the stack but at the tree. This fails because there is no parent node, so there can't be a concept of "siblings". – pimvdb Jul 17 '12 at 18:22
  • @pimvdb but it doesn't explain why `end` gives unexpected result. Sure you can't use DOM tree functions on disconnected nodes. – meze Jul 17 '12 at 18:23
  • 2
    @meze: Implicitly it does: `.end` merely pops the stack. Since `.before` only changes the stack for detached nodes, `.end` pops a different item from the stack in that case than it does for attached nodes. – pimvdb Jul 17 '12 at 18:24
  • @pimvdb yes, `.after` and `.before` are only legit if the subject has a parent. Don't count on jQuery's attempts at trying to make it work because it just gives you false hope. `[document.createElement("div"),document.createElement("div")]`: doesn't mean they are siblings or related in any way to each other. – Esailija Jul 17 '12 at 18:36
  • @pimvdb why are they only legit if the subject has a parent? .after and .before works with some limitations, but they still can be used without parents. – meze Jul 17 '12 at 18:44
  • @meze: Think of the definition of "`.before`" and "siblings": Siblings are siblings because they share their parent. One node is before another node in that they are at the same level and connected to each other via their parent. Without a parent, you cannot place one node before another; they have no connection after all. – pimvdb Jul 17 '12 at 20:23

1 Answers1

1

jQuery v1.7.2 uses pushStack to build the new DOM elements.

pushStack adds items to the jQuery object's stack (go figure!), and end pops the last one off, returning the rest of the stack (whatever remains).


jQuery v1.7.2 line #5860:
annotation mine

before: function() {
    if ( this[0] && this[0].parentNode ) {
        return this.domManip(arguments, false, function( elem ) {
            this.parentNode.insertBefore( elem, this );
        });
    } else if ( arguments.length ) {
        var set = jQuery.clean( arguments );
        set.push.apply( set, this.toArray() ); 
        return this.pushStack( set, "before", arguments ); //pushStack in use
    }
}
zzzzBov
  • 174,988
  • 54
  • 320
  • 367
  • Thanks, this explains "how". So actually the issue is bigger, since *any* function that depends on the stack behaves differently between the two cases, e.g. http://jsfiddle.net/R2uc7/4/. Is this behaviour reliable? It seems a bit odd. – pimvdb Jul 17 '12 at 18:01
  • @pimvdb, "it seems a bit odd" just remember that jQuery was written by people and that they sometimes make mistakes. It's hard to tell if this sort of behavior is intentional, or whether it's simply an extreme case. – zzzzBov Jul 17 '12 at 18:34
  • @zzzzBov: I understand that :) Just had a hard time debugging this. Anyway, what I didn't take into account is the difference between the jQuery set and the actual siblings which caused confusion. Thanks for your help :) – pimvdb Jul 17 '12 at 18:36
  • [File a bug on the jQuery website](http://docs.jquery.com/How_to_Report_Bugs). They would love to hear about real bugs and you would love to hear from them directly if it's by design after all. – hippietrail Oct 17 '12 at 10:49