Isn't text node a child when using children function? - javascript

I have the following html code:
<h1>
When
<!-- Green highlight effect -->
<span class="highlight">banking</span>
meets<br />
<span class="highlight">minimalist</span>
</h1>
And i wrote the following code in js:
const h1 = document.querySelector('h1');
// Going downwards: child
console.log(h1.childNodes); //prints text element(**When**)
console.log(h1.children); // doesn't print text element (**When**)
i understand that children function prints only direct child, but As far as i understand, When is indeed direct child of h1.
Can someone explain me if i'm wrong?

Isn't text node a child when using children function?
Yes. Text nodes are nodes and can be child nodes of an element.
doesn't print text element
There's no such thing as a text element (at least in HTML, SVG has one). Element nodes and text nodes are different kinds of nodes.
You're reading too much into the name of the property.
See the MDN documentation:
children is a read-only property that returns a live HTMLCollection which contains all of the child elements
or the specification:
The children attribute’s getter must return an HTMLCollection collection rooted at this matching only element children.
If you want to get all the nodes, including the text nodes, then use childNodes. DOM doesn't need two properties that do the same thing and it is often useful to get just the elements (especially if the only text nodes contain only white space).

Related

In mdn web docs Element.querySelector method says that it should be descendant but example shows otherwise

I am learning JavaScript from the MDN web docs. I was studying Element.querySelector() method.
It is written that it returns the first element that is a descendant of the element on which it is invoked that matches the specified group of selectors.
But there is an example given, which contradicts this fact.
var baseElement = document.querySelector("p");
document.getElementById("output").innerHTML =
(baseElement.querySelector("div span").innerHTML);
<div>
<h5>Original content</h5>
<p>
inside paragraph
<span>inside span</span>
inside paragraph
</p>
</div>
<div>
<h5>Output</h5>
<div id="output"></div>
</div>
Here, div tag is not a descendant of p tag, still this code works.
Can you point where I am going wrong ?
Element.querySelector() first applies the CSS Selectors passed to .querySelector() method, on the whole document and not the base element on which .querySelector() was invoked. This is done to generate initial set of potential elements.
After generating initial set of potential elements, list of potential elements is then filtered to retain only those elements which are descendants of the base element. Finally, the first element from this filtered list is returned.
In your code example, entire document is first searched for elements that match div span. As there is only one element in the entire document that matches div span selector, initial set of potential elements contains only one span element. After that, this span element is checked to see if it is the descendant of baseElement. Since, in this case, it is a descendant of
the baseElement, it is returned.
What i explained above is written under "Return Value" heading in MDN - Element.querySelector()
The entire hierarchy of elements is
considered when matching, including those outside the set of elements
including baseElement and its descendants; in other words, selectors
is first applied to the whole document, not the baseElement, to
generate an initial list of potential elements. The resulting elements
are then examined to see if they are descendants of baseElement. The
first match of those remaining elements is returned by the
querySelector() method.

When and how does firstChild select #text nodes?

Bit new to JS here, so I apologize if this is something obvious. I've read through the relevant documentation, and I'm a bit perplexed about how and when exactly firstChild selects text nodes.
I have a span and an input like so:
<span class="checkbox">
<input class="inputs" value="1">
</span>
On page load, if I call:
$(".checkbox").firstChild
I'll get back that input html element. Now, if I make an ajax call that replaces the entire span and its input with identical code, and then call:
$(".checkbox").firstChild
I get a #text node element back. Why? It may be that a more pertinent question is when are #text nodes inserted into whitespaces?
Please let me know if you need some more context and I appreciate you taking the time to help a beginner out.
Whitespace between nodes creates text nodes, so depending on whether there's any space/newline/tab between the closing > of the parent and the opening < of the child you may or may not get text nodes.
Use firstElementChild instead. Similar equivalents exist for sibling traversal.
Other options are to adjust your CSS selector to get the first child or use jquery's traversal methods.

Is an element attribute a node or not?

According to http://www.w3schools.com/js/js_htmldom_navigation.asp tutorial, element attributes are also nodes. In the example below, script is showing only element nodes.
<!DOCTYPE html>
<html>
<body>
link
<p id="demo"></p>
<script>
var n = document.body.childNodes;
var s = "";
for (var i = 0; i < n.length; i++)
s += n[i].nodeName + "<br>";
document.getElementById("demo").innerHTML = s;
</script>
some text
</body>
</html>
The result (node names) is:
#text
A
#text
P
#text
SCRIPT
I guess #text is node name for line breaks etc (but I have no idea why text after SCRIPT is not shown as #text).
Why it's not showing href attribute? Even when I try to see all child nodes of anchor element, it's showing only text node inside.
tl;dr: Yes, they are nodes, but they are treated differently.
From the DOM spec:
Attr objects inherit the Node interface, but since they are not actually child nodes of the element they describe, the DOM does not consider them part of the document tree. Thus, the Node attributes parentNode, previousSibling, and nextSibling have a null value for Attr objects. The DOM takes the view that attributes are properties of elements rather than having a separate identity from the elements they are associated with; this should make it more efficient to implement such features as default attributes associated with all elements of a given type. Furthermore, Attr nodes may not be immediate children of a DocumentFragment. However, they can be associated with Element nodes contained within a DocumentFragment. In short, users and implementors of the DOM need to be aware that Attr nodes have some things in common with other objects inheriting the Node interface, but they also are quite distinct.
As the text says, attribute nodes are not considered to be children of the element, hence they are not contained in childNodes. To get the attributes of an element, you can access element.attributes.
Element attributes are not nodes.
If you check MDN about .childNodes (wich is a far better source that w3schools) you will read:
The Node.childNodes read-only property returns a live collection of child nodes of the given element.
In the DOM3 spec one can read:
Attr objects inherit the Node interface, but since they are not actually child nodes of the element they describe, the DOM does not consider them part of the document tree.
About your other question, the reason why you do not see the text after the <script>tag is because the DOM is not ready loaded yet. If you put that code in the end, near </body>, then everything will be there.

Merging Text Nodes Together After Inserting Span

I have an extension where I am storing/retrieving a section of the DOM structure (always a selection of text on the screen) the user has selected. When I am storing a selection, I enclose the section in a SPAN tag, and highlight the text in yellow. This causes the DOM structure around the selected text to split up into various text nodes. This causes a problem for me as when I try to restore this selection (without refreshing the page) it causes problems as the DOM structure has been modified.
My question is how do I prevent the DOM structure from splitting up after inserting the SPAN? If this cannot be achieved, how would I reassemble the DOM structure after removing the SPAN tag to its original state?
//Insert the span
var sel = restoreSelection(mootsOnPage[i].startXPath);
var range = sel.getRangeAt(0).cloneRange();
var newNode = document.createElement('span');
newNode.className = 'highlightYellow';
range.surroundContents(newNode);
//Original DOM structure
<p>Hello there, how are you today</p>
//What the DOM looks like after insertion of SPAN
<p>
"Hello there, "
<span class="highlightYellow">how</span
" are you today"
</p>
Use element.normalize().
After you remove the span you inserted, you can use the element.normalize() method to merge the extra text nodes that were created as a result of the insertion/removal of the span. The normalize() method puts the specified element and all of its subtree into a "normalized" form (i.e. no text nodes in the subtree are empty and there are no adjacent text nodes). Found, thanks to #tcovo's comment.
Text nodes inside of an element are broken apart if you insert nodes and then remove them. Unfortunately they don't automatically re-merge once the extra node is removed. To answer peoples' questions as to "why" this matters, it usually causes issues when working with text highlighting in your UI.
The very act of inserting a <span> tag will alter the DOM. That's, somewhat by definition, what you're doing when you call surroundContents(). You can't add a span tag without altering the DOM which includes splitting text nodes and adding new elements for the span.
Further, unless the selected text includes only whole text nodes and the selection never starts/stops in the middle of a text node, you will have to split text nodes to put the span in the right place. When you later remove the span tags, you will have extra text nodes. That shouldn't really matter to anything, but if you really think you have to get the split text nodes back to the way they were, I can think of a couple options:
1) Save the original parentNode before the span is inserted into it. Clone it, add your span to the clone, replace the original node with the clone and save the original. When you want to restore, put the original back and remove the cloned one.
2) When you remove the span, run a function that looks for neighboring text nodes and combine them.
3) Figure out why it matters that there are more text nodes afterwards than there were before because this should not matter to any code or display.
When using normalize() pay attention!
It will strip away nodes like <br/> and will alter the text and its visualisation.
normalize() is good, but it has its drawbacks.
So <p>"this is an "<br/>"example"</p> will turn into <p>this is an example</p>
Is there a way to use normalize() but keeping the <br/>s?
You can concatenate and then remove the second node
node1.textContent += node2.textContent;
node2.remove();
You can use this to unwrap your content.
$(".highlightYellow").contents().unwrap();
Demo: http://jsfiddle.net/R4hfa/

Can't understand this line of JavaScript

Thanks for your attention and time.
I'm modifying an existing JavaScript but can't understand a line of code. Please help me understanding this line:
rowArray[i].value = rows[i].getElementsByTagName('td')[sortOn].firstChild.nodeValue;
I am clear till .getElementsByTagName('td'), sortOn is being passed in this function as a parameter. But I couldn't understand [sortOn].firstChild.nodeValue;
Please guide me,
thanks
.getElementsByTagName('td') - returns a list of TD elements.
.getElementsByTagName('td')[sortOn] - fetches a single element from that list
.firstChild - returns the first element that is positioned inside this TD.
.nodeValue: see here - https://developer.mozilla.org/En/DOM/Node.nodeValue
[sortOn]
is array notation. It works in exactly the same way as rows[i]. Let's say sortOn is equal to 5, and that there are seven elements in rows[i].getElementsByTagName('td'), which is an array of <td> elements. Then you will get the sixth one (JavaScript arrays are 0 based), and this will be a <td> element.
firstChild means the first element beneath that td, so in this case
<td><em>emphasis</em><strong>some text</strong></td>
the <em> element is the first child
nodeValue is in this case the contents of that element, so "emphasis" will be returned.
You may well find the gecko DOM reference useful
rows[i].getElementsByTagName('td') will get all td elements that are children of rows[i]. The [sortOn] part selects the td whose index is specified by the sortOn parameter. The .firstChild.nodeValue gets the text contained in the first element within that td.
Update: In the DOM, elements such as <td> can only contain other child elements, but they don't have any text property. The text itself is contained in a special "text node" that is a child of the <td> node. This is why you use .firstChild to obtain the text node, then use .nodeValue to get the text contained in that node.
getelementsByTagName returns you an array of element with same tag, then by using sortOn variable you select specified one form collection and take his first child and look on it.

Categories

Resources