Why does renaming an HTML element remove its events? - javascript

I've noticed that when I change the ID or name of a form element via JavaScript, the jQuery events that were tied to it are no longer there. I've tried this with Firefox 17 and IE 10. Is this by design? And if so, is there any way to prevent it?
UPDATE: Please check http://jsfiddle.net/qHH7P/2/ for an example.
I'm adding the button to remove the row via jQuery. When I remove the first row, I give the elements in the second row a new name and ID. Then the remove button for that remaining row doesn't fire the event anymore. I need to rename the elements because ASP.NET MVC expects a certain naming convention for the collection of objects when binding. That's why I need to rename them with a "0" instead of "1". I'm doing my rename with
var regexpattern = new RegExp("WorkspaceQuestionSets\\[.+\\]", "g");
$(this).html($(this).html().replace(regexpattern, "WorkspaceQuestionSets[" + index + "]"));
var regexpattern = new RegExp("WorkspaceQuestionSets_.+__", "g");
$(this).html($(this).html().replace(regexpattern, "WorkspaceQuestionSets_" + index + "__"));
I just realized that I'm not even renaming the buttons. So it makes even less sense that the event is gone. But if I comment out the code to rename the elements, the event remains.

You are rewriting the HTML. .html() returns a string, which you then modify and set again. The browser will parse that HTML string and create new DOM elements from it. In this process, your destroying DOM elements and consequently loose the event handlers you bound to them before.
I just realized that I'm not even renaming the buttons. So it makes even less sense that the event is gone.
You are destroying and recreating every single element that is inside this (each row I assume), no matter whether you modified its HTML representation or not.
You have to possibilites to solve this:
Use event delegation: Instead of binding the event handlers directly to the elements, bind them to an ancestor, which will always exist. Read more about event delegation in the .on *[docs] documentation, section Direct and delegated events.
Don't rewrite the HTML. Select the elements whose name attribute you want to modify and modify it. For example:
var name_exp1 = /WorkspaceQuestionSets\[.+\]/g;
var name_exp2 = /WorkspaceQuestionSets_.+__/g;
sourceEle.closest(".table").find(".row.set").each(function(index) {
// Edit the name attribute of all `select` and `input` elements
$(this).find('select, input').prop('name', function(i, name) {
if (name_exp1.test(name)) {
return name.replace(
name_exp1,
"WorkspaceQuestionSets[" + index + "]"
);
}
else if (name_exp2.test(name)) {
return name.replace(
name_exp1,
"WorkspaceQuestionSets_" + index + "__"
);
}
return name;
});
});
In JavaScript, forget the HTML and work with the DOM.

This does not happen to me with plain JavaScript in Chrome 24.
Here's what I typed in the JS console:
document.write("<button id='bb'/>")
//undefined
var bb=document.getElementById('bb')
//undefined
bb
//<button id=​"bb">​</button>​
bb.addEventListener('click',function(){alert('hi');});
//undefined
bb
//<button id=​"bb">​</button>​
bb.id
//"bb"
bb.id="qq"
//"qq"
bb
//<button id=​"qq">​</button>​
document.getElementById("bb")
//null
document.getElementById("qq")
//<button id=​"qq">​</button>​
And importantly clicking the button made the event trigger both before and after the assignment to bb.id.
Also in Firefox 17 I didn't quite see that happening with plain JavaScript. I was on the page: http://start.ubuntu.com/12.04/Google/?sourceid=hp
And in the console here's what I issued:
[19:29:41.261] var bb=document.getElementById('sbtn')
[19:29:41.267] undefined
[19:29:45.501] bb.addEventListener('click',function(){alert('hi')});
[19:29:45.507] undefined
[19:29:48.292] bb.id
[19:29:48.298] "sbtn"
[19:29:59.613] bb.id="sbttttttn"
[19:29:59.619] "sbttttttn"
The alert still occurred after the id was changed.
This must be specific to either IE, which I cannot try, or the way jQuery sets up events or handles ID changes (perhaps invisibly destroying the object?).

Related

Javascript - jquery - selecting multiple change events

I am trying to create some calculations using a wordpress forms plugin that a customer insists on using (due to integrations with other plugins). I have created the code which fires on an onchange field event. I want to be able to trigger the code from multiple form fields events however.
See below - item meta[76] onchange fires the scrip. How can i modify this to enable other fields to fire the script as well (ie item meta[34] || item meta[43]). Is it possible in the select one statement? The strange IDs are due to the plugin. Thank you in advance.
<script type="text/javascript">
jQuery(document).ready(function($){
$('select[name="item_meta[76]"]').change(function(){
var val1 = $("select[name='item_meta[76]']").val();
var myArea = $("#field_f4wns").val() * $("#field_ymo4z").val();
var myTotalArea = $("#field_xuyzx").val();
var myToolPrice = "#field_8v27o";
var myBoardPrice = "#field_ku0w8";
if (val1 == "One Layer"){
if (myArea > 0 && myArea < 5000) {
$(myToolPrice).val(20);
}...... and many other calculations follow.
Use a delegate listener with an appropriate selector and make use of the this variable.
You have one function, and N elements that need the logic performed. Don't attach a listener to every single element. That means lots of extra memory and code tracking (and potential source of bugs) for your web app. Instead, use a delegate listener:
// "bad" (not wrong; could be better)
jQuery('select[name=a],select[name=b],sele...').change(...
// "good" (the specific scenario dictates best)
jQuery('body').on('change', 'select[name=a],select[name=b],sele...', function (evt) {
The first example attaches a copy of the function to the change event of every single element matched in the selector. In the example above, any <select name="a"> or <select name="b">. The second example attaches a single listener to the change event on the body element, but only actually executes if one of the selectors matches. Same effect, much less memory, much less churn for your JS engine, and presumably now only a single place for you to worry about generating your comma-separated selector.
Understand how jQuery abuses the this keyword. Whenever jQuery calls back to an event listener, it sets the this variable to the actual element of the event. So, in your code above, you have this:
$('select[name="item_meta[76]"]').change(function(){
var val1 = $("select[name='item_meta[76]']").val();
That is wholly too much work. Consider:
$('select[name="item_meta[76]"]').change(function(){
var val1 = $(this).val();
With this last tidbit, you should be able to write a single, more general, delegate listener that works for you and is easier to alter as appropriate.
You can define multiple selector in for calling function on event as:
$('selector1','selector2','selector3',...).change(function(){
// your code
});
Or you can give common class to all the element and define the jQuery function as:
$('.common_class').change(function(){
// your code
});
Additionally, inside the function, you are using that selector again to get value:
var val1 = $("select[name='item_meta[76]']").val();
So in this case, you can replace your static selector with this keyword as:
var val1 = $(this).val();

Checking DOM element node Types

This exercise will be to write a helper script that could theoretically be used on any web page to help identify the elements that contain text, simply by including your JavaScript file!
Define the script in a source file called highlightNodes.js.
This script should navigate every element in the DOM, and for each element in the body determine whether it is a element ( type 3) or not.
Now add to your script code to create a new child node for every non-text node encountered. This new node should take on the class " hoverNode" and innerHTML equal to the parent tag name. Define appropriate styles for that CSS class.
Now add listeners so that when you click on the newly created nodes, they will alert you to information about the tag name, so that when a node is clicked a pop- up alerts us to the details about that node including its ID and innerHTML.
The example picture that the teacher took it really poor quality, but I think you will still be able to see what's going on.
http://imgur.com/7XKs4U5
Here's the code I have so far: https://jsfiddle.net/vuku3qdu/2/
window.onload = function () {
var bodyNodes = document.getElementsByTagName("*");
for (var i = 0; i < bodyNodes.length; i++) {
if (bodyNodes[i].nodeType != 3) {
newChildNode = document.createElement("p");
newChildNode.className = "hoverNode";
newChildNode.innerHTML = bodyNodes[i].tagName;
bodyNodes[i].appendChild(newChildNode);
newChildNode.addEventListener("click", function () {
alert("Tag Name: " + newChildNode.tagName + " innerHtml: " + newChildNode.innerHTML);
});
}
i++;
}
};*
For some reason, this isn't working on Jfiddle correctly. It displays highlight nodes locally. My issue is that it isn't evaluating the nodeTypes properly. It is saying that they are all "1" for NodeType. I know this isn't correct and it's giving me too many of these highlight nodes. Also, it is skipping the "label" elements that are embedded within the Fieldset.
My last problem... I can't get the Highlight nodes to output the proper newChildNodes.innerHTML in the listener functions. It always gives me undefined.
I've logged all of the steps through the console and I know that it is evaluating the node types wrong, but I cannot figure out the correct command to do so.
Thanks Guys!

Prevent element from updating - JQuery

I select an element of the page:
$mainSection = $('#main');
then I add more Elements via AJAX into the <div id="main"></div> element. Next time I call $mainSection, the newly added elements are also in it. But I don't want that. I would like that the variable $mainSection only has the content in it from the initial rendering of the page. I can't find a way to prevent jQuery from updating.
I tried this:
$(document).ready(function(){
$mainSection = $('#main').clone(true);
Then I add new elements to #main and then I check if they get found via:
$foundElement = $($mainSection + ":not(:has(*)):not(script):contains('"+newlyAddedContent+"')");
On page load, they are not there. But after I add them, they get found.
I also tried:
$mainSection = $('#main').html();
$mainSection = $($mainSection);
didn't work also.
Here is a jsFiddle to illustrate my point:
http://jsfiddle.net/VEQ2E/2/
The problem is somewhere burried in this line:
$foundElement = $($mainSection + ":not(:has(*)):not(script):contains('"+newlyAddedContent+"')");
It somehow always searches through the whole document, when I do it like this.
You can use .clone(true):
$mainSection = $('#main').clone(true);
It every time takes the clone/copy of the initial state of this div.
Note:
.clone( [withDataAndEvents ] [, deepWithDataAndEvents ] )
withDataAndEvents : Boolean (default: false)
deepWithDataAndEvents : Boolean (default: value of withDataAndEvents)
A Boolean indicating whether event handlers and data for all children of the cloned element should be copied.
Your problem was not that your clone was getting changed, but rather the selector you were using to try finding something within the clone. Your code was like this:
$($mainSection + ":not(:has(*)):not(script):contains('"+newlyAddedContent+"')");
Concatenating an object with a string will turn the object into a string, simply "[object Object]", then your selector will just look at the ":not(:has..."
Instead, you should use filter:
$foundElement = $mainClone.filter(":not(:has(*)):not(script):contains('world')");
This will now only look within your $mainClone for items matching that filter.
JSFiddle
You can take several approaches:
Cache the contents of #main before the update. This gives you just the contents of the element without the element.:
mainsectionContents = $('#main').html();
Or Cache a copy of #main before the update. This will give you the content together with the element, and depending on whatever else you may want to copy feel free to check the api docs:
$mainsectionCopy = $('#main').clone();

.addClass() not working

This is my first time working with .addClass().
In my project, I need to display notifications on a dummy phone screen (an image of iPhone). A notification has a title and some description. This title and description is coming from a form on the same webpage. To compose this notification, I am doing:
var notificationText = $('#title').val().addClass('title') + plainText.addClass("description");
However, I am getting an error:
TypeError: $(...).val(...).addClass is not a function
What am I doing wrong here?
UPDATE:
So, as per the overwhelming requests, I did:
var notificationText = $('#title').addClass('title').val() + plainText.addClass("description");
However, I am getting an error:
Uncaught TypeError: Object sss has no method 'addClass'
jsFiddle
UPDATE 2: I do not need to style the description, so I removed the class related to it. Please see my updated fiddle. Now the problem is that the text in title is getting bold instead of the one copied in #notifications. It is not getting styled as per the CSS.
So many answers in so little time... sigh
I gathered what I think you wanted. Try this one:
JSFiddle: http://jsfiddle.net/TrueBlueAussie/7b3j2/13/
$(document).ready(function(){
CKEDITOR.replace( 'description' );
$('#title').focus();
$('form').submit(function(event){
event.preventDefault();
var html=CKEDITOR.instances.description.getSnapshot();
var divEle=document.createElement("DIV");
divEle.innerHTML=html;
var plainText=(divEle.textContent || divEle.innerText);
var $title = $('<span></span');
$title.addClass('title');
$title.text($('#title').val());
var $desc = $('<span></span');
$desc.addClass('description');
$desc.text(plainText);
$('form').append($title);
$('form').append($desc);
});
});
You can obviously chain some of the span operations, but I left them readable for now. Shorter version would look like:
var $title = $('<span></span').addClass('title').text($('#title').val());
var $desc = $('<span></span').addClass('description').text(plainText);
$('form').append($title).append($desc);
As you probably know by now, but for completeness, the initial errors were the result of trying to apply jQuery methods to string objects. This solution creates new jQuery span objects that can then be styled and appended to the form.
You are trying add class to a value, which is definitely is not a jQuery object
Try this instead:
$('#title').addClass('title').val()
addClass can only be performed on jQuery objects and returns a jQuery object - that's what makes it chainable. You can't add a class to a string.
So, in this code, there are actually two mistakes:
1) plainText.addClass - plainText is a string, and not a jQuery object. You must add the class to the element you created (in your case, the divEle element), but, since addClass only works with jQuery objects, you must convert your div to a jQuery element first. You can accomplish this by doing the following:
$(divEle).addClass('description');
2) addClass returns a jQuery object, so you can't concatenate it with a string.
EDIT: Just realized that you're appending notificationText (which is a string) to the DOM. You must convert it to a div and add the div to the DOM.
jsFiddle: http://jsfiddle.net/7b3j2/17/
Mistake done by you:
<div id="title"><div>
$('#title').val().addClass('title')
->Now here $('#title').val() will give that particular element value.
->$('#title').val().addClass() you are adding class to that value.
Use this:
$('#title').addClass();
As you cannot add class to element's value.
You should addClass to particular element as addClass internally will add attribute class to that element.
So finally solution becomes:
$('#title').addClass('title').val()
For adding a class, you have to use
$('#title').addClass('title');
If you want to get the value, you can use
$('#title').addClass('title').val()
While addClass and val() are both methods on the jQuery object, val() is not chainable like addClass is. When you do $('#title').val() you aren't returning the object, you're only returning the string value of the element.
Use this instead:
$('#title').addClass('title');
And if you still need to get the value:
$('#title').addClass('title').val();
The reason why plaintext is producing an error is because you're trying to use the jQuery addClass method on a DOM node that has been natively created with document.createElement("DIV");. This will not work. To get it to work you either need to to define your new element with jQuery:
var divEle = $('<div></div>');
and then add the class:
divEle.addClass('description');
Or use the native classname method to add the class to the DOM node:
divEle.className = divEle.className + " description";
Try putting addClass first
$('#title').addClass('title');
Update
To get the code fully working you should split up the line like so.
var notificationText = $('#title').val() + ' ' + plainText;
$('#title').addClass('title');
$(plainText).addClass("description");
Fiddle
Final Update
So what we actually want to do here is:
get the values of the content
append them on submit and style the appended text
Example
// Get the text.
var notificationText = $('#title').val() + ' ' + plainText;
// Append to form.
$('form').append('<span class="summary">' + notificationText + '</span>');
// CSS styling
.summary {
display:block;
font-weight: bold;
}
See Fiddle
Considering #title is the id of the element.
You can directly need to add classname to it.
$('#title').addClass('className');
where className is the name of the class.
because you are trying to add class over value instead of element.
$('#title').val().addClass('title') //it is wrong
replace it with:
$('#title').addClass('title')
if plainText is not an element object you initialize by
var plainText = $('#anotherId');
will also cause this error.

'div_element' is null or not an object in IE7

Here I have get one error in JavaScript div_element is null or not an object.
I have given my code below:
function showLoading(id) {
div_element = $("div#" + id)[0];
div_element.innerHTML = loading_anim; // Error in this line
}
When I am debugging my script but it's working fine in other browsers including IE 8, but it's not working in IE 7. I don't understand what exact issue occur in this script.
First of all, you dont need to put a tag name infront of the jQuery, unless you have other elements with exact same id on other elements, in other pages.
Next, your statement div_element.innerHTML = loading_anim; is correct. So, the only explanation is that, there is no element with that ID, in the DOM.
Finally, since you are usign jQuery already, no need to mix up native JS and jQuery to create a dirty looking code.
function showLoading(id) {
div_element = $("#" + id);
console.log(div_element); //check the console to see if it return any element or not
div_element.html(loading_anim);
}
I think you don't select anything with your jquery selector (line 2)
try to display
id
"div#" + id
$("div#" + id)
$("div#" + id)[0]
You can use firebug javascript console or a simple alert like this:
alert($("div#" + id)[0]);
And see if you must id or class on your div ( use # or . selector)
I suppose, that nic wants to display some loader GIF animation, I confirm, that nic must use jQuery .html() method for DOM objects, and tusar solution works fine on IE6+ browsers. Also (it is obvious but anyway) nic must assign a value to loading_anim variable in script, lets say: var loading_anim = $('#loader').html(); before assigning its value to div_element.
Use .html() for jQuery objects. innerHTML work for dom objects, they wont work for jQuery objects.
function showLoading(id) {
div_element = $("div#" + id);
$(div_element).html(loading_anim); // Provided `loading_anim` is valid html element
}

Categories