Replace every instance of a word in the dom with Javascript - javascript

I am trying to parse with a chrome extension I am making, and replace ever instance of one word with another. This is what I have that is not working for me
function jamify() {
$("body").html().replace(/James/g,"Jamie");
}

The quick and rather dirty replacement of .html() has a couple of downsides.
it will actually replace the entire DOM structure, removing any event bindings if these are not bound 'live' on an element higher up the hierarchy
it will replace a lot more than you may expect. You should be safe with 'James' to 'Jamie', but it may get funky when 'em' wants to be named 'emmy' and suddenly certain italic texts get straightened out.
A better way is to replace only strings in actual text nodes, as jQuery is not (currently) a tag on the question, I assume vanilla javascript is a proper option.
var walker = document.createTreeWalker(
document.body,
NodeFilter.SHOW_TEXT, {
acceptNode: function(node) {
return NodeFilter.FILTER_ACCEPT;
}
},
false
);
while (walker.nextNode()) {
walker.currentNode.data = walker.currentNode.data.replace(/James/g, 'Jamie');
}
<!-- James -->
<div data-name="James" class="James">
James
</div>
This example will only touch the actual text element(s), the comment and both the attributes (data-name and class) will not get replaced, so it remains safe to have javascript and/or css referring to these.

If the words are in the textContent you can try:
var all = document.querySelectorAll('.test')
//using .test as a wrapper section, try using body in production as selector (in the snippets it breaks)
all.forEach(x => x.textContent = x.textContent.replace(/James/gi, "Jamie"))
// keep in mind forEach for nodes has limited support, tested in chrome
<div class="test">
<p>James is here</p>
<div >this div is James</div>
</div>

I am showing it this way to show that you have to call some function to reset the html to the newly replaced string
NOTE: This will destroy any DOM event you had attached before the replace
you can shorten this by nesting the call all into one if you wanted
function jamify() {
var str = $(".test").html();
console.log('jamify', str);
str2 = str.replace(/James/g,"Jamie");
$(".test").html(str2);
//to simplify it could be done this way too
//$(".test").html($(".test").html().replace(/James/g,"Jamie"))
}
$(document).ready(function(){
//alert('ready');
$('.inner').click(function(){console.log('inner click')})
//Yea!, my click event is all good.
jamify();
//Now all your inner click EVENT is broken so this is not good
//solution if there are any events attached in your DOM
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="test">
<p>James is here</p>
<div class="inner">this div is James</div>
</div>

Related

jQuery contents from a higher level?

I have the following jQuery that mostly works:
$("article > p, article > div, article > ol > li, article > ul > li").contents().each(function() {
if (this.nodeType === 3) {
strippedValue = $.trim($(this).text());
doStuff(strippedValue);
}
if (this.nodeType === 1) {
strippedValue = $.trim($(this).html());
doStuff(strippedValue);
}
})
The problems comes when (inside doStuff()) I try to replace HTML tags. Here is a view of my elements:
And I'm trying to replace those <kbd> tags thusly:
newStr = newStr.replace(/<kbd>/g, " <b>");
newStr = newStr.replace(/<\/kbd>/g, "<b> ");
That doesn't work, and I'm seeing in the debugger that the <kbd> tags are seen as first-class children and looped separately. Whereas I want everything inside my selectors to be seen as a raw string so I can replace things. And I realize I'm asking for a contradiction, because .contents() means get children and their contents. So if I have a selector that is a direct parent of <kbd>, then <kdb> ceases to become a raw string and becomes instead a node that is being looped.
So it seems like my selectors are wrong BUT whenever I try to bring my selectors higher in the hierarchy, immediately I lose textual contents and I end up with a bunch of html with no contents inside the elements. (The screenshot shows good contents, as expected.)
So for example I tried this:
$("article").contents().each(function() {
...
}
...hoping that the selector looping would occur a little higher, and thus allow HTML tags further down to come through as raw text. But clearly I'm lost.
My objective is to simply perform a bunch of string replacements on the contents of the html. But there are two challenges with this:
The page contents load dynamically, with ajaxy calls or similar, so full contents are not available until about a second or two after page load.
When I try to grab high-level elements such as body, it ends up devoid of much of the textual contents. The selectors I currently have don't suffer from that problem; those get everything I want BUT then HTML/XML elements get looped instead of coming through as plain text so that I can perform replacements.
Why do you need to perform the modification on raw HTML? You could just replace the DOM elements directly (not to mention that this is much more reliable then using string replacement):
$('kbd').replaceWith(function() {
return ` <b>${this.textContent}</b> `;
// or directly create DOM elements:
// const b = document.createElement('b');
// b.textContent = this.textContent;
// return b;
});
console.log($('b').length);
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<kbd>hello world</kbd>
Of course you can still do string replacements where it makes sense, but you should work with DOM elements as much as possible.

Replace non-code text on webpage

I searched through a bunch of related questions that help with replacing site innerHTML using JavaScript, but most reply on targetting the ID or Class of the text. However, my can be either inside a span or td tag, possibly elsewhere. I finally was able to gather a few resources to make the following code work:
$("body").children().each(function() {
$(this).html($(this).html().replace(/\$/g,"%"));
});
The problem with the above code is that I randomly see some code artifacts or other issues on the loaded page. I think it has something to do with there being multiple "$" part of the website code and the above script is converting it to %, hence breaking things.using JavaScript or Jquery
Is there any way to modify the code (JavaScript/jQuery) so that it does not affect code elements and only replaces the visible text (i.e. >Here<)?
Thanks!
---Edit---
It looks like the reason I'm getting a conflict with some other code is that of this error "Uncaught TypeError: Cannot read property 'innerText' of undefined". So I'm guessing there are some elements that don't have innerText (even though they don't meet the regex criteria) and it breaks other inline script code.
Is there anything I can add or modify the code with to not try the .replace if it doesn't meet the regex expression or to not replace if it's undefined?
Wholesale regex modifications to the DOM are a little dangerous; it's best to limit your work to only the DOM nodes you're certain you need to check. In this case, you want text nodes only (the visible parts of the document.)
This answer gives a convenient way to select all text nodes contained within a given element. Then you can iterate through that list and replace nodes based on your regex, without having to worry about accidentally modifying the surrounding HTML tags or attributes:
var getTextNodesIn = function(el) {
return $(el)
.find(":not(iframe, script)") // skip <script> and <iframe> tags
.andSelf()
.contents()
.filter(function() {
return this.nodeType == 3; // text nodes only
}
);
};
getTextNodesIn($('#foo')).each(function() {
var txt = $(this).text().trim(); // trimming surrounding whitespace
txt = txt.replace(/^\$\d$/g,"%"); // your regex
$(this).replaceWith(txt);
})
console.log($('#foo').html()); // tags and attributes were not changed
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="foo"> Some sample data, including bits that a naive regex would trip up on:
foo<span data-attr="$1">bar<i>$1</i>$12</span><div>baz</div>
<p>$2</p>
$3
<div>bat</div>$0
<!-- $1 -->
<script>
// embedded script tag:
console.log("<b>$1</b>"); // won't be replaced
</script>
</div>
I did it solved it slightly differently and test each value against regex before attempting to replace it:
var regEx = new RegExp(/^\$\d$/);
var allElements = document.querySelectorAll("*");
for (var i = 0; i < allElements.length; i++){
var allElementsText = allElements[i].innerText;
var regExTest = regEx.test(allElementsText);
if (regExTest=== true) {
console.log(el[i]);
var newText = allElementsText.replace(regEx, '%');
allElements[i].innerText=newText;
}
}
Does anyone see any potential issues with this?
One issue I found is that it does not work if part of the page refreshes after the page has loaded. Is there any way to have it re-run the script when new content is generated on page?

How to hide specific sentence on page load with Javascript

I need to hide a specific sentence "The leader of tomorrow, are great" from the content below without calling any class or div.
Example:
<p>The leader of tomorrow, are great leaders</p>
<br>
The leader of tomorrow, are great leaders of tomorrow.
Any solution to this would be appreciated.
Why does jQuery('*:contains("sentence to hide")') not work on this scenario?
It will match all elements that contain the sentence. So it will match the closest node but in addition all parent nodes all the way up. So instead of matching element eg in this case the "P-node" it will also match for body, html and even the script itself if it is inline because it also contains the sentence! And if the element that actual contains the sentence to hide is wrapped by lets say:
<div id="_1">
<div id="_2">
<div id="_3">
<p id="_4">sentence to hide</p>
</div>
</div>
</div>
Then with jQuery('*:contains("sentence to hide")') you will get a jQuery collection with [{html}, {body}, {#_1}, {#_2}, {#_3}, {#_4}]
Here is another approach...
jQuery( document ).ready(function(){
jQuery('body').html(function(iIndex, sHtml) {
return sHtml.replace(/The leader of tomorrow, are great/g, '');
});
});
Or to have more control you can wrap the sentence and style the wrapper as you like...
jQuery( document ).ready(function(){
var sNeedle = 'The leader of tomorrow, are great';
jQuery('body').html(function(iIndex, sHtml) {
return sHtml.replace(
new RegExp(sNeedle, 'g'),
'<span class="hide-specific-sentence">' + sNeedle + '</span>'
);
});
});
and then in your CSS:
.hide-specific-sentence{
display: none;
}
NOTES:
I would not recommend to treat web content like this but if you have to for whatever reason you can do it this way
narrow the selcetor to the closest possible parent that actual contains the sentence(s) maybe ".content" or whatever
make shure that you do this action before anything else because
attached event handlers could get lost (depending on the way they are bound)
If you try the code as SO snippet you will get an error. This is propably due to some internal restrictions (I guess). But I tried it local and it works like a charme or you can try with this working PEN that also works like a charme...
jQuery has the contains-method. Here's a snippet for you:
<script type="text/javascript">
$(function() {
var result = $('*:contains("The leader of tomorrow, are great")');
});
</script>
The selector above selects any element that contains the target string. The result will be a jQuery object that contains any matched element. See the API information at: http://docs.jquery.com/Selectors/contains#text
One thing to note with the '*' wildcard is that you'll get all elements, including your html and body elements, which you probably don't want. That's why most of the examples at jQuery and other places use $('p:contains("The leader of tomorrow, are great")')

jQuery "add" Only Evaluated When "appendTo" Called

this has been driving me crazy since yesterday afternoon. I am trying to concatenate two bodies of selected HTML using jQuery's "add" method. I am obviously missing something fundamental. Here's some sample code that illustrated the problem:
<html>
<head>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
</head>
<body>
<p id="para1">This is a test.</p>
<p id="para2">This is also a test.</p>
<script>
var para1 = $("#para1").clone();
var para2 = $("#para2").clone();
var para3 = para1.add(para2);
alert("Joined para: " + para3.html());
para3.appendTo('body');
</script>
</body>
</html>
I need to do some more manipulation to "para3" before the append, but the alert above displays only the contents of "para1." However, the "appendTo appends the correct, "added" content of para1 and para2 (which subsequently appears on the page).
Any ideas what's going on here?
As per the $.add,
Create a new jQuery object with elements added to the set of matched elements.
Thus, after the add, $para3 represents a jQuery result set of two elements ~> [$para1, $para2]. Then, per $.html,
Get the HTML contents of the first element in the set of matched elements or set the HTML contents of every matched element.
So the HTML content of the first item in the jQuery result ($para1) is returned and subsequent elements (including $para2) are ignored. This behavior is consistent across jQuery "value reading" functions.
Reading $.appendTo will explain how it works differently from $.html.
A simple map and array-concat can be used to get the HTML of "all items in the result set":
$.map($para3, function (e) { return $(e).html() }).join("")
Array.prototype.map.call($para3, function (e) { return $(e).html() }).join("")
Or in this case, just:
$para1.html() + $para2.html()
Another approach would be to get the inner HTML of a parent Element, after the children have been added.

How to get plain text from an element in PrototypeJS?

How can I get plain text from an element?
<div id="summary_header">
<span class="heading" style="margin-left: 210px;">This is first line</span><br/>
<span style="margin-left: 260px;"><b> You are in second line</b></span><br/>
</div>
What's the way to get plain text from this div tag? I want to get plain text as "This is first line You are in second line".
$('summary_header').textContent gives with white spaces.
If you need a method, no problem. That is the beauty of javascript and it's prototypal inheritance.
Prototype.js allows you to extend the Element object with new methods.
Just add this to your scripts:
Element.addMethods({
getText: function(element) {
element = $(element);
return element.innerHTML.strip().stripTags().replace(/\n/g,' ').replace(/\s+/g,' ');
}
});
That is almost the same as #koko proposed, but I suggest changing each new line character \n to one space instead of removing them, and changing any number of consecutive withe-space characters to single space. This is similar behavior to what browsers do.
When you at that to your scripts, after the prototype.js loads, you can use new method to get plain text:
var plainText = $('summary_header').getText();
It's not very beautiful, but it works. I don't know what you're trying to achieve, but there you go:
var f = $('summary_header').innerHTML.strip().stripTags().replace(/\n/g,'').replace(/\s{2}/g,'');
http://jsfiddle.net/e8uFS/1/

Categories

Resources