Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JavaScript unexpected object behavior with jQuery

I have two <div> elements, and the following JavaScript code:

var myObject = {
    $input: $('<input />'),
    insert: function () {
        $('div').append(this.$input);
        $('div').append(' ');
    }
};

myObject.insert();

This, as I expect, produces an <input> element within each of the two <div> elements.

Now when I create a new instance of myObject and call insert() again I will be expecting 4 <input> elements, two in each <div>. Weirdly, I only get 3 <input> elements!

See example code here: http://jsfiddle.net/FNEax/

like image 518
Randomblue Avatar asked Aug 02 '11 19:08

Randomblue


4 Answers

You're creating 1 input explicitly:

$input: $('<input />',{value:i}),

...but cloning it implicitly when you try to append it to multiple divs

// 2 divs
$('div').append(this.$input);

Then Object.create doesn't create a new $input, so on the second pass, it appends (moves) the input from the second div (which is actually the original) to the first div, and then does the implicit clone to populate the second.

Here's a jsFiddle example that increments an i variable whenever insert() is called, and adds it as the value of the input. Notice that it is always set at 0.

I also modified it to pass a string to insert so you can see which call each input came from.

The two inputs from the second call both still have the string passed to the first call.


EDIT:

I flipped it around mid explanation, but the concept is the same.

When the second insert() is called, the clone is first created of the original and added to the first div, then the original is appended to the second div (where it already is).

jQuery makes the clones first, then appends the original last.

Here's another jsFiddle example that adds a custom property to the original, then adds some text next to the element with that custom property after each insert(). The text is always added next to that original in the second div.

like image 79
user113716 Avatar answered Nov 03 '22 02:11

user113716


This is what is happening. From the jQuery docs:

If an element selected this way is inserted elsewhere, it will be moved into the target (not cloned)

If there is more than one target element, however, cloned copies of the inserted element will be created for each target after the first.

So the first time around, since your input isn't anywhere in the DOM it is cloned and inserted into both divs. But, the second time it is called it is removed from the second div, before being cloned and added back into both divs.

At the end of your code, the first div contains both inputs, but the second div only contains the most recent input, since each input was removed from your last div.

http://jsfiddle.net/hePwM/

like image 36
Paul Avatar answered Nov 03 '22 03:11

Paul


Once an element is inserted into the DOM, another .append() call with it as the appended content causes it to move within the DOM (docs). Your code creates a jQuery collection with a single input therein, which input has yet to be appended to the DOM. So the first call to insert() appends it to each (using the cloning or copying mechanism internal to jQ).

In the second call, however, this.$input references something which is already in the DOM (due to the first call). Internally, jQuery is each-ing the collection of DIVs and appending the input which lives inside of this.$input. So it adds it, the moves it.

The primary issue is that you're re-appending the same input over and over. Remember that JavaScript generally references existing objects rather than make new ones. That same input element keeps getting re-referenced.

If you want a method to add an input to every DIV, you should simply pass the input markup into append:

$( 'div' ).append( '<input />' );
like image 31
JAAulde Avatar answered Nov 03 '22 03:11

JAAulde


The wierd behavior is due to the fact you are using a JQuery collection where you shouldn't be. How it even worked in the first place is beyond my skillset.

var myObject = {
input: '<input />',
insert: function () {
    $('div').append(this.input);
    //$('div').append(' ');
}
};
like image 2
Caimen Avatar answered Nov 03 '22 02:11

Caimen