I was looking for a way to search through all elements of the same class and remove any as these were causing unwanted gaps in my page layout.
Initially I used this code:
var el = document.querySelector('.offer');
el.innerHTML = el.innerHTML.replace(' ', '');
But this only finds the first node with the class of offer so isn't much use.
I'm answering my own question because I had to piece it together from a number of posts on here as well as other sites and I hope it helps others in my position.
Firstly I needed to use .queryselectorAll instead of .querySelector to return all elements with the offer class.
But the next line wont work since .queryselectorAll returns a string of nodes rather than just the first one it comes across.
el.innerHTML = el.innerHTML.replace(' ', '');
The solution is to loop through each element and replace each instance of
var el = document.querySelectorAll('.offer');
for(var i = 0; i < el.length; i++){
el[i].innerHTML = el[i].innerHTML.replace(' ', '');
}
Related
I am currently building a Chrome extension which has to find specific pages in a website specifically the Log In / Sign In page, the Sign Up / Register page, the About page and the Contact Us page.
I am trying to achieve this by first getting the list of elements in the page (which I have already done). Now I need to check the innerHTML of the element such that it is a leaf node in the DOM and contains a part of the keyword, and I am trying to do this with a regex. I managed to build a regex which successfully returns what's in between a start or end tag of an element (i.e. the tag name along with its attributes), but not the innerHTML. Below is what I have done so far (with the example for the About page:
var list = document.body.getElementsByTagName("*");
var aboutElement = /^[^<.+>].*About.*[^(<.+>]$/i;
for (var i = 0; i <= list.length; i++) {
if ((aboutElement.test(list[i].innerHTML)) || (aboutElement.test(list[i].alt))) {
list[i].click();
}
}
Any idea what I should add to it such that it only matches leaf nodes (nodes which do not contain other nodes) and not what's in a start or end tag? I also think that with what I've done it's going to match everything in the innerHTML because of the .* part so I may need to change that as well. Any help would be greatly appreciated!
Thanks to two of the answers in the comments I managed to solve the problem. I used .textContent and changed the regex as shown below and it worked.
var list = document.body.getElementsByTagName("*");
var aboutElement = /^(.*?\s*(\bAbout\b)[^$]*)$/i;
for (var i = 0; i <= list.length; i++) {
if ((aboutElement.test(list[i].textContent)) || (aboutElement.test(list[i].alt))) {
list[i].click();
}
}
Is there a way to get the index of class name (I.e. the third element with the class "className" would be 3 without using jQ?
I don't know jQ, and I don't have time to learn it right now, and I don't want to include code into my code that I don't understand at least some.
Thanks.
BTW, I've used jQ instead of spelling it out so those results can be filtered out in Google should somebody have the same question. If I spelled it out, and somebody used the NOT operator in Google, this one would also disappear.
You could do something like:
// the element you're looking for
var target = document.getElementById("an-element");
// the collection you're looking in
var nodes = document.querySelectorAll(".yourclass");
var index = [].indexOf.call(nodes, target);
See: Array's indexOf.
If you have already a proper array as nodes instead of a NodeList, you can just do nodes.indexOf(target).
you can use document.getElementsByClassName
var el = document.getElementsByClassName('className');
for (var i = 0; i < el.length; i++) {
// your index is inside here
}
el[i] is the element in the current iteration, i is the index
(I.e. the third element with the class "className" would be 3)
if (i == 3)
return el[i]
JsFiddle: here.
Just use getElementsByClassName, it returns a list of elements with the specified classes.
elements = document.getElementsByClassName("test")
element = elements[2] //get the 3rd element
Hope this helps!
these work as of es6:
Array.from(document.querySelectorAll('.elements')).indexOf(anElement)
or
[...document.querySelectorAll('.elements')].indexOf(anElement)
Please bear with me on this question; I have only recently started coding in Javascript and for this reason I would like a strong foundation before breaking into jQuery.
I have the following Javascript code:
var app = {
text: document.getElementById('text'),
output: document.getElementById('output'),
createDiv: document.createElement('div')
};
function postData(){
app.output[0].appendChild(app.createDiv.firstChild);
app.createDiv.classname = 'text';
document.getElementsByClassName('username').innerHTML += app.text.value;
}
onClick I want the value of app.text to print (using innerHTML) ABOVE the output div. Any ideas or questions? Sorry if I am being vague or not making sense, I have been staring at this for 4 hours now and am probably just burned out!
You'll have to create a text node and insert it before each element in the NodeList returned by document.getElementsByClassName:
var node = document.createTextNode(app.text.value);
var elements = document.getElementsByClassName('username');
for (var i = 0; i < elements.length; i++) {
var elem = elements[i];
elem.parentNode.insertBefore(node.cloneNode(), elem)
}
And just to tempt you with the jQuery version:
$('.username').before(app.text.value);
I'm not sure what you're asking, but I see the following problems with your code:
app.output[0]: there should be no [0], app.output is already an element (you got it by id). So use app.output.appendChild(app.createDiv.firstChild);. Still, this makes no sense; as Barmar said, you just created that div, so it doesn't have any children at that point.
app.createDiv.classname: should be className
document.getElementsByClassName('username').innerHTML: this returns multiple elements, maybe you want document.getElementsByClassName('username')[0].innerHTML?
I swear this was just working fine a few days ago...
elm = document.querySelectorAll(selector);
var frag = document.createDocumentFragment();
while (elm[0]){
frag.appendChild(elm[0]);
}
Right, so, this should append each node from our elm node list. When the first one is appended, the second "moves" to the first position in node list, hence the next one is always elm[0]. It should stop when the elm nodeList is completely appended. However, this is giving me an infinite loop. Thoughts?
EDIT - because I've gotten the same answer several times...
A nodeList is not an array, it is a live reference. When a node is "moved" (here, appended) it should be removed automatically from the node list. The answers all saying "you're appending the same element over and over" - this is what's happening, it shouldn't be. A for loop shouldn't work, because when the first node is appended, the next node takes its index.
2nd EDIT
So the question is now "why is the nodeList behaving as an array?". The node list SHOULD be updating every time a node is being appended somewhere. Most peculiar.
Solution (in case someone needs something to handle live + non-live node lists)
elm = (/*however you're getting a node list*/);
var frag = document.createDocumentFragment();
var elength = elm.length;
for (var b = 0; b<elength; b++){
if (elm.length === elength){
frag.appendChild(elm[b]);
} else {
frag.appendChild(elm[0].cloneNode());
}
}
Basically, just checking to see if the node list has changed length.
From the MDN Docs
Element.querySelectorAll
Summary
Returns a non-live NodeList of all elements descended from the element on which it is invoked that match the specified group of CSS selectors.
Syntax
elementList = baseElement.querySelectorAll(selectors);
where
elementList is a non-live list of element objects.
baseElement is an element object.
selectors is a group of selectors to match on.
From the docs above you can see it does not automatically remove it when you append it to another element since it is non live. Run a demo to show that feature.
var selector = "div";
elm = document.querySelectorAll(selector);
var frag = document.createDocumentFragment();
console.log("before",elm.length);
frag.appendChild(elm[0]);
console.log("after",elm.length);
When the code above runs, in the console you get.
before 3
after 3
If you want to do the while loop, convert to an array and shift() the items off
var selector = "div";
var elmNodeLIst = document.querySelectorAll(selector);
var frag = document.createDocumentFragment();
var elems = Array.prototype.slice.call(elmNodeLIst );
while (elems.length) {
frag.appendChild(elems.shift());
}
console.log(frag);
You are appending the first item in the node list, over and over and over. You never removing any items from the array, but always adding the first one to the fragment. And the first one is always the same.
elm = document.querySelectorAll(selector);
var frag = document.createDocumentFragment();
while (elm.length){
frag.appendChild(elm.shift());
}
This may be closer to what you meant to do. We can use while (elm.length) because as items get removed form the array, eventually length will be zero which is a flasy value and the loop will stop.
And we use elm.shift() to fetch the item from the array because that method will return the item at index zero and remove it from the array, which gives us the mutation of the original array we need.
I think you thought this might work because a node can only have one parent. Meaning adding somewhere removes it from the previous parent. However, elm is not a DOM fragment. It's just a aray (or perhaps a NodeList) that holds references to element. The array is not the parent node of these elements, it just holds references.
Your loop might work if you had it like this, since you are query the parent node each time for its children, a list of node that will actually change as you move around:
elm = document.getElementById(id);
var frag = document.createDocumentFragment();
while (elm.children[0]){
frag.appendChild(elm.children[0]);
}
I wouldn't have expected it to work in the first place.
Your elm array is initialized, and never updated. Even if the result from running document.querySelectorAll(selector); would return something different, this doesn't change your current references in the array.
You would either need to rerun the selector, or manually remove the first element in the array after appending it.
elm[0] is static and unchanging in above code
fix is as below
elm = document.querySelectorAll(".container");
var frag = document.createDocumentFragment();
console.log(elm);
var i=0;
while (elm[i]){
frag.appendChild(elm[i++]);
}
I didn't actually focus much on the code (and if it made sense -judging from the comments- or not); but if this worked a few days ago then the problem is in the input you are giving to your code selector.
That's when Unit Testing comes in handy. If you can remember the input with which the code worked, then you can make it work again and start debugging from there.
Otherwise, you are just lying to yourself.
It's an infinite loop as it's written right now because elm[0] always refers to the same element, and that element is never null (any non-null/non-zero result would be true). You also don't do anything with the elements themselves to make it iterate across the list. You should be using a for loop instead of a while or at least having some kind of indexer to try to traverse the collection.
elm = document.querySelectorAll(selector);
var frag = document.createDocumentFragment();
for (i= 0; i < elm.length; i++)
{
frag.appendChild(elm[i]);
}
Edit:
From the documentation:
A "live" collection
In most cases, the NodeList is a live collection. This means that changes on the DOM tree >are going to be reflected on the collection.
var links = document.getElementsByTagName('a'); // links.length === 2 for instance
document.body.appendChild( links[0].cloneNode(true) ); // another link is added to the document
// the 'links' NodeList is automatically updated
// links.length === 3 now. If the NodeList is the return value of document.querySelectorAll, it is NOT live.
Going on this documentation, your current usage of the method indicates you do not have a live NodeList. Thus appending will never modify the original list. You will either need to modify your usage within the loop to mirror this usage of .cloneNode(true) or iterate manually.
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
}
});