Javascript search for all occurences of a character in the dom? - javascript

I would like to find all occurrence of the $ character in the dom, how is this done?

You can't do something semantic like wrap $4.00 in a span element?
<span class="money">$4.00</span>
Then you would find elements belonging to class 'money' and manipulate them very easily. You could take it a step further...
<span class="money">$<span class="number">4.00</span></span>
I don't like being a jQuery plugger... but if you did that, jQuery would probably be the way to go.

One way to do it, though probably not the best, is to walk the DOM to find all the text nodes. Something like this might suffice:
var elements = document.getElementsByTagName("*");
var i, j, nodes;
for (i = 0; i < elements.length; i++) {
nodes = elements[i].childNodes;
for (j = 0; j < nodes.length; j++) {
if (nodes[j].nodeType !== 3) { // Node.TEXT_NODE
continue;
}
// regexp search or similar here
}
}
although, this would only work if the $ character was always in the same text node as the amount following it.

You could just use a Regular Expression search on the innerHTML of the body tag:
For instance - on this page:
var body = document.getElementsByTagName('body')[0];
var dollars = body.innerHTML.match(/\$[0-9]+\.?[0-9]*/g)
Results (at the time of my posting):
["$4.00", "$4.00", "$4.00"]

The easiest way to do this if you just need a bunch of strings and don't need a reference to the nodes containing $ would be to use a regular expression on the body's text content. Be aware that innerText and textContent aren't exactly the same. The main difference that could affect things here is that textContent contains the contents of <script> elements whereas innerText does not. If this matters, I'd suggest traversing the DOM instead.
var b = document.body, bodyText = b.textContent || b.innerText || "";
var matches = bodyText.match(/\$[\d.]*/g);

I'd like to add my 2 cents for prototype. Prototype has some very simple DOM traversal functions that might get exactly what you are looking for.
edit so here's a better answer
the decendants() function collects all of the children, and their children and allows them to be enumerated upon using the each() function
$('body').descendants().each(function(item){
if(item.innerHTML.match(/\$/))
{
// Do Fun scripts
}
});
or if you want to start from document
Element.descendants(document).each(function(item){
if(item.innerHTML.match(/\$/))
{
// Do Fun scripts
}
});

Related

Passing multiple values with GetElementById (& Split after certain a character)

I have little to no experience of JavaScript but I do know that the getElementID only carries one value so how can I have 2 values passed?
Can I use it twice like I have down below or would I be better to use another GetElementBy/GetElementsBy method to do it?
<script type="text/javascript">
$(document).ready(function () {
hash();
function hash() {
var hashParams = window.location.hash.substr(1).split('&');
for (var i = 0; i < hashParams.length; i++) {
var p = hashParams[i].split('=');
document.getElementById("<%=start.ClientID%>").value = decodeURIComponent(p[1]);
document.getElementById("<%=end.ClientID%>").value = decodeURIComponent(p[1]);;
}
}
});
</script>
EDIT
So I've decided to use the loop twice and its working but the values I'm passing contain text I need removed. Is there a way in which I can cut off the split after a certain character? Here is my new code
<script type="text/javascript">
$(document).ready(function () {
hash();
function hash() {
var hashParams = window.location.hash.substr(1).split('#');
for (var i = 0; i < hashParams.length; i++) {
var p = hashParams[i].split('=');
document.getElementById("<%=start.ClientID%>").value = decodeURIComponent(p[1]);
}
var hashParams = window.location.hash.substr(1).split('&');
for (var i = 0; i < hashParams.length; i++) {
var p = hashParams[i].split('=');
document.getElementById("<%=end.ClientID%>").value = decodeURIComponent(p[1]);;
}
}
});
</script>
And here is the text that appears in the search bar when forwarded from the previous page.
localhost:56363/Bookings.aspx#start=27/02/2018 12:30&end=27/02/2018 17:30
The start and end input boxes fill with the values but the start input box (27/02/2018 12:30&end) has characters I want cut off (&end).
Is there a way to stop a split after a certain character?
Using it twice as you have is perfectly acceptable. And, if they are separate things, then it makes sense.
While you could also use getElementsByTagName(), getElementsByName() or getElementsByClassName(), usually using document.querySelectorAll() is the more modern choice.
If they have something in common with them (like say a class), you could use it like this:
const nodeList = document.querySelectorAll('.classToGet');
Array.prototype.forEach.call(nodeList, element => element.value = decodeURIComponent(p[1]));
document.querySelectorAll() (as well as the getElementsBy functions) return a NodeList, which is kind of like an Array, but doesn't have an Array's functions, so you need to Array.prototype.forEach.call() to loop over them.
document.querySelectorAll() accepts a string like you would give to CSS, and the NodeList has all elements that match that.
And FYI, there is an equivalent document.querySelector() which gets a single element, so you could use it for IDs:
document.querySelector("#<%=start.ClientID%>")
Note the # like you would have for CSS at the beginning.
ID is a unique identifier, unlike class, so there should be only one of it with the same name in your DOM.
getElementById is intended to find the one element in the DOM with the specified ID.
If you need to get multiple elements, then yes make multiple calls to getElementById.
See here for the documentation on the getElementById method showing that it only accepts a single ID parameter: https://developer.mozilla.org/en-US/docs/Web/API/Document/getElementById

JavaScript + Regex:: Replace "foo" with "bar" across entire document, excluding URLs

I'm trying to replace all instances of "foo" on a page with "bar", but to exclude instances occurring within image or URL links.
The current code I have is a simple replace:
document.documentElement.innerHTML = document.documentElement.innerHTML.replace(/foo/g, "bar");
But it breaks images and links containing "foo" in the address.
I'm looking for a regular expression replacement that will take the following:
foo
barfoo
foo
<img src="foo.jpg">
And give me:
bar
barbar
bar
<img src="foo.jpg">
If this can't be accomplished with regex in JavaScript, would there be a more elegant way to only run the replacement against non-URL strings?
Yeah, you're not going to want to use regex to do this. What you want to do is replace the text of every text node in your DOM tree. Try something like this.
var allElements = document.getElementsByTagName("*"); // Get every element.
for (var i = 0; i < allElements.length; i++) {
var children = allElements.item(i).childNodes;
for (var j = 0; j < children.length; j++) {
if (children[j].nodeType === 3 /* is this node a text node? */) {
children[j].nodeValue = children[j].nodeValue.replace(/* run your replacement regex here */).
}
}
}
There are 2 problems to solve.
Firstly, you need to get all the text nodes. This is a problem in and of itself.
This thread on stackoverflow discusses some techniques.
getElementsByTagName() equivalent for textNodes
Once you have your text nodes, you can run your regex on each node, and be fairly certain that you got everything.

Replacing a lot of text in browser's addon

I'm trying to develop a Firefox add-on that transliterates the text on any page into specific language. Actually it's just a set of 2D arrays which I iterate and use this code
function escapeRegExp(str) {
return str.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, "\\$1");
}
function replaceAll(find, replace) {
return document.body.innerHTML.replace(new RegExp(escapeRegExp(find), 'g'), replace);
}
function convert2latin() {
for (var i = 0; i < Table.length; i++) {
document.body.innerHTML = replaceAll(Table[i][1], Table[i][0]);
}
}
It works, and I can ignore HTML tags, as it can be in english only, but the problem is performance. Of course it's very very poor. As I have no experience in JS, I tried to google and found that maybe documentFragment can help.
Maybe I should use another approach at all?
Based on your comments, you appear to have already been told that the most expensive thing is the DOM rebuild that happens when you completely replace the entire contents of the page (i.e. when you assign to document.body.innerHTML). You are currently doing that for each substitution. This results in Firefox re-rendering the entire page for each substitution you are making. You only need assign to document.body.innerHTML once, after you have made all of the substitutions.
The following should provide a first pass at making it faster:
function escapeRegExp(str) {
return str.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, "\\$1");
}
function convert2latin() {
newInnerHTML = document.body.innerHTML
for (let i = 0; i < Table.length; i++) {
newInnerHTML = newInnerHTML.replace(new RegExp(escapeRegExp(Table[i][1]), 'g'), Table[i][0]);
}
document.body.innerHTML = newInnerHTML
}
You mention in comments that there is no real need to use a RegExp for the match, so the following would be even faster:
function convert2latin() {
newInnerHTML = document.body.innerHTML
for (let i = 0; i < Table.length; i++) {
newInnerHTML = newInnerHTML.replace(Table[i][1], Table[i][0]);
}
document.body.innerHTML = newInnerHTML
}
If you really need to use a RegExp for the match, and you are going to perform these exact substitutions multiple times, you are better off creating all of the RegExp prior to the first use (e.g. when Table is created/changed) and storing them (e.g. in Table[i][2]).
However, assigning to document.body.innerHTML is a bad way to do this:
As the8472 mentioned, replacing the entire content of document.body.innerHTML is a very heavy handed way to perform this task, which has some significant disadvantages including probably breaking the functionality of other JavaScript in the page and potential security issues. A better solution would be to change only the textContent of the text nodes.
One method of doing this is to use a TreeWalker. The code to do so, could be something like:
function convert2latin(text) {
for (let i = 0; i < Table.length; i++) {
text = text.replace(Table[i][1], Table[i][0]);
}
return text
}
//Create the TreeWalker
let treeWalker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT,{
acceptNode: function(node) {
if(node.textContent.length === 0
|| node.parentNode.nodeName === 'SCRIPT'
|| node.parentNode.nodeName === 'STYLE'
) {
//Don't include 0 length, <script>, or <style> text nodes.
return NodeFilter.FILTER_SKIP;
} //else
return NodeFilter.FILTER_ACCEPT;
}
}, false );
//Make a list of nodes prior to modifying the DOM. Once the DOM is modified the TreeWalker
// can become invalid (i.e. stop after the first modification). Doing so is not needed
// in this case, but is a good habit for when it is needed.
let nodeList=[];
while(treeWalker.nextNode()) {
nodeList.push(treeWalker.currentNode);
}
//Iterate over all text nodes, changing the textContent of the text nodes
nodeList.forEach(function(el){
el.textContent = convert2latin(el.textContent));
});
Don't use innerhtml, it would destroy any javascript event handlers registered on the DOM nodes or make references to dom nodes held by the page's javascript obsolete. In other words, you could easily break a page with that. And of course it's inefficient.
You can use a treewalker instead and filter for text nodes only. The walking can be incrementalized by deferring the next step with window.setTimeout every 1000th text node or something like that.
If you register your addon script early enough you could also use a mutation observer to get notified about text nodes as soon as they get inserted and replace them incrementally which should make things less janky.

Iterate through all html tags, including children in Javascript

Just to clarify what I'm trying to do, I'm trying to make a Chrome extension that can loop through the HTML of the current page and remove html tags containing certain text. But I'm having trouble looping through every html tag.
I've done a bunch of searching for the answer and pretty much every answer says to use:
var items = document.getElementsByTagName("*");
for (var i = 0; i < items.length; i++) {
//do stuff
}
However, I've noticed that if I rebuild the HTML from the page using the elements in "items," I get something different than the page's actual HTML.
For example, the code below returns false:
var html = "";
var elems = document.getElementsByTagName("*");
for (var i = 0; i < elems.length; i++) {
html += elems[i].outerHTML;
}
alert(document.body.outerHTML == html)
I also noticed that the code above wasn't giving ALL the html tags, it grouped them into one tag, for example:
var html = "";
var elems = document.getElementsByTagName("*");
alert(elems[0].outerHTML);
I tried fixing the above by recurssively looking for an element's children, but I couldn't seem to get that to work.
Ideally, I would like to be able to get every individual tag, rather than ones wrapped in other tags. I'm kind of new to Javascript so any advice/explanations or example code (In pure javascript if possible) as to what I'm doing wrong would be really helpful. I also realize my approach might be completely wrong, so any better ideas are welcome.
What you need is the famous Douglas Crockford's WalkTheDOM:
function walkTheDOM(node, func)
{
func(node);
node = node.firstChild;
while (node)
{
walkTheDOM(node, func);
node = node.nextSibling;
}
}
For each node the func will be executed. You can filter, transform or whatever by injecting the proper function.
To remove nodes containing a specific text you would do:
function removeAll(node)
{
// protect against "node === undefined"
if (node && node.nodeType === 3) // TEXT_NODE
{
if (node.textContent.indexOf(filter) !== -1) // contains offending text
{
node.parentNode.removeChild(node);
}
}
}
You can use it like this:
filter = "the offending text";
walkTheDOM(document.getElementsByTagName("BODY")[0], removeAll);
If you want to parametize by offending text you can do that, too, by transforming removeAll into a closure that is instantiated.
References to DOM elements in JavaScript are references to memory addresses of the actual nodes, so you can do something like this (see the working jsfiddle):
Array.prototype.slice.call(document.getElementsByTagName('*')).forEach(function(node) {
if(node.innerHTML === 'Hello') {
node.parentNode.removeChild(node);
}
});
Obviously node.innerHTML === 'Hello' is just an example, so you'll probably want to figure out how you want to match the text content (perhaps with a RegEx?)

Remove element created by JavaScript on load

How can I remove elements which are created by JavaScript, I tried using CSS by setting display:none; but that doesn't seem to solve my problem, also I can't remove them since I don't have them in HTML, any other ways? Thank you
UPDATE:
Can't use any JavaScript such as jQuery, MooTools, ExtJS etc, and actual elements I want to remove are divs, with a specified class so I can't use getElementById.
I found this script on Google, but it doesn't seem to work but this is what I need:
HERE
This is fairly simple to do this using jQuery.
$("#myId").remove();
will remove
<div id="myId"></div>
Edit: You can also do it with "old school" javascript.
The function you're looking for is removeChild()
Example:
function removeElement(divNum) {
var d = document.getElementById('myDiv');
var olddiv = document.getElementById(divNum);
d.removeChild(olddiv);
}
You will want something like this to take advantage of browser support where you can:
if(document.getElementsByClassName){
// Safari 3+ and Firefox 3+
var itms = document.getElementsByClassName('your_class');
for(var i = 0; i<itms.length; i++){
var it = itms[i];
if(it.tagName == "DIV"){
it.parentNode.removeChild(it);
i = i - 1; //It actually gets removed from the array, so we need to drop our count
}
}
} else {
// All other browsers
var itms = document.getElementsByTagName('div');
for(var i = 0; i<itms.length; i++){
var it = itms[i];
// Test that className contains your class
if(/your_class/.test(it.className)) it.parentNode.removeChild(it);
}
}
JavaScript handles all memory mangement for you using garbage collection so once all references to an object cease to exist that object will be handled by the browsers specific implementation.
If you have the dom element itself:
if(node && node.parentNode){
// return a ref to the removed child
node.parentNode.removeChild(node);
}
Since you say you can't use Javascript, you're pretty stuck. Have you tried this CSS:
.classname {
display: none !important;
}
It's possible that the created elements have inline styles set on them, in which case normal CSS is overridden. I believe the !important will override inline styles.
Of course, the best solution is not to add the element in the first place... but I'm guessing you're in one of those (unfathomably common) scenarios where you can't change or get rid of the JS?
Not all browsers have a
document.getElementsByClassName
method, but for your purpose you can
fake it well enough- This method does
not work like the native
HTMLElement.getElementsByClassName- it
returns an array, not a live nodelist.
You can specify a parent element and a
tag name to speed it up.
function getElementsByClass(css, pa, tag){
pa= pa || document.body;
tag= tag || '*';
css= RegExp('\\b'+css+'\\b');
var A= [], elements, L, i= 0, tem;
elements= pa.getElementsByTagName(tag);
L= elements.length;
while(i<L){
tem= elements[i++];
if(css.test(tem.className)) A[A.length]= tem;
}
return A;
}
// test
var A= getElementsByClass('removeClass'), who;
while(A.length){
who= A.pop(); // remove from the end first, in case nested items also have the class
if(who.parentNode) who.parentNode.removeChild(who);
who= null;
}
If you have assigned event handlers to
the elements being removed, you should
remove the handlers before the
elements.
You will probably have to be more specific.
The general answer is 'with JavaScript'. As long as you have a way of navigating to the element through the DOM, you can then remove it from the DOM.
It's much easier if you can use a library like jQuery or prototype, but anything you can do with these you can do with JavaScript.
marcgg has assumed that you know the ID of the element: if you don't but can trace it in the DOM structure, you can do something like this (in prototype - don't know jQuery)
var css_selector = 'table#fred tr td span.mydata';
$$(css).invoke('remove');
If you can't use a JS library, you'll have to do the navigation through the DOM yourself, using Element.getElementsByTagName() a lot.
Now you've specified your question a bit: use Element.getElementsByTagName, and loop through them looking at their className property.
Use:
document.removeChild('id_of_element');

Categories

Resources