There is HTML page with contents like this.
Documentation at MDN says that childNodes returns a collection of child nodes of the given element which is a NodeList.
So, according to the doc, the first child for the NodeList should be <h1>PyCon Video Archive</h1>.
But, in Developer Tools (Chromium), it says the other way.
![enter image description here][2]
So, why exactly the first node is not <h1>PyCon Video Archive</h1>?
Why a text object as first element?
I would appreciate some help here.
EDIT
So, I just figured out that in Firebug (FF), the same function behaves differently.
My new question: Is using .childNodes() an unreliable way of accessing DOM elements?
To get the first element child, you can use...
document.body.firstElementChild;
...but older brwosers don't support it.
A method that has greater support is the children collection...
document.body.children[0];
...which has pretty good support but still has some holes in terms of older browsers.
(Just double checked, and as long as you don't support Firefox 3, and as long as you don't include HTML code comments in the markup, using .children will be safe.)
To ensure that you have the widest browser support, create a function...
function firstElementChild( parent ) {
var el = parent.firstChild;
while( el && el.nodeType !== 1 )
el = el.nextSibling;
return el;
}
and use it like this...
var h1 = firstElementChild( document.body );
Because there's a white-space text-node before the h1 element. Presumably, in the source (if you view source), the h1 opening tag's been either indented, or moved to a new line within the body (or both) in order for readability. At a guess, I'd imagine that it's something like the following:
<body>
<h1>PyCon Video Archive</h1>
<!-- ...other html... -->
If you revise that to:
<body><h1>PyCon Video Archive</h1><!-- ...other html... -->
Then the first childNode will, indeed, be the h1 element.
It's worth noting that text, even outside of an element tag, is still a child-node of the parent element. Albeit one that can't be easily targeted with a selector.
Related
I am just beginning to learn client-side JavaScript and using an online tutorial, so please bear with me.
This question is based on my understanding of the following:
To access the properties of the document's body, the syntax is "document.body", which returns all the elements in the body.
Similarly when you access the head, you use "document.head". Makes sense and most importantly, it works.
However, when I attempt to access elements WITHIN the body or head following the same logic, I get a return value of "undefined". For example, document.body.h1, returns "undefined", in spite of there being an h1 element inside the body element.
Further, when I enter document.head.title -- "undefined".
Strangely, however, when I enter "document.title", it returns the string value associated with the title tag.
I thought in order to access the title, you would have to access it through the head, since it is an element nested inside the head. But ok, that's fine. Using the same logic, I should then be able to enter document.h1 and get its value. Nope, instead, I get undefined.
Would someone be kind enough to explain to me why this behavior is so inconsistent. Thanks in advance.
You've really asked two questions:
Why document.title rather than document.head.title?
and
Why doesn't document.body.h1 return an element if there's an h1 in the body?
document.title
document.title is historical. Various parts of the browser environment were developed somewhat ad hoc by multiple different people/organizations in the 1990s. :-) That said, it's the title of the document, so this isn't an unreasonable place to put it, even if you use the title tag in head.
document.body.h1
One answer is: Because no one decided to design it that way. There were some early things like document.all (a list of all elements in the document) and even tag-specific ones (I forget exactly what they were, but they weren't a million miles off your document.body.h1 — I think document.tags.h1 or something, where again it was a list.)
But another answer is: Because the DOM is a tree. body can have multiple h1 elements, both as direct children and as children of children (or deeper); collectively, descendants. Creating automatic lists with all of these proved not to be scalable to large documents.
Instead, you can query the DOM (either the entire document, or just the contents of a specific element) via a variety of methods:
getElementById - (Just on document) Get an element using its id attribute value.
querySelector - Find the first element matching a CSS selector (can use it on document or on an element). Returns null if there were no matches.
querySelectorAll - Get a list of all elements matching a CSS selector (can use it on document or on an element). You can rely on getting back a list; its length may be 0, of course.
getElementsByTagName - Get a list of all elements with a given tag name (such as "h1").
getElementsByClassName - (No support in IE8 and earlier) Get a list of all elements with a given class.
There are many more. See MDN's web documentation and/or the WHAT-WG DOM Standard for more.
Some of the automatic lists persist (they got so much use that they had to be maintained/kept), such as document.forms, document.links, the rows property on HTMLTableElement and HTMLTableSectionElement instances, the cells property on HTMLTableRowElement instances, and various others.
document.head.title is a thing... but not what you might think.
title is an attribute that is applicable to all html elements; that is, it is a global attribute. It's meaning is 'advisory information'; one use is to display a tooltip:
<span title="hover over me and you'll see this">information</span>
So, all elements have a title attribute - including head. The title element - which is completely different - should be a child of the head though. So you might be tempted to set its value via document.head.title = "my title" , but document.head.title is not the head's title element, it's a property of the head element.
What you're actually doing is setting the title property on the head element:
<head title="my title">.... </head>
... which isn't what you want at all.
The correct way to set the title is document.title, which is a shortcut way of doing
document.querySelector("title").innerText = "my title"
As far as I know, standard JavaScript has no way to get at the ::before or ::after pseudo-elements. Element.children doesn't let you get to it.
I know there has to be a way, at least in Chrome-privileged Firefox add-on code, since it lists every ::before element in the page (and apparently getComputedStyle() works on it too, as you can list all styles of it in inspector, which is written in JavaScript).
Where is this API documented, and is it something that's different and privileged-only in say Firefox and Chrome browser, or something that is on track to be standard soon?
The CSS generated content is not part of the DOM, and you wouldn't be able to do much with the ::before/::after pseudo-elements, even if you get at them. The only use-cases I can think of are:
Access the CSS computed values on the pseudo-elements. window.getComputedStyle() supports this via an optional 2nd parameter.
Enumerate the generated content. You can accomplish this:
by using a browser-specific API. In Firefox, the DevTools inspector uses a special interface - inIDeepTreeWalker.
or by walking the DOM and checking (for each element) if it has content in its computed style for :before / :after. For example:
window.getComputedStyle(elt, ':before').content
Get the "live" value of a counter defined in CSS, like in How to access CSS generated content with JavaScript - see that question for details.
At least to me, your question is unclear as to exactly what you are attempting to do, or get.
The most direct equivalent to ::before and ::after:
If you are wanting to actually insert content, which is what the ::before and ::after CSS selectors do, then the most direct equivalent is Element.insertAdjacentHTML(position, text). In that case:
The equivalent of ::before would be:
Element.insertAdjacentHTML("beforebegin", "<p>Additional HTML content before element.</p>");
The equivalent of ::after would be:
Element.insertAdjacentHTML("afterend", "<p>Additional HTML content after element.</p>");
Element.insertAdjacentHTML() also has options of afterbegin and beforeend which insert the HTML text just after the beginning, or just before the end, of the referenced Element.
Alternately:
You could insert nodes using Node.insertBefore(newNode, referenceNode).
For ::before it would be (insert newNode before myNode):
myNode.parentNode.insertBefore(newNode, myNode);
For ::after it would be (insert newNode after myNode):
myNode.parentNode.insertBefore(newNode, myNode.nextSibling);
Obtaining references:
If you are attempting to get a reference to the element that is earlier in the DOM, then it sounds like you are looking for Node.previousSibling. If you are looking for a reference to the element that is later in the DOM, then you are looking for Node.nextSibling.
In DOM walk order:
It is also possible that you are looking for the elements that are just before and just after the reference Node in DOM walk order. However, that is not really what the CSS selectors ::before and ::after do. However, from your mention of Page Inspector, it kind of sounds like this is what you want. If so, then you will can use a TreeWalker to walk the DOM tree.
The following should do what you want (Note: Currently untested, so might be missing something.):
//referenceNode is the node for which we want to find the elements
// before and after in DOM walk order.
//Create the TreeWalker
let treeWalker = document.createTreeWalker(document.body, NodeFilter.SHOW_ELEMENT,
{acceptNode: function(node) {
return NodeFilter.FILTER_ACCEPT;
}
},
false );
//Point the TreeWalker at the referenceNode.
treeWalker.currentNode = referenceNode;
//Get the node immediately prior to the referenceNode in DOM walk order
let thePreviousNode = treeWalker.previousNode();
//Point the TreeWalker back at the referenceNode.
treeWalker.currentNode = referenceNode;
//Get the node immediately after to the referenceNode in DOM walk order
let theNextNode = treeWalker.nextNode();
As mentioned by Nickolay, if you want the full detail that Page Inspector, or the DOM Inspector (documentation), provides then you will need to use an inIDeepTreeWalker. However, it is unlikely that you want, or need, the detail which using that Firefox specific non-standard interface provides. You only need it if you want to walk through how something like how an XUL <toolbarbutton> is constructed (not the attributes/properties, but the XBL which makes up a XUL elements like a <toolbarbutton>). For the vast majority of what you are potentially thinking about, a standard TreeWalker should be just fine.
With the exception of inIDeepTreeWalker, all of the above are standard parts of JavaScript and do not require elevated privileges (i.e do not require it to be in an add-on).
You can use iniDOMUtils - selectorMatchesElement() function.
You can read more about it here - https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM/Reference/Interface/inIDOMUtils#selectorMatchesElement%28%29
Learning Javscript and trying to trigger a click event.
I'm not sure what I'm doing wrong but the following doesn't seem to work.
JSFiddle
http://jsfiddle.net/73e7H/1/
HTML
<ul>
<li id="nav-item-39">
Visit
</li>
<ul>
JS
var $visit = document.getElementById('nav-item-39').firstChild;
$visit.addEventListener('click', function() {
print('hello');
});
The firstChild is a text node containing the whitespace after the end of the li start tag and the beginning of the link. You need the first child that's an element.
On many browsers, that's firstElementChild, but if you need support for older browsers you may need a loop.
Fiddle (using alert rather than print)
Another option is to use querySelector, which is available on all modern browsers (even IE8 — but of course, IE8 doesn't have addEventListener):
var $visit = document.querySelector('#nav-item-39 a');
$visit.addEventListener('click', function() {
print('hello');
});
That finds the first a element that's a descendant of #nav-item-39. If you want to require that it's a direct child instead, use the selector string "#nav-item-39 > a" instead.
Fiddle
(Just for completeness: querySelector finds the first matching element and returns it [or null if no match]. There's also querySelectorAll which finds all matching elements and returns a static NodeList.)
Side note: print, unless you override it (and I don't think you do in the fiddle), is window.print which opens the browser's Print dialog.
Try Using firstElementChild instead of firstChild.
firstElementChild will defenitely return element, when firstChild can return text node also.
I was just reading this article by MDN and saw that, according to the specifications for "document.getElementById", elements not in the document are not searched.
I'm confused by why/how elements can be outside the document. How does this differ from the definition of an absolutely positioned element (namely, that absolutely positioned elements are removed from the document flow). I'm not entirely sure the absolutely positioned element case applies to this, but a clarification on what it means to be "outside the document" and why something like that would be used would be greatly appreciated.
A document is a tree, but you can have nodes (leaves/branches) that aren't on the tree (either because they never were, or because they've come off it).
Examples will probably make this clearer.
Example 1: Never in the tree:
Here's an element that's not in any document:
var elm = document.createElement('div');
elm.id = "foo";
That's an element, with an id, but it isn't part of any document.
Example 2: Removed from the tree:
HTML:
<body>
<div id="foo"></div>
</body>
JavaScript:
// The div is in the document, so this works:
var elm = document.getElementById("foo");
// Now we remove it:
elm.parentNode.removeChild(elm);
// 'elm' is no longer in any document
console.log(document.getElementById("foo")); // null
Example 2 Live | Source
"not in the document" means "not stored in the DOM tree of the current document", i.e. the nodes exist in memory, but they're not "attached" to any node on the page.
A corrollary of that is that the elements must therefore be invisible, but it's for an entirely different reason to that of absolute position. In the latter, the nodes do exist in the DOM, they just may not be position "on screen".
So much confusion, so few answers. I'm trying to loop through the DOM, looking for a specific node by id, however, this code has several problems for which I have no explanation. First, the length of the childNodes list comes up as '5'. Two "ul"'s, two "id"'s, if those count...and one for luck?
Second, it dies at if(y[i].hasAttribute('id')===true). Firebug says this is not a function. I have no reason to not believe it, but am not sure why it isn't.
Thank you for any help.
<div id="list">
<ul id="first"></ul>
<ul id="second"></ul>
</div>
<script>
var comments=document.getElementById('list')
var y=comments.childNodes;
var count=y.length
for(i=0;i<count;i++)
{
document.write(y.length);
if(y[i].hasAttribute('id')===true)
{ document.write('here!');}
}
</script>
the childNodes attribute contains all nodes in the DOM, which specifically means, it includes text nodes. you have 3 of them - the newline/linefeed characters inside your div.
you can test for element children using the nodeType attribute ( see eg. here; 1represents ELEMENT_NODE, 3 stands for TEXT_NODE).
If you use a tool like Firebug and inspect the DOM itself, you would see all the children of an element and the difference between .children and .childNodes .
It's by hunting around in the DOM that I discovered why there are so many things that at first appear to be duplicates of each other, but are definitely not. The Mozilla developers site developer.mozilla.org is also a wealth of information.