JavaScript unexpected object behavior with jQuery - javascript

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/

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.

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/

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 />' );

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(' ');
}
};

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

Related

jQuery: Using .after() or .before() adds element to last item in selection only

I've been using jQuery for a while but this is a new one. A simplified example:
HTML
<div class='custom'></div>
<div class='custom'></div>
<div class='custom'></div>
jQuery:
var $customElems = $('.custom'),
$spanOuter = $('<span class="outer"/>'),
$spanInner = $('<span class="inner"/>');
$customElems.each( function() {
$(this).wrap($spanOuter).after($spanInner);
});
JSFiddle:
http://jsfiddle.net/a3ZK8/
I would have expected the 'inner' span to be added to all three elements in the selection but it gets always inserted into the last one only (no matter how many). I tried it with .before(), with and without the chaining, same result. What am I missing??
The problem is you are using a reference to a jQuery object.
Hence you keep moving the object reference around within each iteration.
If you have no events attached or no need for the span to be a jQuery object then just pass the parameter as a HTML string literal instead of an object reference
Cloning a jQuery object that doesn't need to be a jQuery object in the first place is just redundant processing and unnecessary overhead.
Change your jQuery object to a string similar to this:
spanInnerString = '<span class="inner"/>';
and your method like this:
$(this).wrap($spanOuter).after(spanInner);
The result is:
<span class="outer"><div class="custom"></div><span class="inner"></span></span>
<span class="outer"><div class="custom"></div><span class="inner"></span></span>
<span class="outer"><div class="custom"></div><span class="inner"></span></span>
DEMO - Passing parameter as HTML string
Off course, the same goes for the outer span. Don't create jQuery objects unless you have to.
If you must use a jQuery object because you want to attach events to the span or similar, than cloning is the way to go, though make sure you use clone(true, true) then to also clone the attached events.
You need to clone the element. Otherwise, after() will relocate the same element 3 times, which results in it being attached to only the last looped element.
$customElems.each(function () {
$(this).wrap($spanOuter).after($spanInner.clone());
});
Demo: Fiddle
You might ask, "Why would wrap() work?" That's because 'wrap()' internally clones the element.
You're moving the same span from place to place. If you acted on all three divs at once, jquery will instead clone the span.
http://jsfiddle.net/a3ZK8/1/
var $customElems = $('.custom'),
$spanOuter = $('<span class="outer"/>'),
$spanInner = $('<span class="inner"/>');
$customElems.wrap($spanOuter).after($spanInner);
From the documentation for .after:
Important: If there is more than one target element, cloned
copies of the inserted element will be created for each target except
for the last one.
which means the last element will always get the original, while all other selected elements will get a clone. That's why when you acted on one element at a time, it simply moved the same span around.

How to say jQuery to do .blur() function in newly appended object?

This question is connected with that
This code hides div when user type data to inputs and focus on another div
$(".Q,.A").blur(function(e) {
if ($(this).val().length > 0 && $(this).siblings("input").val().length > 0) {
$(this).parent().fadeOut(1000);
getData("ajaxPHP/insertNewWords.php?q='" + $(this).siblings('input').val() + "'&a='" + $(this).val() + "'&zestawID="+zestawID, "console");
$(".main").append("<div><input type='text' class='Q'></input><input type='text' class='A'></input></div><br>");
}
});
And when user enter data, this div hides and script creates new div (so user can enter infinite amount of data).
The problem is: new created divs don't hide.
So what should I do, if I want to involve new created divs into "$(".Q,.A")"?
The problem is (as I understand it), that you have a behaviour attached to a set of nodes on your page, and new nodes added to the page do not pick up this behaviour.
This is because of the way JQuery works. When you define a selector like $(".Q,.A") this selector evaluates to a set of known nodes on your page. The code that follows only applies to those found elements. This selector is never evaluated again, so any new nodes never get a chance to gain your desired behaviour.
The solution is to get JQuery to re-evaluate the selector every time the event occurs. So you need to listen for the event globally, then filter to only handle the elements that match your selector.
The correct way to do this is on
$(document).on("blur", ".Q,.A", function(){ ... });
See: http://jsfiddle.net/sAT6L/
Live has some discussion on how it used to be done in each version of JQuery.
Note: You should be able to restrict the scope to something more local than $(document).
You can use jquery's .on method on the parent container, because events "bubble" to the parent container. The on function also allows you to specify a selector to filter the children elements, which gets applied dynamically, so you can use your ".Q,.A" selector there:
$(document).ready(function(){
$("#container").on("blur", ".Q,.A", function(e){
if($(this).val().length>0 && $(this).siblings("input").val().length>0){
$(this).parent().fadeOut(1000);
$("#container").append('<div><input type="text" class="Q"><input type="text" class="A"></div>');
}
});
});
Fiddle: http://jsfiddle.net/rK3HS/1/

Using remove as opposite of append not working

Here's how I append the value:
$('<div>someText</div>').appendTo(self);
And here's how I want to remove it:
$(self).remove('<div>someText</div>');
The appending works, the removing doesnt. What am I doing wrong?
The .remove() function takes a selector to filter the already matched elements, not to match elements inside of them. What you want is something like this:
$(self).find('div:contains(someText)').remove();
That will find a <div> element containing the text someText inside of whatever element self is, then removes it.
The API http://api.jquery.com/remove/ sais that a selector is required.
Try $(self).remove('> div');
This will remove the first childs of div.
You can use $(self).filter('div:contains("someText")').remove(); to remove a div with a specific content or $(self).find('> div').remove(); to remove the first childs of div.
EDIT: removed first version I posted without testing.
It most likely has to do with the scope of self. Since you've named it self I am assuming that you are getting this variable using $(this) on the click event. If that's the case, and you want to call the remove method, you can only do so from within the same function. Otherwise you need to either store the element in a variable or provide another selector to access it.
Example:
<div class="div1"></div>
this will be the div with the click event
$(document).ready(function(){
var self = null;
$('.div1').click(function(e){
self = $(this);
var itemToAdd = '<div>SomeText</div>';
$(itemToAdd).appendTo(self);
});
// to remove it
// this will remove the text immediately after it's placed
// this call needs to be wrapped in a function called on another event
$('.div1').find('div:contains(someText)').remove();
});

Jquery - Dynamically pass parameters to function

I'm not even sure I'm asking or going about this the right way but it's probably easier if I just show it. Basically, I'm trying to have run an attr of each div as a parameter through a function and have it return a different result based on that div's attr.
The example, as you can see, is a group of dropdowns that appear when you click on a link in the container div. If you make a selection it saves that as a attr in the parent div. The problem arises when you click out, then back in on the container ... instead of reshowing each dropdown with the appropriate default or selection showing, it just mirrors the result of the a next to it.
http://jsfiddle.net/nosfan1019/b7F6x/5/
TIA
I inserted some console.log() statements to see what was happening with your various jQuery selectors. I observe the following:
when I click in the first "click" node, _container is "top one"
thus in your iteration of the three divs, you select both divs with class 'dd' contained in the div with class 'top one'
the parameters _attr and _parent that you pass to your function select() are the same for each node that is processed, giving the same result for both 'dd' boxes.
I think you want to change the selectors you use to locate the nodes to modify.
foo = foo.find('.dropdown-toggle').html(_new + '<b class="caret"></b>');
with this line you get two divs and hence you've change both values(in case the value was chosen from the droplist).
To restore selected values correctly:
function modified(_select) {
console.log("modify");
foo = $('#box').html();
foo = $(_select).html(foo);
// iterate on collection to restore selected value from selection tag;
foo.filter("div[selection]").each(function(i, v){
var selected = $(v).attr('selection');
$(v).find('.dropdown-toggle').html(selected + '<b class="caret"></b>');
});
}
Then, it's needed to be checked if any of parentDiv has [selection] attr:
if($(y).filter("div[selection]").length > 0){
return modified(y);
}
http://jsfiddle.net/b7F6x/50/

Parse string for text between divs

I have a string of html text stored in a variable:
var msg = '<div class="title">Alert</div><div class="message">New user just joined</div>'
I would like to know how I can filter out "New user just joined" from the above variable in jQuery/Javascript so that I can set the document title to just the message.
Like this:
document.title = $(msg).filter("div.message").text();
Note that if the message changes to be wrapped in an element, you'll need to replace filter with children.
EDIT: It looks like the div that you want is nested in other element(s).
If so, you can do it like this:
document.title = $("div.message", msg).text();
Explanation: $('<div>a</div><div>b</div>') creates a jQuery object holding two different <div> elements. You can find the one you're looking for by calling the filter function, which finds mathcing elements that are in the jQuery object that you call it on. (Not their children)
$('<p><div>a</div><div>b</div><p>') creates a jQuery object holding a single <p> element, and that <p> element contains two <div> elements as children. Calling $('selector', 'html') will find all descendants of the elements in the HTML that match the selector. (But it won't return the root element(s))
This is a hack and not very clean, but it should work:
add a div node and set its html to your text message,
get the text of the added element and store it in a variable
destroy the node
set the title with the contents of the variable in step 2

Categories

Resources