How to write Javascript to search nodes - without getElementsByClassName - javascript

I'm very new at recursion, and have been tasked with writing getElementsByClassName in JavaScript without libraries or the DOM API.
There are two matching classes, one of which is in the body tag itself, the other is in a p tag.
The code I wrote isn't working, and there must be a better way to do this. Your insight would be greatly appreciated.
var elemByClass = function(className) {
var result = [];
var nodes = document.body; //<body> is a node w/className, it needs to check itself.
var childNodes = document.body.childNodes; //then there's a <p> w/className
var goFetchClass = function(nodes) {
for (var i = 0; i <= nodes; i++) { // check the parent
if (nodes.classList == className) {
result.push(i);
console.log(result);
}
for (var j = 0; j <= childNodes; j++) { // check the children
if (childNodes.classList == className) {
result.push(j);
console.log(result);
}
goFetchClass(nodes); // recursion for childNodes
}
goFetchClass(nodes); // recursion for nodes (body)
}
return result;
};
};

There are some errors, mostly logical, in your code, here's what it should have looked like
var elemByClass = function(className) {
var result = [];
var pattern = new RegExp("(^|\\s)" + className + "(\\s|$)");
(function goFetchClass(nodes) {
for (var i = 0; i < nodes.length; i++) {
if ( pattern.test(nodes[i].className) ) {
result.push(nodes[i]);
}
goFetchClass(nodes[i].children);
}
})([document.body]);
return result;
};
Note the use of a regex instead of classList, as it makes no sense to use classList which is IE10+ to polyfill getElementsByClassName
Firstly, you'd start with the body, and check it's className property.
Then you'd get the children, not the childNodes as the latter includes text-nodes and comments, which can't have classes.
To recursively call the function, you'd pass the children in, and do the same with them, check for a class, get the children of the children, and call the function again, until there are no more children.

Here are some reasons:
goFetchClass needs an initial call after you've defined it - for example, you need a return goFetchClass(nodes) statement at the end of elemByClass function
the line for (var i = 0; i <= nodes; i++) { will not enter the for loop - did you mean i <= nodes.length ?
nodes.classList will return an array of classNames, so a direct equality such as nodes.classList == className will not work. A contains method is better.
Lastly, you may want to reconsider having 2 for loops for the parent and children. Why not have 1 for loop and then call goFetchClass on the children? such as, goFetchClass(nodes[i])?
Hope this helps.

Related

Split a String Array and Store Values for each Iteration

So the variable classList stores all classes for the body. I then created a variable classListLength that has the length of classList so I can iterate through each index and then split each class. I do not know how to store the splits for each index as it loops through classList. Help me please.
var classList = jQuery('body').attr('class').split(' ');
var classListLength = classList.length;
var keyWords = function(array) {
for (var i = 0; i < classListLength; i++ ) {
classList[i].split('-');
}
}
if i do the following in the console
var keyWords = function(array) {
for (var i = 0; i < classListLength; i++ ) {
console.log(classList[i].split('-'));
}
}
I can see exactly what I want but I want to be able to store that and check it later on with a conditional.
var splitClassList = classList.map(function (class) {
return class.split('-');
});
So I solved what I needed. The below code allows me to iterate through my split classList. I then check the the specific class I want within the classList using .includes method and execute what I need done. If you guys know how to make this a bit more modular please chime in.
var brandClass
// Iterate though split classes
jQuery.each( classList, function(i) {
if ( classList[i].includes('product-wildridge') ) {
brandClass = classList[i];
}
});
// check specific class for certin keywords
var customTab = function(brandClass) {
if (brandClass.includes('wildridge') && brandClass.includes('deep') ) {
return true;
} else {
jQuery('#tab-fabric').css('display', 'none');
}
}
customTab(brandClass);

Modify all elements with the same tag name

In jQuery simply adding $("div") selects all elements with a tag name of <div>.
Suppose I wanted to do this in pure JavaScript, I would try entering this: document.getElementsByTagName("div"), but this only returns a list of elements.
You cannot edit these elements all at once, you have to select them in order of occurrence, like so: document.getElementsByTagName("div")[*occurrence*].
Is there any way to select all of these elements at once and store them in a variable?
Thanks.
Is there any way to select all of these elements at once and store them in a variable?
getElementsByTagName (or querySelectorAll) is how you do that.
But no, the DOM doesn't define functions that act on lists of elements the way jQuery does. You can, of course, write ones yourself.
function setValue(list, value) {
var n;
for (n = 0; n < list.length; ++n) {
list[n].value = value;
}
};
jQuery's set-based approach is, I suspect, a big part of its appeal. That and the fact that by encapsulating sets of elements with mutators and accessors, it lets you chain things together. You could do that yourself, too, of course:
function MyList(selector) {
this.elements = document.querySelectorAll(selector);
}
MyList.prototype.setAttr = function(attr, value) {
var n;
for (n = 0; n < list.length; ++n) {
this.elements[n].setAttribute(attr, value);
}
return this;
};
MyList.prototype.setHTML = function(html) {
var n;
for (n = 0; n < list.length; ++n) {
this.elements[n].innerHTML = html;
}
return this;
};
// Usage
var list = new MyList("div");
list.setHTML("hi there").setAttr("data-foo", "bar");
Naturally those are quite primitive compared with jQuery's versions...
I have made a JSFiddle that shows how to use all found divs using a for ... in loop:
HTML
<div>text1</div>
<div>text2</div>
<div>text4</div>
<div>text3</div>
<span id="orderOfOutput"></span>
JavaScript
var allDivs = document.getElementsByTagName('div');
for (var i in allDivs) {
if (isNaN(i)) continue;//we only want the numbered indexes
document.getElementById('orderOfOutput').innerHTML += '<span>Output div:' + allDivs[i].innerText + '</span><br />';
}
To store them in a variable just replace the innerHTML line with divSelection[i] = allDivs[i];

How to get all childNodes in JS including all the 'grandchildren'?

I want to scan a div for all childNodes including the ones that are nestled within other elements. Right now I have this:
var t = document.getElementById('DivId').childNodes;
for(i=0; i<t.length; i++) alert(t[i].id);
But it only gets the children of the Div and not the grandchildren. Thanks!
Edit: This question was too vague. Sorry about that. Here's a fiddle:
http://jsfiddle.net/F6L2B/
The body.onload script doesn't run at JSFiddle, but it works, except that the 'Me Second' and 'Me Third' input fields are not being assigned a tabIndex and are therefore being skipped over.
This is the fastest and simplest way, and it works on all browsers:
myDiv.getElementsByTagName("*")
If you're looking for all HTMLElement on modern browsers you can use:
myDiv.querySelectorAll("*")
What about great-grandchildren?
To go arbitrarily deep, you could use a recursive function.
var alldescendants = [];
var t = document.getElementById('DivId').childNodes;
for(let i = 0; i < t.length; i++)
if (t[i].nodeType == 1)
recurseAndAdd(t[i], alldescendants);
function recurseAndAdd(el, descendants) {
descendants.push(el.id);
var children = el.childNodes;
for(let i=0; i < children.length; i++) {
if (children[i].nodeType == 1) {
recurseAndAdd(children[i]);
}
}
}
If you really only want grandchildren, then you could take out the recursion (and probably rename the function)
function recurseAndAdd(el, descendants) {
descendants.push(el.id);
var children = el.childNodes;
for(i=0; i < children.length; i++) {
if (children[i].nodeType == 1) {
descendants.push(children[i].id);
}
}
}
If anyone else wants all nodes within that tree, and not just the elements, here's a 2022-era JS snippet:
function getDescendantNodes(node, all = []) {
all.push(...node.childNodes);
for (const child of node.childNodes)
getDescendantNodes(child, all);
return all;
}
Protip: afterwards, you can filter to your preferred nodeType with the Node global: nodes = getDescendantNodes($0); nodes.filter(n => n.nodeType === Node.TEXT_NODE)

How to Get Element By Class in JavaScript?

I want to replace the contents within a html element so I'm using the following function for that:
function ReplaceContentInContainer(id,content) {
var container = document.getElementById(id);
container.innerHTML = content;
}
ReplaceContentInContainer('box','This is the replacement text');
<div id='box'></div>
The above works great but the problem is I have more than one html element on a page that I want to replace the contents of. So I can't use ids but classes instead. I have been told that javascript does not support any type of inbuilt get element by class function. So how can the above code be revised to make it work with classes instead of ids?
P.S. I don't want to use jQuery for this.
This code should work in all browsers.
function replaceContentInContainer(matchClass, content) {
var elems = document.getElementsByTagName('*'), i;
for (i in elems) {
if((' ' + elems[i].className + ' ').indexOf(' ' + matchClass + ' ')
> -1) {
elems[i].innerHTML = content;
}
}
}
The way it works is by looping through all of the elements in the document, and searching their class list for matchClass. If a match is found, the contents is replaced.
jsFiddle Example, using Vanilla JS (i.e. no framework)
Of course, all modern browsers now support the following simpler way:
var elements = document.getElementsByClassName('someClass');
but be warned it doesn't work with IE8 or before. See http://caniuse.com/getelementsbyclassname
Also, not all browsers will return a pure NodeList like they're supposed to.
You're probably still better off using your favorite cross-browser library.
document.querySelectorAll(".your_class_name_here");
That will work in "modern" browsers that implement that method (IE8+).
function ReplaceContentInContainer(selector, content) {
var nodeList = document.querySelectorAll(selector);
for (var i = 0, length = nodeList.length; i < length; i++) {
nodeList[i].innerHTML = content;
}
}
ReplaceContentInContainer(".theclass", "HELLO WORLD");
If you want to provide support for older browsers, you could load a stand-alone selector engine like Sizzle (4KB mini+gzip) or Peppy (10K mini) and fall back to it if the native querySelector method is not found.
Is it overkill to load a selector engine just so you can get elements with a certain class? Probably. However, the scripts aren't all that big and you will may find the selector engine useful in many other places in your script.
A Simple and an easy way
var cusid_ele = document.getElementsByClassName('custid');
for (var i = 0; i < cusid_ele.length; ++i) {
var item = cusid_ele[i];
item.innerHTML = 'this is value';
}
I'm surprised there are no answers using Regular Expressions. This is pretty much Andrew's answer, using RegExp.test instead of String.indexOf, since it seems to perform better for multiple operations, according to jsPerf tests.
It also seems to be supported on IE6.
function replaceContentInContainer(matchClass, content) {
var re = new RegExp("(?:^|\\s)" + matchClass + "(?!\\S)"),
elems = document.getElementsByTagName('*'), i;
for (i in elems) {
if (re.test(elems[i].className)) {
elems[i].innerHTML = content;
}
}
}
replaceContentInContainer("box", "This is the replacement text.");
If you look for the same class(es) frequently, you can further improve it by storing the (precompiled) regular expressions elsewhere, and passing them directly to the function, instead of a string.
function replaceContentInContainer(reClass, content) {
var elems = document.getElementsByTagName('*'), i;
for (i in elems) {
if (reClass.test(elems[i].className)) {
elems[i].innerHTML = content;
}
}
}
var reBox = /(?:^|\s)box(?!\S)/;
replaceContentInContainer(reBox, "This is the replacement text.");
This should work in pretty much any browser...
function getByClass (className, parent) {
parent || (parent=document);
var descendants=parent.getElementsByTagName('*'), i=-1, e, result=[];
while (e=descendants[++i]) {
((' '+(e['class']||e.className)+' ').indexOf(' '+className+' ') > -1) && result.push(e);
}
return result;
}
You should be able to use it like this:
function replaceInClass (className, content) {
var nodes = getByClass(className), i=-1, node;
while (node=nodes[++i]) node.innerHTML = content;
}
var elems = document.querySelectorAll('.one');
for (var i = 0; i < elems.length; i++) {
elems[i].innerHTML = 'content';
};
I assume this was not a valid option when this was originally asked, but you can now use document.getElementsByClassName('');. For example:
var elements = document.getElementsByClassName(names); // or:
var elements = rootElement.getElementsByClassName(names);
See the MDN documentation for more.
There are 3 different ways to get elements by class in javascript. But here for your query as you have multiple elements with the same class names you can use 2 methods:
getElementsByClassName Method - It returns all the elements with the specified class present in the document or within the parent element which called it.
function ReplaceContentInContainer(className, content) {
var containers = document.getElementsByClassName(className);
for (let i = 0; i < containers.length; i++) {
containers[i].innerHTML = content;
}
}
ReplaceContentInContainer('box', 'This is the replacement text');
<div class='box'></div>
querySelectorAll Method - It select element on the basic of CSS selectors. Pass your CSS class to it with a dot and it will return all the element having specified class as an array-like object.
function ReplaceContentInContainer(className, content) {
var containers = document.querySelectorAll(`.${className}`);
for (let i = 0; i < containers.length; i++) {
containers[i].innerHTML = content;
}
}
ReplaceContentInContainer('box', 'This is the replacement text');
<div class='box'></div>
I think something like:
function ReplaceContentInContainer(klass,content) {
var elems = document.getElementsByTagName('*');
for (i in elems){
if(elems[i].getAttribute('class') == klass || elems[i].getAttribute('className') == klass){
elems[i].innerHTML = content;
}
}
}
would work
jQuery handles this easy.
let element = $(.myclass);
element.html("Some string");
It changes all the .myclass elements to that text.
When some elements lack ID, I use jQuery like this:
$(document).ready(function()
{
$('.myclass').attr('id', 'myid');
});
This might be a strange solution, but maybe someone find it useful.

How do I make this loop all children recursively?

I have the following:
for (var i = 0; i < children.length; i++){
if(hasClass(children[i], "lbExclude")){
children[i].parentNode.removeChild(children[i]);
}
};
I would like it to loop through all children's children, etc (not just the top level). I found this line, which seems to do that:
for(var m = n.firstChild; m != null; m = m.nextSibling) {
But I'm unclear on how I refer to the current child if I make that switch? I would no longer have i to clarify the index position of the child. Any suggestions?
Thanks!
Update:
I'm now using the following, according to answer suggestions. Is this the correct / most efficient way of doing so?
function removeTest(child) {
if (hasClass(child, "lbExclude")) {
child.parentNode.removeChild(child);
}
}
function allDescendants(node) {
for (var i = 0; i < node.childNodes.length; i++) {
var child = node.childNodes[i];
allDescendants(child);
removeTest(child);
}
}
var children = temp.childNodes;
for (var i = 0; i < children.length; i++) {
allDescendants(children[i]);
};
function allDescendants (node) {
for (var i = 0; i < node.childNodes.length; i++) {
var child = node.childNodes[i];
allDescendants(child);
doSomethingToNode(child);
}
}
You loop over all the children, and for each element, you call the same function and have it loop over the children of that element.
Normally you'd have a function that could be called recursively on all nodes. It really depends on what you want to do to the children. If you simply want to gather all descendants, then element.getElementsByTagName may be a better option.
var all = node.getElementsByTagName('*');
for (var i = -1, l = all.length; ++i < l;) {
removeTest(all[i]);
}
There's no need for calling the 'allDescendants' method on all children, because the method itself already does that. So remove the last codeblock and I think that is a proper solution (á, not thé =])
function removeTest(child){
if(hasClass(child, "lbExclude")){
child.parentNode.removeChild(child);
}
}
function allDescendants (node) {
for (var i = 0; i < node.childNodes.length; i++) {
var child = node.childNodes[i];
allDescendants(child);
removeTest(child);
}
}
var children = allDescendants(temp);
You can use BFS to find all the elements.
function(element) {
// [].slice.call() - HTMLCollection to Array
var children = [].slice.call(element.children), found = 0;
while (children.length > found) {
children = children.concat([].slice.call(children[found].children));
found++;
}
return children;
};
This function returns all the children's children of the element.
The most clear-cut way to do it in modern browsers or with babel is this. Say you have an HTML node $node whose children you want to recurse over.
Array.prototype.forEach.call($node.querySelectorAll("*"), function(node) {
doSomethingWith(node);
});
The querySelectorAll('*') on any DOM node would give you all the child nodes of the element in a NodeList. NodeList is an array-like object, so you can use the Array.prototype.forEach.call to iterate over this list, processing each child one-by-one within the callback.
If you have jquery and you want to get all descendant elements you can use:
var all_children= $(parent_element).find('*');
Just be aware that all_children is an HTML collection and not an array. They behave similarly when you're just looping, but collection doesn't have a lot of the useful Array.prototype methods you might otherwise enjoy.
if items are being created in a loop you should leave a index via id="" data-name or some thing. You can then index them directly which will be faster for most functions such as (!-F). Works pretty well for 1024 bits x 100 items depending on what your doing.
if ( document.getElementById( cid ) ) {
return;
} else {
what you actually want
}
this will be faster in most cases once the items have already been loaded. only scrub the page on reload or secure domain transfers / logins / cors any else and your doing some thing twice.
If you use a js library it's as simple as this:
$('.lbExclude').remove();
Otherwise if you want to acquire all elements under a node you can collect them all natively:
var nodes = node.getElementsByTagName('*');
for (var i = 0; i < nodes.length; i++) {
var n = nodes[i];
if (hasClass(n, 'lbExclude')) {
node.parentNode.removeChild(node);
}
}
To get all descendants as an array, use this:
function getAllDescendants(node) {
var all = [];
getDescendants(node);
function getDescendants(node) {
for (var i = 0; i < node.childNodes.length; i++) {
var child = node.childNodes[i];
getDescendants(child);
all.push(child);
}
}
return all;
}
TreeNode node = tv.SelectedNode;
while (node.Parent != null)
{
node = node.Parent;
}
CallRecursive(node);
private void CallRecursive(TreeNode treeNode)
{
foreach (TreeNode tn in treeNode.Nodes)
{
//Write whatever code here this function recursively loops through all nodes
CallRecursive(tn);
}
}

Categories

Resources