replace all elements belonging to a specific class - javascript

I was trying to develop an embedded widget. User would include an anchor tag and a javascript in their page, and it would render the content. Similar to embedded tweets.
<a href="http://localhost:3000/user/13"
target="_blank"
class="my-widget"
data-widget-type="profile"
data-widget-identifier="id"
data-identifier-value="13"
>Sayantan Das</a>
</div>
<script src="//localhost/my-widget/js/widget.js" async ></script>
And from widget.js, i would get all the elements with class="my-widget" and replace with an iframe.
widgets.js
!function(global, document) {
global.MyWidgets = global.MyWidgets || {};
for(let widgets = document.getElementsByClassName('my-widget'), i = 0; i < widgets.length; i++) {
console.log(widgets)
let element = widgets[i];
let span = document.createElement('span');
span.innerHTML = "Changed from widget " + i;
element.parentNode.appendChild(span);
element.parentNode.removeChild(element);
}
}(window, document);
The problem is , when I remove the element the loop also runs for a shorter number. for example, if there are two elements with class my-widget, after first time the loop runs and one element is removed and the loop runs only once. How do I replace all the elements?

That's because getElementsByClassName returns a live HTMLCollection; when you remove the class="my-widget" element from the DOM, it's also removed from the collection.
Either work backward through the collection (so you're removing from the end, which doesn't affect the ones before it), or use querySelectorAll(".my-widget") instead, which returns a snapshot NodeList, not a live HTMLCollection.
Working backward:
for(let widgets = document.getElementsByClassName('my-widget'), i = widgets.length - 1; i >= 0; i--) {
Using qSA instead:
for(let widgets = document.querySelectorAll('.my-widget'), i = 0; i < widgets.length; i++) {
or if you don't need i (you seem only to be using it to get the element and for demo purposes), you can use for-of with NodeLists now (on most platforms; this answer has a polyfill for others):
for (const element of document.querySelectorAll('.my-widget')) {
// ...and remove the `let element = ...` line

use document.querySelectorAll to the length of the widgets

Related

Using array to store elements doesn't let you change the properties

const divArr = [];
for (let i = 0; i < 5; i++) {
document.getElementById("h").innerHTML += `<div id=${i}>hello</div>`;
divArr.push(document.getElementById(`${i}`));
}
console.log(divArr);
let divArrIndex = 0;
setInterval(() => {
document.getElementById(`${divArrIndex}`).style.backgroundColor = 'green';
if (divArrIndex > 4) {
divArrIndex = 0;
}
divArrIndex += 1;
}, 1000 / 1);
<div id="h">alsdjf;lkajsdf</div>
The code above successfully turns all the divs green.
But, when I use
divArr[divArrIndex].style.backgroundColor = "green"
instead of
document.getElementById(`${divArrIndex}`).style.backgroundColor='green';
I only get the last div green.
Why?
codepen: https://codepen.io/sai-nallani/pen/KKopOXZ?editors=1111
By reassignment to innerHTML, you are destroying and recreating the contents of #h in each iteration of the loop. You create #0, then discard it and create #0 and #1, then discard those and create #0, #1, #2... So the elements you push into the array don't exist any more in your document (though references to them in divArr still keep the garbage collector from destroying them outright).
When you change the colour of divArr[0], you are changing the colour of an element that only exists in memory, but is not in DOM any more. However, #4 is still the original #4, it has not been discarded, since you have performed no further assignments to innerHTML.
One solution is to gather all the divs after you have constructed them all. You can use another loop, but the easiest way would be:
const divArr = Array.from(document.querySelectorAll('#h > div'));
(Depending on what you are doing with it next, you may not need Array.from since NodeList should suffice for many purposes.)
Another solution is to construct elements in their own right, not by changing the parent's innerHTML:
const hEl = document.querySelector('#h');
for (let i = 0; i < 5; i++) {
const divEl = document.createElement('div');
divEl.textContent = 'Hello';
divEl.id = i;
hEl.appendChild(divEl);
divArr.push(divEl);
}
This way, every element is created and added to #h without affecting any other elements already there.

Beginner JavaScript for loop

I was wondering if anyone knows the reason for [i-1] in the fourth line when you also have i++ in the second line? Thank you for any help! (It's from the book "JavaScript" by Vodnik and Gosselin.)
if (figureCount === 3) {
for (var i = 1; i < 4; i++) {
filename = "images/IMG_0" + photoOrder[i] + "sm.jpg";
currentFig = document.getElementsByTagName("img")[i - 1];
currentFig.src = filename;
}//end of for loop
It because document.getElementsByTagName returns an HTMLCollection(similar to array) hence. So accessing the 1st(and subsequent) img tag on the page are done through setting i-1
document.getElementsByTagName return a HTMLCollection which is not an array but an array like object. So to access any element from that collection you can pass the index.
document.getElementsByTagName("img")[i - 1] is creating a collection of all the img tags & it is accessing specific element in that collection by passing the index [i-1]
In the below example [1] is trying to access the second element from the collection
var getAllDiv = document.getElementsByTagName('div');
console.log(getAllDiv[1].innerHTML)
<div>1</div>
<div>2</div>
Some developers get confused with the for loop operators logic instead of doing it correctly:
for (var i = 0; i < 3; i++) {
they decided to add some extra processing to the mix ( which isn't that big of a deal ) but iv'e fired developers for less.
CurrentFig is using i - 1 because it appears there is a prepended img element so the developer also chose to select it as well instead of selecting the exact elements that he needs.

javascript getElementsByClassName("foo"||"bar")

Basically I want a snippet of javascript to add the height of the browser window to any elements with either of the classes fullscreen or fullheight
function fullsize(){
var a = document.getElementsByClassName("fullscreen"),
b = document.getElementsByClassName("fullheight");
for (var i=0; i<a.length; i++){
a[i].style.height = window.innerHeight + "px";
}
for (var j=0; j<b.length; j++){
b[j].style.height = window.innerHeight + "px";
}
}
window.addEventListener("load", fullsize),
window.addEventListener("resize", fullsize);
This (above) works perfectly well, I was just wondering if there was any way to condense it into just one for(){...} like the jQuery equiv.
function fullsizes(){
$(".fullheight,.fullscreen").each(function(){
$(this).height($(window).height())
})
}
You can use document.querySelectorAll
Returns a list of the elements within the document (using depth-first pre-order traversal of the document's nodes) that match the specified group of selectors. The object returned is a NodeList.
Code example
document.querySelectorAll(".fullscreen, .fullheight");
Change your to code
function fullsize(){
var a = document.querySelectorAll(".fullscreen, .fullheight");
for (var i=0; i<a.length; i++){
a[i].style.height = window.innerHeight + "px";
}
}
If you really have to use javascript instead of writing some css
style.css:
.fullscreen, .fullheight {
height: 100vh;
}
You can use document.querySelectorAll like Satpal said.
You can use querySelector() or querySelectorAll() to do this:
querySelector
Returns the first matching Element node within the node's subtree. If no matching node is found, null is returned.
querySelectorAll
Returns a NodeList containing all matching Element nodes within the node's subtree, or an empty NodeList if no matches are found.
You might also find the following website useful; it contains examples of how you can do jQuery-like things without using jQuery: http://youmightnotneedjquery.com/
You can merge arrays with the concat method
var c = a.concat(b)
And then you can iterate over c instead.

How can I loop through ALL DOM elements on a page?

I'm trying to loop over ALL elements on a page, so I want to check every element that exists on this page for a special class.
So, how do I say that I want to check EVERY element?
You can pass a * to getElementsByTagName() so that it will return all elements in a page:
var all = document.getElementsByTagName("*");
for (var i=0, max=all.length; i < max; i++) {
// Do something with the element here
}
Note that you could use querySelectorAll(), if it's available (IE9+, CSS in IE8), to just find elements with a particular class.
if (document.querySelectorAll)
var clsElements = document.querySelectorAll(".mySpeshalClass");
else
// loop through all elements instead
This would certainly speed up matters for modern browsers.
Browsers now support foreach on NodeList. This means you can directly loop the elements instead of writing your own for loop.
document.querySelectorAll('*').forEach(function(node) {
// Do whatever you want with the node object.
});
Performance note - Do your best to scope what you're looking for by using a specific selector. A universal selector can return lots of nodes depending on the complexity of the page. Also, consider using document.body.querySelectorAll instead of document.querySelectorAll when you don’t care about <head> children.
Was looking for same. Well, not exactly. I only wanted to list all DOM Nodes.
var currentNode,
ni = document.createNodeIterator(document.documentElement, NodeFilter.SHOW_ELEMENT);
while(currentNode = ni.nextNode()) {
console.log(currentNode.nodeName);
}
To get elements with a specific class, we can use filter function.
var currentNode,
ni = document.createNodeIterator(
document.documentElement,
NodeFilter.SHOW_ELEMENT,
function(node){
return node.classList.contains('toggleable') ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT;
}
);
while(currentNode = ni.nextNode()) {
console.log(currentNode.nodeName);
}
Found solution on
MDN
As always the best solution is to use recursion:
loop(document);
function loop(node){
// do some thing with the node here
var nodes = node.childNodes;
for (var i = 0; i <nodes.length; i++){
if(!nodes[i]){
continue;
}
if(nodes[i].childNodes.length > 0){
loop(nodes[i]);
}
}
}
Unlike other suggestions, this solution does not require you to create an array for all the nodes, so its more light on the memory. More importantly, it finds more results. I am not sure what those results are, but when testing on chrome it finds about 50% more nodes compared to document.getElementsByTagName("*");
Here is another example on how you can loop through a document or an element:
function getNodeList(elem){
var l=new Array(elem),c=1,ret=new Array();
//This first loop will loop until the count var is stable//
for(var r=0;r<c;r++){
//This loop will loop thru the child element list//
for(var z=0;z<l[r].childNodes.length;z++){
//Push the element to the return array.
ret.push(l[r].childNodes[z]);
if(l[r].childNodes[z].childNodes[0]){
l.push(l[r].childNodes[z]);c++;
}//IF
}//FOR
}//FOR
return ret;
}
For those who are using Jquery
$("*").each(function(i,e){console.log(i+' '+e)});
Andy E. gave a good answer.
I would add, if you feel to select all the childs in some special selector (this need happened to me recently), you can apply the method "getElementsByTagName()" on any DOM object you want.
For an example, I needed to just parse "visual" part of the web page, so I just made this
var visualDomElts = document.body.getElementsByTagName('*');
This will never take in consideration the head part.
from this link
javascript reference
<html>
<head>
<title>A Simple Page</title>
<script language="JavaScript">
<!--
function findhead1()
{
var tag, tags;
// or you can use var allElem=document.all; and loop on it
tags = "The tags in the page are:"
for(i = 0; i < document.all.length; i++)
{
tag = document.all(i).tagName;
tags = tags + "\r" + tag;
}
document.write(tags);
}
// -->
</script>
</head>
<body onload="findhead1()">
<h1>Heading One</h1>
</body>
</html>
UPDATE:EDIT
since my last answer i found better simpler solution
function search(tableEvent)
{
clearResults()
document.getElementById('loading').style.display = 'block';
var params = 'formAction=SearchStocks';
var elemArray = document.mainForm.elements;
for (var i = 0; i < elemArray.length;i++)
{
var element = elemArray[i];
var elementName= element.name;
if(elementName=='formAction')
continue;
params += '&' + elementName+'='+ encodeURIComponent(element.value);
}
params += '&tableEvent=' + tableEvent;
createXmlHttpObject();
sendRequestPost(http_request,'Controller',false,params);
prepareUpdateTableContents();//function js to handle the response out of scope for this question
}
Getting all elements using var all = document.getElementsByTagName("*"); for (var i=0, max=all.length; i < max; i++); is ok if you need to check every element but will result in checking or looping repeating elements or text.
Below is a recursion implementation that checks or loop each element of all DOM elements only once and append:
(Credits to #George Reith for his recursion answer here: Map HTML to JSON)
function mapDOMCheck(html_string, json) {
treeObject = {}
dom = new jsdom.JSDOM(html_string) // use jsdom because DOMParser does not provide client-side Window for element access
document = dom.window.document
element = document.querySelector('html')
// Recurse and loop through DOM elements only once
function treeHTML(element, object) {
var nodeList = element.childNodes;
if (nodeList != null) {
if (nodeList.length) {
object[element.nodeName] = []; // IMPT: empty [] array for parent node to push non-text recursivable elements (see below)
for (var i = 0; i < nodeList.length; i++) {
console.log("nodeName", nodeList[i].nodeName);
if (nodeList[i].nodeType == 3) { // if child node is **final base-case** text node
console.log("nodeValue", nodeList[i].nodeValue);
} else { // else
object[element.nodeName].push({}); // push {} into empty [] array where {} for recursivable elements
treeHTML(nodeList[i], object[element.nodeName][object[element.nodeName].length - 1]);
}
}
}
}
}
treeHTML(element, treeObject);
}
Use *
var allElem = document.getElementsByTagName("*");
for (var i = 0; i < allElem.length; i++) {
// Do something with all element here
}
i think this is really quick
document.querySelectorAll('body,body *').forEach(function(e) {
You can try with
document.getElementsByClassName('special_class');

how to access element whose id is variable

I need to access elements in html file using javascript, their names are like arr_1, arr_2, arr_3, I wish to use a loop to dynamically create the id then to access them like below:
for(var i=0; i< 10; i++) {
var id = "arr_" + i;
$document.getElementById('id')....
}
But it doesn't work. I remember there is an function to allow me do that, anyone know what that is?
You don't need the dollar sign preceding document, and you should pass your id variable to the getElementById function, not a string containing 'id':
for(var i=0; i< 10; i++) {
var id = "arr_" + i;
var element = document.getElementById(id);
// work with element
}
You might also want to check if getElementById actually found your element before manipulating it, to avoid run-time errors:
if (element) {
element.style.color = '#ff0000';
}
for (var i = 0; i < 10; i++) {
var obj = document.getElementById("arr_" + i);
obj.style.border = "1px solid red";
}
change
$document.getElementById('id')
to
$document.getElementById(id)
Since this question was published and answered quite correctly several times by using document.getElementById(id), another contender has entered the fray, querySelector, which takes any valid CSS selector and returns the first matched element.
Note that because querySelector takes a CSS selector, selecting an ID requires prefixing the ID with #.
for(var i=0; i< 10; i++) {
// Added the # here in the id variable.
var id = "#arr_" + i;
// assuming $document is a reference to window.document in the browser...
var element = $document.querySelector(id);
if (element) {
// do something with element
}
}
getElementById is typically faster than querySelector (in some browsers, twice as fast), which makes sense, since it doesn't have to invoke the CSS subsystem to find the element.
However, the option exists, and Stack Overflow is nothing if not thorough when answering questions.

Categories