Javascript childNodes - javascript

I am trying to make a childNode be invisible so that the user will not be able to see it.
function hideLetters() {
var squares = document.querySelectorAll( "#squarearea div" );
for ( var i = 0; i < squares.length; i++ ) {
squares[ i ] = hide( squares[ i ] );
}
}
function hide( squares ) {
var nodeList = squares.childNodes;
nodeList.style.display = "none";
squares.childNodes = nodeList;
return squares;
}
I have been trying to make the child nodes found within squares invisible so that they do not appear on the screen. Please note that I am only using JavaScript, HTML, and CSS for this project.

You need to apply it to every element of the node list:
squares.childNodes.forEach(node => node.style.display = "none");

Try this one
Array.prototype.slice.call(squares.childNodes).forEach(node => node.style.display = 'none')

There were a few things incorrect about your code and I took the liberty of taking out bits that didn't merit staying in given what you were trying to do.
In no way are you manipulating squares other than looping over it. In your code you said squares[i] = hide(squares[i] - not to put to fine a point on it, but this is worthless and does nothing. The list itself is a reference to the nodes, not the nodes themselves. You can think of every item in the list like a sign-post that tells the code where to look. So when that node is changed it doesn't need to be updated in the list because the list is simply saying "this is what you want to look at", it doesn't actually contain a copy of the node.
because of the reasons listed above you don't need to return anything from your hide function.
The nodeList in your hide function needs to be iterated over and each node manipulated individually. It's worth noting that you can't say "adjust all of these" at any point in JavaScript unless you yourself create a function that allows that functionality, but under the hood you're still going through every list or array one by one.
Your nodeList is aptly named. It is a list of nodes. Most people, at least the newer people to JavaScript(no shame for that, we all learn sometime), assume that tags(a.e. <div></div>, <a></a>, <span></span>) are nodes. And yes, you're right, they are! But the text within those tags are completely separate and individual nodes as well. This means that when you iterate over all the nodes you probably aren't just getting Element Nodes you might be getting Text Nodes or Document Fragment Nodes or Entity Nodes, etc.
While we iterate over the nodeList we need to separate out the nodes that we can hide(those with a style object that's able to be manipulated) and we do this by comparing the built-in nodeType property that's in every node with the Node.ELEMENT_NODE property. If it returns true we know absolutely that the node is an Element Node.
After we've checked that what we're manipulating is an element, we simply set it's display property (which is normally "block") to the value "none" and in that way hiding it on the DOM.
The code below, I think, is what you're looking for.
function hideLetters() {
let squares = document.querySelectorAll("#squarearea div");
for(let i = 0; i < squares.length; i++) {
hide( squares[i] );
});
}
function hide(squares) {
var nodeList = squares.childNodes;
for(let i = 0; i < nodeList.length; i++) {
if (nodeList[i].nodeType === Node.ELEMENT_NODE) {
nodeList[i].style.display = "none";
}
});
}
It's worth noting that you could simply use .children instead of .childNodes to return only the elements of a parent node. I don't know if you had a reason for wanting all nodes to be searched through, but this would condense the iteration down to simply setting the style property:
var nodeList = squares.children;
nodeList.forEach(node => node.style.display = "none");
function hideLetters() {
let squares = document.querySelectorAll("#squarearea div");
for (let i = 0; i < squares.length; i++) {
hide(squares[i]);
};
}
function hide(squares) {
var nodeList = squares.childNodes;
for (let i = 0; i < nodeList.length; i++) {
if (nodeList[i].nodeType === Node.ELEMENT_NODE) {
nodeList[i].style.display = "none";
}
};
}
hideLetters();
#squarearea div {
border: solid 1px black;
width: 10px;
padding: 3px;
margin: 10px;
}
<div id="squarearea">
<div><span>a</span></div>
<div><span>b</span></div>
<div><span>c</span></div>
<div><span>d</span></div>
<div><span>e</span></div>
<div><span>f</span></div>
<div><span>g</span></div>
</div>

Related

Vanilla JS: Find all the DOM elements that just contain text

I want to get all the DOM elements in an HTML that doesn't contain any node, but text only.
I've got this code right now:
var elements = document.querySelectorAll("body *");
for(var i = 0; i < elements.length; i++) {
if(!elements[i].hasChildNodes()) {
console.log(elements[i])
}
}
This prints of course elements that have absolutely no content (and curiously enough, iframes).
Texts are accounted as a child node, so the .childNodes.length equals 1, but I don't know how to distinguish the nodes from the text. typeof the first node is always object, sadly.
How to distinguish the texts from the nodes?
Basically you are looking for leaf nodes of DOM with something inside the textContent property of the leaf node.
Let's traverse DOM and work out our little logic on leaf nodes.
const nodeQueue = [ document.querySelector('html') ];
const textOnlyNodes = [];
const textRegEx = /\w+/gi;
function traverseDOM () {
let currentNode = nodeQueue.shift();
// Our Leaf node
if (!currentNode.childElementCount && textRegEx.test(currentNode.textContent)) {
textOnlyNodes.push(currentNode);
return;
}
// Nodes with child nodes
nodeQueue.push(...currentNode.children);
traverseDOM();
}
childElementCount property make sure that the node is the leaf node and the RegEx test on textContent property is just my understanding of what a text implies in general. You can anytime tune the expression to make it a btter fit for your use case.
You can check for elements that have no .firstElementChild, which means it will only have text (or other invisible stuff).
var elements = document.querySelectorAll("body *");
for (var i = 0; i < elements.length; i++) {
if (!elements[i].firstElementChild) {
console.log(elements[i].nodeName)
}
}
<p>
text and elements <span>text only</span>
</p>
<div>text only</div>
The script that the stack snippet is included because it also only has text. You can filter out scripts if needed. This will also include elements that can not have content, like <input>.

Get all the descendant nodes (also the leaves) of a certain node

I have an html document consists of a <div id="main">. Inside this div may be several levels of nodes, without a precise structure because is the user who creates the document content.
I want to use a JavaScript function that returns all nodes within div id="main". Any tag is, taking into account that there may be different levels of children.
For example, if I has this document:
...
<div id="main">
<h1>bla bla</h1>
<p>
<b>fruits</b> apple<i>text</i>.
<img src="..">image</img>
</p>
<div>
<p></p>
<p></p>
</div>
<p>..</p>
</div>
...
The function getNodes would return an array of object nodes (I don't know how to represent it, so I list them):
[h1, #text (= bla bla), p, b, #text (= fruits), #text (= _apple), i, #text (= text), img, #text (= image), div, p, p, p, #text (= ..)]
As we see from the example, you must return all nodes, even the leaf nodes (ie #text node).
For now I have this function that returns all nodes except leaf:
function getNodes() {
var all = document.querySelectorAll("#main *");
for (var elem = 0; elem < all.length; elem++) {
//do something..
}
}
In fact, this feature applied in the above example returns:
[H1, P, B, I, IMG, DIV, P, P, P]
There aren't #text nodes.
Also, if text elements returned by that method in this way:
all[elem].children.length
I obtain that (I tested on <p>fruits</p>) <p> is a leaf node.
But if I build the DOM tree it is clear that is not a leaf node, and that in this example the leaf nodes are the #text...
Thank you
Classic case for recursion into the DOM.
function getDescendants(node, accum) {
var i;
accum = accum || [];
for (i = 0; i < node.childNodes.length; i++) {
accum.push(node.childNodes[i])
getDescendants(node.childNodes[i], accum);
}
return accum;
}
and
getDescendants( document.querySelector("#main") );
Aside from the already existing and perfectly functional answer, I find it worth mentioning that one can do away with the recursion and the many resulting function calls by simply navigating via the firstChild, nextSibling, and parentNode properties:
function getDescendants(node) {
var list = [], desc = node, checked = false, i = 0;
do {
checked || (list[i++] = desc);
desc =
(!checked && desc.firstChild) ||
(checked = false, desc.nextSibling) ||
(checked = true, desc.parentNode);
} while (desc !== node);
return list;
}
(Whenever we encounter a new node, we add it to the list, then try going to its first child node. If such does not exist, get the next sibling instead. Whenever no child node or following sibling is found, we go back up to the parent, while setting the checked flag to avoid adding that to the list again or reentering its descendant tree.)
This will, in virtually every case, improve performance greatly. Not that there is nothing left to optimize here, e.g. one could cache the nodes where we descend further into the hierarchy so as to later get rid of the parentNode when coming back up. I leave implementing this as an exercise for the reader.
Keep in mind though that iterating through the DOM like this will rarely be the bottleneck in a script. Unless you are going through a large DOM tree many tens/hundreds of times a second, that is — in which case you probably ought to think about avoiding that if at all possible, rather than simply optimizing it.
the children property only returns element nodes. If you want all children, I would suggest using the childNodes property. Then you can loop through this nodeList, and eliminate nodes that have nodeType of Node.ELEMENT_NODE or pick which other node types you would be interested in
so try something like:
var i, j, nodes
var result=[]
var all = document.querySelectorAll("#main *");
for (var elem = 0; elem < all.length; elem++) {
result.push(all[elem].nodeName)
nodes = all[elem].childNodes;
for (i=0, j=nodes.length; i<j; i++) {
if (nodes[i].nodeType == Node.TEXT_NODE) {
result.push(nodes[i].nodeValue)
}
}
}
If you only need the html tags and not the #text, you can just simply use this:<elem>.querySelectorAll("*");

Javascript DOM ChildNodes property doesn't capture all children

I want to save a DOM subtree (everything under the div called "block diagram") then paint it on a div called "bus_diagram". Saving the childNodes property doesn't seem to capture all of the elements for some reason.
here's the javascript I'm using. On calling the function "dostuff()" everything under "block_diagram" should go to "bus_diagram"
var SAVED_BLOCK_DOM = null;
function save_block() {
SAVED_BLOCK_DOM = document.getElementById("block_diagram").childNodes;
}
function refresh_block() {
for (var i = 0; i < SAVED_BLOCK_DOM.length; i++) {
document.getElementById("bus_diagram").appendChild(SAVED_BLOCK_DOM[i]);
}
}
function dostuff() {
save_block();
refresh_block();
}
Here's a JSFiddle: http://jsfiddle.net/BFp5s/3/
.childNodes is a live collection of nodes and as you start doing .appendChild() on the nodes (which moves the elements to a different place in the DOM), the live collection changes while you are iterating it, causing you to miss nodes. So, when the index of your for loop is 0, you do .appendChild() on the 0th element of the list. That removes that element from the live list. You then increment your index to 1, but the next item to process is now in the 0th spot in the list causing you to process every other item.
You can either make a copy of the live list into an array (so it won't change while iterating it) or change the way you iterate the list.
For example, you can change save_block() to this:
function save_block() {
SAVED_BLOCK_DOM = Array.prototype.slice.call(document.getElementById("block_diagram").childNodes, 0);
}
This makes SAVED_BLOCK_DOM into a normal array so it won't change while you iterate it.
jsFiddle Demo: http://jsfiddle.net/jfriend00/R8c94/
Or, if you want/need to support IE6/7/8 support which won't work with the above copy mechanism, you can just copy the nodeList manually:
function save_block() {
SAVED_BLOCK_DOM = [];
var list = document.getElementById("block_diagram").childNodes;
for (var i = 0; i < list.length; i++) {
SAVED_BLOCK_DOM.push(list[i]);
}
}
If you don't need SAVED_BLOCK_DOM to continue to hold the list of nodes and want to support IE8, you can change the way you iterate like this:
function refresh_block() {
while (SAVED_BLOCK_DOM.length) {
document.getElementById("bus_diagram").appendChild(SAVED_BLOCK_DOM[0]);
}
}
jsFiddle demo: http://jsfiddle.net/jfriend00/PK7Tg/

Show/hide in javascript when no html access

I have this:
<div id="myid1">Text 1</div>
<div id="myid2">Text 2</div>
<div id="myid3">Text 3</div>
and I would hide all these elements by default. Then when I click on a link, I would like show all them at once. I looked for some solution in Javascript but it seem is not possible to declare multiple ID when using document.getElementById.
Precision: I seen many solution who suggest to use class instead ID. The problem is I work with an external application integrated in my site and I have access partially to html, but I can set javascript code inside a dedicated JS file.
You could create a function that retrieves several elements by their id, and simply iterate over that collection of elements to hide or show them:
function getElementsByIds(idArray) {
// initialise an array (over which we'll iterate, later)
var elements = [];
// if no arguments have been passed in, we quit here:
if (!arguments) {
return false;
}
else {
/* we're running a basic check to see if the first passed-argument
is an array; if it is, we use it: */
if (Object.prototype.toString.call(arguments[0]) === '[object Array]') {
idArray = idArray;
}
/* if a string has been passed-in (rather than an array), we
make an array of those strings: */
else if ('string' === typeof arguments[0]) {
idArray = [].slice.call(arguments);
}
// here we iterate over the array:
for (var i = 0, len = idArray.length; i < len; i++) {
// we test to see if we can retrieve an element by the id:
if (document.getElementById(idArray[i])) {
/* if we can, we add that found element to the array
we initialised earlier: */
elements.push(document.getElementById(idArray[i]));
}
}
}
// returning the elements:
return elements;
}
// here we toggle the display of the elements (between 'none' and 'block':
function toggle (elems) {
// iterating over each element in the passed-in array:
for (var i = 0, len = elems.length; i < len; i++) {
/* if the current display is (exactly) 'none', we change to 'block'
otherwise we change it to 'none': */
elems[i].style.display = elems[i].style.display === 'none' ? 'block' : 'none';
}
}
function hide (nodes) {
// iterating over the passed-in array of nodes
for (var i = 0, len = nodes.length; i < len; i++) {
// setting each of their display properties to 'none':
nodes[i].style.display = 'none';
}
}
// getting the elements:
var relevantElements = getElementsByIds('myid1','myid2','myid3'),
toggleButton = document.getElementById('buttonThatTogglesVisibilityId');
// binding the click-handling functionality of the button:
toggleButton.onclick = function(){
toggle (relevantElements);
};
// initially hiding the elements:
hide (relevantElements);
JS Fiddle demo.
References:
arguments keyword.
Array.prototype.push().
Array.protoype.slice().
document.getElementById().
Function.prototype.call().
Object.prototype.toString().
typeof.
Three options (at least):
1) Wrap them all in a parent container if this is an option. Then you can just target that, rather than the individual elements.
document.getElementById('#my_container').style.display = 'block';
2) Use a library like jQuery to target multiple IDs:
$('#myid1, #myid2, #myid3').show();
3) Use some ECMA5 magic, but it won't work in old browsers.
[].forEach.call(document.querySelectorAll('#myid1, #myid2, #myid3'), function(el) {
el.style.display = 'block'; //or whatever
});
If id="myid< increment-number >" then you can select these elements very easily.
Below code will select all elements that START WITH "myid".
$("div[id^='myid']").each(function (i, el) {
//do what ever you want here
}
See jquery doc
http://api.jquery.com/attribute-contains-selector/

how to find common in inline styles?

I have an array of sequential dom element nodes which may or may not have inline styles. I need to end up with an object or array with only keys and values common to all the nodes. Needs to work in IE8+, chrome and FF.
I can't even get one nodes styles into an array without a bunch of other stuff being included as well.
I've tried to use node[x].style but it seems to return a lot of extraneous stuff and other problems.
//g is node array
s=[];
for(k in g)
{
if(g.hasOwnProperty(k) && g[k]) s[k]=g[k];
}
console.log(s);
gives me ["font-weight", cssText: "font-weight: bold;", fontWeight: "bold"] which is close but I only want fontWeight: "bold" in the array. In any case, this only works in chrome.
The only idea I have at the moment that might work is using the cssText and splitting on semi-colons and splitting again on colons but that seems an ugly and slow way to do it especially as I then need to compare to a bunch of nodes and do the same to their styles.
So, I'm hoping someone can come up with a simple elegant solution to the problem posed in the first paragraph.
If you truly want ONLY styles that are specified inline in the HTML for the object, then you will have to deal with text of the style attribute as you surmised.
The .style property will show you more styles than were specified on the object itself (showing you default values for some styles) so you can't use that.
Here's a function that takes a collection of DOM nodes and returns a map of common styles (styles that are specified inline and are the same property and value on every object):
function getCommonStyles(elems) {
var styles, styleItem, styleCollection = {}, commonStyles = {}, prop, val;
for (var i = 0; i < elems.length; i++) {
var styleText = elems[i].getAttribute("style");
if (styleText) {
// split into an array of individual style strings
styles = styleText.split(/\s*;\s*/);
for (var j = 0; j < styles.length; j++) {
// split into the two pieces of a style
styleItem = styles[j].split(/\s*:\s*/);
// only if we found exactly two pieces should we count this one
if (styleItem.length === 2) {
prop = styleItem[0];
val = styleItem[1];
// if we already have this style property in our collection
if (styleCollection[prop]) {
// if same value, then increment the cntr
if (styleCollection[prop].value === val) {
++styleCollection[prop].cntr;
}
} else {
// style tag didn't exist so add it
var newTag = {};
newTag.value = val;
newTag.cntr = 1;
styleCollection[prop] = newTag;
}
}
}
}
}
// now go through the styleCollection and put the ones in the common styles
// that were present for every element
for (var prop in styleCollection) {
if (styleCollection[prop].cntr === elems.length) {
commonStyles[prop] = styleCollection[prop].value;
}
}
return(commonStyles);
}
Working demo: http://jsfiddle.net/jfriend00/JW7CZ/

Categories

Resources