tags:

views:

60

answers:

5

I have this code (running jQuery 1.4.2)

var elementToAdd = $('<h3>').html('header');
var p = $('<p>').html('hello world');
elementToAdd.after(p);
$('div#content').append(elementToAdd);

However, the output is

<div id="content">
  <h3>header</h3>
</div>

The paragraph "Hello world" is not added.

What am I doing wrong?


I have been trying out some variations:

This does not work either:

var elementToAdd = $('<div>Header</div>');
var p = $('<p>hello world</p>');
elementToAdd.after(p);

or this:

var elementToAdd = $('<h3>header</h3>').after('<p>hello world</p>');

But this works (on Firefox, at least):

var elementToAdd = $('<div>').after('<h3>header</h3>').after('<p>hello world</p>');

Why?

+5  A: 

I don't think .after works with elements that have not been added to the DOM yet...

Try this

var elementToAdd = $('<h3>').html('header');
var p = $('<p>').html('hello world');
$('div#content').append(elementToAdd);
elementToAdd.after(p);

Edit: Yep, that works alright! http://jsfiddle.net/tu2GY/

Edit 2: ok, I am not extremely good at this, but here goes...

As @Extrakun pointed out, the jQuery documentation indeed notes that the element may not be attached to the DOM...

As of jQuery 1.4, .before() and .after() will also work on disconnected DOM nodes.

So, I took a deep breath and opened jquery-1.4.2.js (in vim :D) and what I found was that whatever is being passed to .after on an element that has no parentNode, is being used as a selector string which I think is failing when you are passing a jQuery object. I'll try to post some code of what I mean in a minute or two here... (inside pushStack method)

ret.selector = this.selector + "." + name + "(" + selector + ")";

The selector here is what ends up being whatever you pass to .after. Didn't really make out much after that.

Shrikant Sharat
Thanks for braving the source code! :)
Extrakun
ha! It helps me think of jQuery as *not-a-black-box* ;)
Shrikant Sharat
A: 

'elementToAdd' is not a jQuery object pointing to the element. try this:

var elementToAdd = $('<h3>');
elementToAdd.html('header');
var p = $('<p>').html('hello world');
$('div#content').append(elementToAdd);
elementToAdd.after(p);
Moin Zaman
No. It is a jquery object pointing to the element. **Edit:** That's why chaining works :)
Shrikant Sharat
var x = $('a#someID').attr('href'); // this will return the value inside the href, x will not point to the <a> element. Or am I missing something?
Moin Zaman
That's the behaviour of `.attr`. But `.html` allows chaining and hence, does return the jQuery object. In fact, if you call `.attr` with two arguments, you get a jQuery object and hence chaining still works...
Shrikant Sharat
Let me put it this way, when you call `.attr('href')` you are *expecting* a non-jQuery object and you don't *intend* to do chaining after that `.attr` call. But for `.html` with arguments, you are *setting* something, not *getting*, hence the return value is the jQuery object to facilitate further chaining... hope that makes it clear :)
Shrikant Sharat
A: 

Manipulating dom is an expensive operation and its best to do it minimum number of times, you above code can be very easily changed to,

$('div#content').append('<h3>header</h3><p>hello world</p>');
ovais.tariq
That was just an example. The real code involves dynamically writing out strings fetched from a DB and setting up an link with a function callback...
Extrakun
still you can use concatenation to create a single string and then pass it on to the append function
ovais.tariq
+1  A: 

EDIT: To modify the original set, you can .push() a DOM element (not a jQuery object) into the set.

var elementToAdd = $('<h3>').html('header');
var p = $('<p>').html('hello world');

   // Push the DOM element in
elementToAdd.push( p[0] );   // [0] gets the DOM element at index 0
$('div#container').append( elementToAdd );​

Instead of .after(), use jQuery's .add() method.

var elementToAdd = $('<h3>').html('header');
var p = $('<p>').html('hello world');

 // Add the new object ----------------v
$('div#container').append( elementToAdd.add(p) );​

This will append it as a sibling like you want.

It returns a new jQuery object with both elements as siblings. Because it doesn't modify the original object, you need to either call it in the .append() or store the result in a variable to append.


EDIT: (As noted in another answer, you can now use after() like add(), in that it returns a new set and doesn't modify the original.)

To explain why, this is because a jQuery object is an Array of DOM elements. A DOM element can be a single element with nested descendants, but not two siblings. So when you do .after(), you're trying to add a sibling to each single element in the array.

To deal with siblings, jQuery has them stored as additional items in its Array.

So when you create a jQuery object by passing 2 or more sibling elements, it splits them apart, and makes them separate items in the Array.

var $obj = $("<div>div element</div> <span>span element</span>");

This will give you a jQuery object with an Array of 2 items.

$obj[0] is the <div> element
$obj[1] is the <span> element

So if you were to create them separately, you would need to use .add() to add the new item to the Array.

var $obj = $("<div>div element</div>");

$obj[0] is the <div>

var $span = $("<span>span element</span>");
$obj = $obj.add( $span );

$obj[0] is the <div>
$obj[1] is the <span>
patrick dw
Using add() works, though reading the API it gives me the impression that it is more expensive than DOM insertion methods. Am I right?
Extrakun
@Extrakun - Deleted previous comment. It looks like `.after` is now able to behave in a manner similar to `.add()` in that it returns a new object with both elements, so you would need to overwrite the old `elementToAdd` value with the new: `elementToAdd = elementToAdd.after(p);`
patrick dw
I guess this raises the question of using after() vs. add(). insert() is listed as DOM insertion, while add() is traversal. I guess that should be another question though :D
Extrakun
@Extrakun - I think they ultimately are doing the same thing. They both call the inner `pushStack` method. And both are (in my opinion) equally confusing in that they don't modify the original set like one would expect (again in my opinion).
patrick dw
@Extrakun - ...here's a third option. Because a jQuery object is an Array, you can use the Array's `.push()` method. But be aware, you need to `.push()` the DOM element in. Not the jQuery object. This will give you a modified set like you expect, but also be aware that you won't be able to chain after `.push()`. Shouldn't be an issue since you have a variable reference. I'll update my answer.
patrick dw
+2  A: 

Interesting. Adding a temporary works:

var e = $('<h3>').html('header');
var p = $('<p>').html('hello world');
var result = e.after(p);
$('div#content').append(result);

...So I'm guessing that e.after(p) returns "p after e", but doesn't actually change e. So it works in a chain, and it works disconnected, but won't work quite the way you're expecting it to work.

Matt Gibson
+1 weee! That explains what I saw in the jQuery source code, the `pushStack` method makes a new object and returns that, which represents both the elements.
Shrikant Sharat
Looks like you're right. If there's no parent node, it now pushes the object in, returning a new set. http://github.com/jquery/jquery/blob/master/src/manipulation.js#L146
patrick dw