.replaceWith() is not working in jQuery 1.9+ - javascript

I'm trying to clone a <textarea> and clone and replace the digit in the label <label> Number 1 <label> increasing by 1 each time the add button is pressed (So the first label will have Number 1, the label underneath Number 2 etc).
This works with jQuery 1.8 and below but anything above does not clone and add 1 to the digit.
HTML
<div>
<label for="number">Number <span class="one">1</span></label>
<textarea id="number"></textarea>
</div>
<button>Add</button>
jQuery
var $row = $('div').clone(),
cloneCount = 2;
$('button').click(function () {
$row.clone().insertBefore($("button"));
$('span').clone().attr('span', cloneCount++).replaceWith($('[class=one]:last')).text(cloneCount - 1);
});
JSFIDDLE: http://jsfiddle.net/wba6jvkj/

I don't know what you were attempting with .attr('span' and why it seemed to work in < 1.8, or why you are subtracting one from cloneCount, but this should do what you want:
var $row = $('div').clone(),
cloneCount = 2;
$('button').click(function () {
$row.clone().insertBefore($("button"));
$('span.one:last').text(cloneCount++);
});
jsFiddle example

(Answering to this old question, as Google led me here.)
The behavior of .replacewith() with disconnected nodes has been modified in jQuery 1.9. You may find the following explanation in the replaceWith() documentation (section "Additional Notes"), and also in the jQuery 1.9 upgrade guide:
Prior to jQuery 1.9, .replaceWith() would attempt to add or change nodes in the current jQuery set if the first node in the set was not connected to a document, and in those cases return a new jQuery set rather than the original set. The method might or might not have returned a new result depending on the number or connectedness of its arguments! As of jQuery 1.9, .after(), .before(), and .replaceWith() always return the original unmodified set. Attempting to use these methods on a node without a parent has no effect—that is, neither the set nor the nodes it contains are changed.
In your code, you are using .replaceWith() on a cloned element, thus which hasn't been inserted into the DOM yet.

Related

Why does jQuery return more than one element when selecting by type and ID? [duplicate]

I fetch data from Google's AdWords website which has multiple elements with the same id.
Could you please explain why the following 3 queries doesn't result with the same answer (2)?
Live Demo
HTML:
<div>
<span id="a">1</span>
<span id="a">2</span>
<span>3</span>
</div>
JS:
$(function() {
var w = $("div");
console.log($("#a").length); // 1 - Why?
console.log($("body #a").length); // 2
console.log($("#a", w).length); // 2
});
Having 2 elements with the same ID is not valid html according to the W3C specification.
When your CSS selector only has an ID selector (and is not used on a specific context), jQuery uses the native document.getElementById method, which returns only the first element with that ID.
However, in the other two instances, jQuery relies on the Sizzle selector engine (or querySelectorAll, if available), which apparently selects both elements. Results may vary on a per browser basis.
However, you should never have two elements on the same page with the same ID. If you need it for your CSS, use a class instead.
If you absolutely must select by duplicate ID, use an attribute selector:
$('[id="a"]');
Take a look at the fiddle: http://jsfiddle.net/P2j3f/2/
Note: if possible, you should qualify that selector with a type selector, like this:
$('span[id="a"]');
The reason for this is because a type selector is much more efficient than an attribute selector. If you qualify your attribute selector with a type selector, jQuery will first use the type selector to find the elements of that type, and then only run the attribute selector on those elements. This is simply much more efficient.
There should only be one element with a given id. If you're stuck with that situation, see the 2nd half of my answer for options.
How a browser behaves when you have multiple elements with the same id (illegal HTML) is not defined by specification. You could test all the browsers and find out how they behave, but it's unwise to use this configuration or rely on any particular behavior.
Use classes if you want multiple objects to have the same identifier.
<div>
<span class="a">1</span>
<span class="a">2</span>
<span>3</span>
</div>
$(function() {
var w = $("div");
console.log($(".a").length); // 2
console.log($("body .a").length); // 2
console.log($(".a", w).length); // 2
});
If you want to reliably look at elements with IDs that are the same because you can't fix the document, then you will have to do your own iteration as you cannot rely on any of the built in DOM functions.
You could do so like this:
function findMultiID(id) {
var results = [];
var children = $("div").get(0).children;
for (var i = 0; i < children.length; i++) {
if (children[i].id == id) {
results.push(children[i]);
}
}
return(results);
}
Or, using jQuery:
$("div *").filter(function() {return(this.id == "a");});
jQuery working example: http://jsfiddle.net/jfriend00/XY2tX/.
As to Why you get different results, that would have to do with the internal implementation of whatever piece of code was carrying out the actual selector operation. In jQuery, you could study the code to find out what any given version was doing, but since this is illegal HTML, there is no guarantee that it will stay the same over time. From what I've seen in jQuery, it first checks to see if the selector is a simple id like #a and if so, just used document.getElementById("a"). If the selector is more complex than that and querySelectorAll() exists, jQuery will often pass the selector off to the built in browser function which will have an implementation specific to that browser. If querySelectorAll() does not exist, then it will use the Sizzle selector engine to manually find the selector which will have it's own implementation. So, you can have at least three different implementations all in the same browser family depending upon the exact selector and how new the browser is. Then, individual browsers will all have their own querySelectorAll() implementations. If you want to reliably deal with this situation, you will probably have to use your own iteration code as I've illustrated above.
jQuery's id selector only returns one result. The descendant and multiple selectors in the second and third statements are designed to select multiple elements. It's similar to:
Statement 1
var length = document.getElementById('a').length;
...Yields one result.
Statement 2
var length = 0;
for (i=0; i<document.body.childNodes.length; i++) {
if (document.body.childNodes.item(i).id == 'a') {
length++;
}
}
...Yields two results.
Statement 3
var length = document.getElementById('a').length + document.getElementsByTagName('div').length;
...Also yields two results.
What we do to get the elements we need when we have a stupid page that has more than one element with same ID? If we use '#duplicatedId' we get the first element only. To achieve selecting the other elements you can do something like this:
$("[id=duplicatedId]")
You will get a collection with all elements with id=duplicatedId.
From the id Selector jQuery page:
Each id value must be used only once within a document. If more than one element has been assigned the same ID, queries that use that ID will only select the first matched element in the DOM. This behavior should not be relied on, however; a document with more than one element using the same ID is invalid.
Naughty Google. But they don't even close their <html> and <body> tags I hear. The question is though, why Misha's 2nd and 3rd queries return 2 and not 1 as well.
If you have multiple elements with same id or same name, just assign same class to those multiple elements and access them by index & perform your required operation.
<div>
<span id="a" class="demo">1</span>
<span id="a" class="demo">2</span>
<span>3</span>
</div>
JQ:
$($(".demo")[0]).val("First span");
$($(".demo")[1]).val("Second span");
Access individual item
<div id='a' data-options='{"url","www.google.com"}'>Google</div>
<div id='a' data-options='{"url","www.facebook.com"}'>Facebook</div>
<div id='a' data-options='{"url","www.twitter.com"}'>Twitter</div>
$( "div[id='a']" ).on('click', function() {
$(location).attr('href', $(this).data('options').url);
});
you can simply write $('span#a').length to get the length.
Here is the Solution for your code:
console.log($('span#a').length);
try JSfiddle:
https://jsfiddle.net/vickyfor2007/wcc0ab5g/2/

Chained method calls doesn't work on original nor cloned element? [duplicate]

This question already has answers here:
Chained method calls doesn't work on original nor cloned element, why?
(2 answers)
Closed 6 years ago.
I have the following HTML:
<input type="text" id="condition_value_1" style="display: none" />
<button id="showme">Make Select2</button>
<button id="clickme">Make Input</button>
Then take a look to the following jQuery:
$(function() {
var cond1 = $('#condition_value_1');
var cloned_cond1 = cond1.clone();
var cond1_select = '<select name="condition_value_1" id="condition_value_1" multiple="multiple"><option></option><option value="1">Opt1</option><option value="2">Opt2</option></select>';
$('#showme').click(function() {
cond1.removeAttr('style').replaceWith(cond1_select).select2({
placeholder: 'Select choice'
});
});
$('#clickme').click(function() {
if ($('#condition_value_1').hasClass('select2-hidden-accessible')) {
$("#condition_value_1").select2('destroy');
}
$('#condition_value_1').replaceWith(cloned_cond1).removeAttr('style');
});
});
You can try the code above here.
Now as soon as you click on #showme you should remove the attr style, replace the original element with the given one and turn it into a Select2, the last part isn't working.
In the other side if you click on #clickme you should destroy the previous Select2 replace the #condition_value_1 with the cloned element and remove the attr style because the cloned has that attribute but this is not working either.
The idea is to switch between elements and turn on/off properties on demand.
Maybe I am missing something here but I am not sure what. Could any help me here?
Note: I've deleted my previous post to avoid confusions, apologies about that!
The problem is because replaceWith() returns the original jQuery object which now contains no elements as you replaced them.
In your logic structure this means you can't chain from those elements and need to start calls on the appended elements, like this:
var $cond1 = $('#condition_value_1');
var $cloned_cond1 = cond1.clone();
var cond1_select = '<select name="condition_value_1" id="condition_value_1" multiple="multiple"><option></option><option value="1">Opt1</option><option value="2">Opt2</option></select>';
$('#showme').click(function() {
$cond1.replaceWith(cond1_select);
$('#condition_value_1').select2({
placeholder: 'Select choice'
});
});
$('#clickme').click(function() {
if ($('#condition_value_1').hasClass('select2-hidden-accessible')) {
$("#condition_value_1").select2('destroy');
}
$('#condition_value_1').replaceWith($cloned_cond1);
$cloned_cond1.removeAttr('style');
});
If you do the following:
$("#div").replaceWith(".item2")
The object returned by the replaceWith method is the original set of objects. This because they might be replaced, but they still exists. Maybe not in the DOM but outside of it. Therefor you might want to do something else with it after replacement.
Therefor you need to make a seperate Javascript call where you select the right element and call the removeAttr and select2 function.
The .replaceWith() method, like most jQuery methods, returns the jQuery object so that other methods can be chained onto it. However, it must be noted that the original jQuery object is returned. This object refers to the element that has been removed from the DOM, not the new element that has replaced it.
http://api.jquery.com/replacewith/

dynamic add and delete input field jquery 1.10

Hi I'm having trouble with this fiddle I found somewhere it works perfectly on 1.4 jquery
but I am using 1.10 version of Jquery.
I notice that live method is deprecated in 1.10 so I user on to replace live but still not doing as it supposed to do.
my toubled fiddle is here
I used to code back end so please spare me, could anyone help me with this?
You should use parent selector $("#content") with on() and use prop() to make button disable like,
$("#content").on("click", ".plus",function(){
var parent = $(this).closest(".iteration");// use closest
parent.append('<input type="text" value="Create a task for this iteration" />');
var nbinput = parent.find("input[type='text']").length;
if(nbinput == 5)
parent.find(".plus").prop("disabled",true);// use prop()
if(nbinput > 0)
parent.find(".moins").prop("disabled",false);
});
$("#content").on("click",".moins", function(){
var parent = $(this).closest(".iteration");// use closest
parent.children("input").last().remove();
var nbinput = parent.find("input[type='text']").length;
if(nbinput < 5)
parent.find(".plus").prop("disabled",false);// use prop()
if(nbinput == 0)
parent.find(".moins").prop("disabled",true);
});
Demo
To make on work like you wanted you need to specify an element that won't be changed as the main selector and pass the dynamic element selector as the second parameter:
$("#content").on("click", ".plus", function(){
Also, there was an error with the code that disabled the plus and minus buttons, instead of setting the disabled attribute to empty you want to remove it completely:
parent.find(".moins").removeAttr("disabled");
Finally, changed the .parent().parent().parent('.iteration') to
$(this).closest(".iteration");
As this is much simpler and less likely to be broken by changes to your html.
http://jsfiddle.net/infernalbadger/L3s3w/3/

Disable a text area by it's class (not id)

I have 2 text area's that are generated automatically, and I need to use JavaScript to disable both when the page has loaded. The catch is because they are generated automatically I can't give them an ID because they would both have the ID - a big no.
Attempted Javascript:
document.getElementByClassName('option_window-size').disabled=true;
I know this works because if I change getElementbyClassName to ID then it will work if I give the text areas the ID as well. But as I say it needs to work off class. Also it can't work of the Name attribute because that is automatically generated per product and per page...
I have tried this but it just doesn't work and I can't figure out why not because it should as the only thing I have changed is from ID to CLASS
Text Areas
<textarea name="willbeautogenerated" class="option_window-size" cols="40" rows="5">willbeautogenerated</textarea>
Additional note: I have tried to count and assign them different IDs using PHP but it gets far to complex. Also it is only these two that need disabling, thus I can't just disable all text area's on the page.
I know this works because if I change getElementByClassName to ID then it will work if I give the text areas the ID as well. But as I say it needs to work off class.
getElementsByClassName returns a NodeList rather than a Node itself. You'll have to loop over the list, or if you expect just 1 item, choose index 0.
var nodes = document.getElementsByClassName("option_window-size"),
i = 0, e;
while (e = nodes[i++]) e.disabled = true;
jQuery makes this pretty simple:
$(".selector").prop("disabled", true);
ALTHOUGH! It should be noted that this note appears on the man pages for $.prop() and $.attr():
Note: Attempting to change the type property (or attribute) of an input element created via HTML or already in an HTML document will result in an error being thrown by Internet Explorer 6, 7, or 8.
This doesn't apply directly to your question, but you are changing prop/attrs on an input element, so be aware.
But it's still possible with plain old JS:
var els = document.getElementsByClassName("selector"); // note: Elements, not Element
for(var e = 0; e < els.length; e++)
{
els[e].disabled = true;
}
getElementsByClassName returns an NodeList, you just have to iterate over each element within.
You can use class selector,
$('.option_window').attr('disabled', true);
OR
$('.option_window')[0].disabled = true;
With Jquery you can do:
//make sure to use .prop() and not .attr() when setting properties of an element
$('.option_window').prop('disabled', true);
Disable textarea using jQuery:
$('.option_window-size').attr('disabled', true);
your missing a s in elements and the index where the element is like [0], for the first element.
document.getElementsByClassName('option_window-size')[0].disabled=true;
or
document.getElementsByName('willbeautogenerated')[0].disabled=true;
Disabel texarea using .prop() methode in jquery...
$('.option_window-size').prop('disabled', true);
You can use CSS:
.option_window-size{display:none;}

Jquery binding to keyup

I'm having some problems binding to the keyup event of a textarea control. I'm trying the below
var shortDescInput = $('nobr:contains("Short Description")').closest('tr').find($('textarea[title="Short Description"]'));
// this doesn't work
shortDescInput.bind('keyup', function () {
countShortDescChars();
});
// Nor this
shortDescInput.keyup(function () {
countShortDescChars();
});
Am I missing something here that's really obvious? This is working for other controls, for example binding events to radiobuttons. I've checked and I'm defiantly selecting the right textarea with
var shortDescInput = $('nobr:contains("Short Description")').closest('tr').find($('textarea[title="Short Description"]'));
I just never seem to get the keyup event....
find($('textarea[title="Short Description"]')) is highly inefficient. For your purposes, find should take a selector as it's argument.
When you pass in a jQuery object to find, jQuery first queries the DOM from the top and finds all elements that match that selector. Then, find loops through all of these results until it finds one that matches the specified parents.
You should, instead, use:
find('textarea[title="Short Description"]')
Also, use .on instead of .bind. .bind is set to be deprecated in future releases for it's inefficiency.
shortDescInput.on("keyup", countShortDescChars);
And the revised code:
$(function () {
var shortDescInput = $('nobr:contains("Short Description")').closest('tr').find('textarea[title="Short Description"]');
shortDescInput.on("keyup", countShortDescChars);
});
To verify that a selector is working use .length with a console.log() or old fashioned alert() :
var shortDescInput = $('nobr:contains("Short Description")').closest('tr').find('textarea[title="Short Description"]');
alert(shortDescInput.length);
You can also go step by step to identify the one not returning anything :
alert($('nobr:contains("Short Description")').length);
alert($('nobr:contains("Short Description")').closest('tr').length);
alert($('nobr:contains("Short Description")').closest('tr').find('textarea[title="Short Description"]').length);
Second try. using .on() instead of .bind() :
shortDescInput.on('keyup',function(){countShortDescChars();});
So I played along with your fiddle and...
There IS something wrong with your selector.
First I remove the script tags from the js part.
then remove the script tag in your html cause it broke the fiddle.
Switched to jQuery 1.8.0 cause MooTools is not what we want.
added shortDescInput = $('textarea'); after your giant selector, event is triggered!
Added again shortDescInput = $('textarea'); in your function to make the counter work.
So again, let's now try to figure why your selector is not working :-)
Edit:
Found it!
I replaced your .closest() with .parent().next() because I kind of think .closest() was targeting the parent .
var shortDescInput = $('nobr:contains("Short Description")').parent().next().find('textarea[title="Short Description"]');
The problem is that at least in the fiddle, the <tr> wasn't in a <table>and so it was removed from the DOM by the browser. Wrapping the <tr> in a <table> made the fiddle work.
Demo: http://jsfiddle.net/kNkXE/9/

Categories