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
Related
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.
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.
I'm trying to develop a script that will take user submitted HTML, loop through it to identify matching tags, make adjustments to those matched tags, and then spit out the resulting HTML as plain text that the user can copy. The end goal here is to replace all href's in a submission and replace them with different URL's.
So for example, this:
Link A
<a data-track="false" href="http://example.com/">Link B</a>
Link C
Becomes this:
Link A
<a data-track="false" href="http://example.com/">Link B</a>
Link C
My first thought was to take the submitted HTML from the <textarea> field and put it in a variable. At this point the HTML becomes a string and I was going to loop through it with a regex to find matching tags. My issue was that I needed to find all <a> tags that did NOT include the attribute data-track="false". And as far as I can tell that's impossible with regex since each link isn't going to be on its own line.
My second thought was to loop through it using jQuery where I could use something like this:
$("a:not([data-tracking='false'])");
But I can't use jQuery like this on a string, right? It needs to be in the DOM.
I'm unsure of the best way to go about doing this. Maybe another language would prove helpful, but other than HTML and CSS, javascript and jQuery are the only ones I'm experienced with.
Any and all help would be greatly appreciated.
I think your question is similar to
Convert String to XML Document in JavaScript
The answer is that you can wrap it in a jQuery object. Then use jQuery's normal DOM manipulation methods on it.
var myhtml = $($('#main-input').val());
myhtml.find('a').each(function () {
alert($(this).text());
});
if it's a top level element you need to use filter instead of find.
You can create a jQuery object from html strings outside of the DOM and maniuplate it just the same as if it was in the DOM.
Simple example:
var html='<div><p>ABC</p></div>';
alert( $(html).find('p').text() ); // alerts "ABC"
Or
var $div= $('<div>').append(html).find('p').after('<p>DEF</p>');
var newHtml= $div.html();
Will return
<div>
<p>ABC</p>
<p>DEF</p>
</div>
Conclusion, I would loop through a jQuery object created from your html and do what you need using jQuery methods
I need to replace one pattern like that : "{12345,abcd}" in html of body but i woludn't lose the events of children.
I have tried that code :
$("body").html($("body").html().replace(/[{]{1}([\d]+)[,]{1}(.*?)[}]{1}/g, "<span>Code:$1</span> - <span>Text:$2</span>"))
but that kill all events of elements.
How i can do?
You have 3 options:
Open the related view in your IDE, modify it and like a gentleman generate the desired markup. Modifying HTML using regex is a bad practice.
Select only the descendant elements than can have that string and replace their textContent instead of the resetting entire body contents.
$('.elements').text(function(_, oldText) {
return oldText.replace('foo', 'bar');
});
Replace the body' contents and delegate all the events:
$(document).on('event', 'element', fn);
You can also replace the body' contents before binding event handlers. This of course won't break future event handlers.
#BlackSeep
That not work if I would do something like angularJs parser, I have solved fetching all text items and searching into for the regex.
This is the solution for me :
$("body").find(":not(iframe)").addBack().contents().each(function() {
if(this.nodeType == 3)
{
$(this).first().replaceWith( $(this).text().replace(/[{]{1}([\d]+)[,]{1}(.*?)[}]{1}/g, "<span class='translator' data-code='$1' data-text='$2'>$2</span>"));
}
});
For information I had used this for have one mapper for webpages translation.
Where i print "{id,text}" i replace that with one span and then I handle one special event for launch the translation of that box.
this happens because html() method works with text (not dom elements) and text doesn't save event listeners, so u first read text, then do something with it (replace), then write it. In this situation you will loose as event listeners as they are attached to DOM objects (not to text)
i think the best way is to go through all Dome tree , find all textNode DOM elements, and to do replace separately in each of them
I have the following javascript working to insert AJAX responses into a div with id results:
document.getElementById("results").innerHTML=xmlhttp.responseText;
However, this adds all new elements after those already present. I need for the new elements to be inserted before everything else.
I know this is probably very trivial but I can't seem to find anyway to do it myself.
Thanks for any help!
With modern js you can utilize the prepend method. Currently caniuse.com says only of IE, Edge, and OperaMini are not supported.
ParentNode.prepend(nodesToPrepend);
e.g.,
ParentNode.prepend(newDiv);
Also, it automatically converts text into a text node so you could do the following for your use case:
document.getElementById("results").prepend(xmlhttp.responseText);
You want either this
results.insertAdjacentHTML( 'beforebegin', xmlhttp.responseText );
or this
results.insertAdjacentHTML( 'afterbegin', xmlhttp.responseText );
(the variable results of course being a reference to the DOM element)
So, the first one will insert the new content before the element itself (as a previous sibling), and the second one will insert it inside the element before any other children).
I don't remember the exact syntax, but it something like:
var newDiv = document.createElement("div");
newDiv.innerHTML=xmlhttp.responseText;
document.getElementById("results").childNodes.addAt(0,newDiv);
if you can use jQuery, it's just simple as:
$("#results").prepend(xmlhttp.responseText);