Event handler stuck after changing body.innerHTML - javascript

I would like to change some text in my body html with code below. Replacing works fine, but after that it seems to stuck, can't get out of function, so next parts of my script cannot be executed.
Nothing will happen after clicking .another-button. I think problem is with line that change body.innerHTML because when I delete this line everything works fine.
$(".do-replace-button").on("click", function () {
//some different code
document.body.innerHTML = document.body.innerHTML.replace(regExp, newWord);
}
$(".another-button").on("click", function (event) {
//some code that won't execute
}

You are effectively replacing the innerHTML of your <body> with something else, which is a string, the result of your replace(). That string is parsed and built into new DOM elements and the old ones are getting replaced by the new ones.
Hence, the events you bound on the old ones are gone, because the elements are gone. One way to solve this would be to:
a) bind on document itself:
$(document).on('click', '.do-replace-button', function() {
// whatever...
})
b) find another, less aggressive way to achieve whatever it is you are achieving with that replace.
Do note it's not only your events that get removed, but any event bound by any library/plugin you might have loaded and already initialized by the time your script runs. I strongly recommend against running replace on innerHTML, especially on <body>.

The call to document.body.innerHTML and the right hand assignment that comes after it is completely replacing the html inside the document, or more specifically, the parser is building a completely new node tree inside the body so all of your HTMLElement nodes that may have previously had event listeners assigned to them are no longer there. I recommend you go another route of only replacing the the HTML that matches your regex instead of the entire HTML contents of the body element. Or you could have a function that is called inside the first event-listener callback that fires after the replacement of the body HTML, that will re-initialize the event listeners.

Related

Preserving event handlers when replacing html in jQuery

I've written a jQuery function as:
function linkify(parent, regex, url) {
var children = parent.children('main,article,div,p,li,ul,ol,span');
const re = RegExp(`(${regex})(?![^<]*>|[^<>]*<\/)`, 'ig');
children.each(function() {
linkify($(this), regex, url);
$(this).html(
$(this)
.html()
.replace(re, `<a rel="nofollow" href="${url}">$1</a>`)
);
});
}
It's a recursive function that goes down the DOM tree, and finds occurrences of words in the body text, and converts them into a link if found.
It works great. The problem I have is that when I do:
$(this).html(
$(this).html()
)
(i.e. copying the HTML), it discards all bound event handlers. This means in a real page, it can break things like JS menus.
How can I copy the HTML code, while preserving anything bound to it?
PS I have checked other similar posts, however this is different, in that this code will be included in an arbitrary page- I need to develop a general solution, not one specific to particular page.
Just use JavaScript to copy the HTML and not jQuery for this specific action

Where could `appendTo()` be usable with an `htmlString`?

The API docs for appendTo list the method being able to select an HTML string for a target.
However, there seems to be no use to this since the set still includes the original elements, and the HTML string seems not to have been added anywhere in the DOM nor do I see a circumstance where it could be available.
var b = button.appendTo('<div>').appendTo('body');
b is a button, and yet it is not wrapped in a div or anything.
You can see this at http://jsfiddle.net/0dgLe5sj/
Where would it be useful to append to a HTML string (which doesn't yet exist on the page)?
appendTo() returns the item being appended.
So your code is:
var btn = button.appendTo('<div>');
btn.appendTo('body');
As you can see, you move it inside a div, then immediately move it inside the body. So you when you look at it at the end, it's inside the body.
Perhaps you meant:
var b = button.appendTo($('<div>').appendTo('body'));
which will append a div to the body and then append the btn to that div.
Updated fiddle: http://jsfiddle.net/0dgLe5sj/8/
or, if you wanted to add to the div first:
var b = button.appendTo("<div>");
b.parent().appendTo("body")
but if you combine it into a single line, you can't get the button back into the variable using .appendTo as you're adding the div to the body so you're going to get the div or the body back.
To address the 'where would this be useful part':
Being able to create detached DOM elements is extremely useful for parsing HTML strings and can also be used to 'batch' up some changes without forcing page redraws between.
Moving one button to a detached div and the back to the body doesn't have a lot of point, but it proves the principles.

Race condition when hooking appendChild

I've encountered a really weird race condition when hooking appendChild.
consider this code:
var RealAppend = IframeWindow.Element.prototype.appendChild;
IframeWindow.Element.prototype.appendChild = function(){
RealAppend.apply(this, arguments); //some more code here --- >};
I'm hooking some iframe appendchild that's why I'm doing "IframeWindow.Element.prototype.appendChild" with the IframeWindow as the iframe content window.
Now after I'm hooking the appendchild then I start to append nodes to the iframe , one of the nodes is a simple div element with id="somediv" , and another node is a javascript element and as soon as the javascript is executed it will use document.createElement to create an iframe and set its attributes and than it will execute document.getElementById('somediv').appendChild(CreatedIframe);
which will append the created iframe to the "somediv" , my goal here is to intercept the last appendchild and hook the appendchild of that iframe also.
now a first I thought that maybe I need to actually override the appendchild like this:
someDiv.appendChild = function(){.....
but after more tests i can see that sometimes my code does succeed and hooks the last iframe and sometimes the SomeDiv.appendChild is the native code and my code fails.
Any ideas why?
EDIT:
I think its might related to the fact that i'm using
document.createRange().createContextualFragment(strHTML);
}
to parse html as string and then append the nodes to the iframe so maybe the div (somediv) is getting a clean appendChild from there? but why sometimes it does work and sometimes not?
one other thing is that after the page loads and i'm using dev tools to get the somediv.appendchild i'm getting the hooked code every time so the problem happens only when the javascript code executes
So i managed to figure out what was the problem, so if anyone will ever encounter this here is the solution:
When i was parsing the html string to nodes so i can append them to the iframe i was first using the "DOMParser" API and i missed the fact that the DOMParser uses the document from the caller contentWindow so instead of calling DOMParser like this:
new DOMParser();
i needed to call it like that:
new HookedIframeContentWindow.DOMParser();

Why is replace() disrupting onclick()?

I am using replace to search my dynamically loaded webpage for this symbol Æ and replace it with ®. I found the code to do so in this question:
Find and replace specific text characters across a document with JS
$("body").children().each(function () {
$(this).html( $(this).html().replace(/Æ/g,"®") );
});
However, after I added this code, this function stopped working.
document.getElementById("backToClasses").onclick = function() {
console.log("Clicked");
};
Can anyone tell me why this might happen?
In the second chunk of code (which I assume runs first) you are locating an element in the DOM and assigning a value to a property of it.
The first chunk of code goes over the DOM and converts large chunks of it into HTML source code, it then modifies that source code, then it generates new DOM elements from it and replaces whatever was there before with them.
So the element with the ID backToClasses:
Gets a click handler
Is converted to HTML
Is destroyed
Is replaced by a new version created from its old HTML
The click handler was only ever on the DOM, so the new element doesn't have it.
If you are going to take this approach, then you should look at looping over just the text nodes in the document and dealing in text rather than HTML. To do that you'll need to recursively loop over the DOM and test the node type of each element.
It would be better to fix the underlying problem that you are hacking around though. It is almost certainly down to an incorrectly specified character encoding somewhere.
The W3C has some material on character encodings that might be helpful.
By doing this:
$("body").children().each(function () {
$(this).html( $(this).html().replace(/Æ/g,"®") );
});
You re-create all the HTML elements, so any events that you might have bound before are lost. Don't use the .html() function to replace text. And still, I'm not sure that this is the best way to replace a character.
This replace should be done server-side, not client side. By doing it client-side (in JavaScript) you can get into different problems like SEO (Google indexing your site with your badly encoded characters). If the characters are like this inside a file, simply replace them in that file, make sure to save the file with the right encoding.

Fire ajax call on onclick and div

I am attempting to fire off an AJAX call based on the onclick event for a google map integration. The info_window_content function seen here: http://jsfiddle.net/6xw2y/ is the call to create the divs that reside within the map itself.
The "v" variable does in fact contain a store_id. So in the opening line of that function, it has the following:
var info_window_string = "<div class='maps_popup' id="+v.id+">";
Now I have an onclick event that I have duplicated and modified. The first onclick event works just fine and refreshes the panel as it should. The second onclick event doesn't work and the code for that is below:
$("#div").click(function(){
var store_id = $(this).find("div").attr("id");
var pathname = "ajax=1&store_id="+store_id+"&action=get_nearby_stores&distance="+distance+"&lat="+lat+"&lng="+lng+"&products="+$('#edit-products').val();
$("#pocp_content").load("file1.php?" + pathname);
});
That doesn't seem to work. I've also tried changing the div tag to be like this:
$("div").click(function(){
Which still doesn't work. As an added side hint. At one point I was able to get it to refresh but it was passing map-container as the store_id, instead of the id itself.
What am I missing here?
I agree with Joke_Sense10,
but I think you're probably not binding the event to the right DOM element.
Try to open up the developer console in your browser (while being on the side you develop this code for), and enter $("#div") to see if the element it returns is the one you expect. You can also use console.log($("#div")) in the code for that.
answer in comments
For a larger number of elements, always use .on() method as the latter will bind an single event listener on one of the topmost nodes in the DOM tree.
$(document).on("click","#"+v.id, function(){

Categories

Resources