You can test whether an element is a div or a span like this:
const div = document.createElement('div');
console.log(div instanceof HTMLDivElement);
const span = document.createElement('span');
console.log(span instanceof HTMLSpanElement);
This way of testing so far has worked for most HTML elements I'm aware of.
Unfortunately, the same approach of checking an element type is not available for section and article elements, which would mean I'd probably have to resort to el.tagName === 'SECTION' respectively el.tagName === 'ARTICLE'.
Edit: Just tested, the following globals all don't exist either:
HTMLNavElement
HTMLHeaderElement
HTMLMainElement
HTMLAsideElement
HTMLFooterElement
Does anyone know, and have any reference, as of why there are no HTMLSectionElement and HTMLArticleElement globals?
Is this because all of them are technically div elements with a different tag name to provide better semantics?
Thanks to #pointy for his comment pointing this out, the following section of the current HTML specification explains this:
The basic interface, from which all the HTML elements' interfaces inherit, and which must be used by elements that have no additional requirements, is the HTMLElement interface.
Related
When creating custom elements in HTML, does the child tag inherit the parent's CSS styles?
Here is my test case, from Chrome:
var h1bProto = document.registerElement ('h1-b',
{
prototype: Object.create (HTMLHeadingElement.prototype),
extends: "h1"
});
When I append a child using the new h1bProto it generates an H1 tag with is="h1-b", example below:
var node = document.body.appendChild (new hibProto());
node.textContent = "Hello";
<h1 is="h1-b">Hello</h1>
Hello
This gives me the parents CSS styles. However, if I add a node by creating the element first, then appending the node, the code looks like this:
var node = document.createElement ("h1-b");
node.textContent = "Hello";
document.body.appendChild (node);
<h1-b>Hello</h1-b>
Hello
Am I missing something, or do children not inherit the parent's CSS styles? If they don't, then is the best work around to use the Shadow DOM?
According to the W3 spec you aren't going crazy!
Trying to use a customized built-in element as an autonomous custom
element will not work; that is, Click
me? will simply create an HTMLElement with no special
behaviour.
Aka, in your example making a tag with <h1-b> will not apply the styling or behavior of an <h1> tag. Instead you must create an <h1> tag with the is attribute set to the name of your custom element. The section I linked you to in the spec actually does a great job explaining how to go about creating the tag.
All in all, you just need to make your element like so:
document.createElement("h1", { is: "h1-b" });
One reason that comes to mind for this is that most bots don't parse your javascript. As a result they would have a challenge to figure out what the elements in your dom really are. Imagine how much your seo would tank if a bot didn't realize that your <h1-b> elements were really <h1> elements!
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
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".
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.
This is the same question as this:
Referring to a div inside a div with the same ID as another inside another
except for one thing.
The reason there are two elements with the same ID is because I'm adding rows to a table, and I'm doing that by making a hidden div with the contents of the row as a template. I make a new div, copy the innerhtml of the template to my new div, and then I just want to edit bits of it, but all the bits have the same ID as the template.
I could dynamically create the row element by element but it's a VERY complex row, and there's only a few things that need to be changed, so it's a lot easier to just copy from a template and change the few things I need to.
So how do I refer to the elements in my copy, rather than the template?
I don't want to mess up the template itself, or I'll never be able to get at the bits for a second use.
Or is there another simpler way to solve the problem?
It will probably just be easiest when manipulating the innerHtml to do a replace on the IDs for that row. Maybe something like...
var copiedRow = templateRow.innerHTML.replace(/id=/g,"$1copy")
This will make the copied divs be prefixed with "copy". You can develop this further for the case that you have multiple copies by keeping a counter and adding that count variable to the replace() call.
When you want to make a template and use it multiple times its best to make it of DOM, in a documentFragment for example.
That way it doesn't respond to document.getElementById() calls in the "live" DOM.
I made an example here: http://jsfiddle.net/PM5544/MXHRr/
id's should be unique on the page.
PM5544...
In reality, there's no use to change the ID to something unique, even though your document may not be valid.
Browsers' selector engines treat IDs pretty much the same as class names. Thus, you may use
document.querySelector('#myCopy #idToLookFor');
to get the copy.
IDs on a page are supposed to be unique, even when you clone them from a template.
If you dynamically create content on your page, then you must change the id of your newly cloned elements to something else. If you want to access all cloned elements, but not the template, you can add a class to them, so you can refer to all elements with that class:
var clonedElement = template.cloneNode(yes); // make a deep copy
clonedElement.setAttribute("id", "somethingElse"); // change the id
clonedElement.setAttribute("class",
clonedElement.getAttribute("class") + " cloned"
);
To access all cloned elements by classname, you can use the getElementsByClassName method (available in newer browsers) or look at this answer for a more in-depth solution: How to getElementByClass instead of GetElementById with Javascript?
Alternatively, if you have jQuery available, you can do this is far less lines of code:
$("#template").clone().attr("id","somethingElse")
.addClass("cloned").appendTo("#someDiv");
The class lookup is even simpler:
$(".cloned").doSomethingWithTheseElements();
Try to avoid using IDs in the child elements of the cloned structure, as all ids of the cloned element should be changed before adding the clone to the page. Instead, you can refer to the parent element using the new id and traverse the rest of the structure using classnames. Class names do not need to be unique, so you can just leave them as they are.
If you really must use ID's (or unique "name" attributes in form fields), I can strongly suggest using a framework like jQuery or Prototype to handle the DOM traversal; otherwise, it is quite a burden to resolve all the cross-browser issues. Here is an example of some changes deeper in the structure, using jQuery:
$("#template").clone().attr("id","somethingElse")
.addClass("cloned") // add a cloned class to the top element
.find("#foo").attr("id","bar").end() // find and modify a child element
.appendTo("#someDiv"); // finally, add the node to the page
Check out my ugly but functional cheese. I wrote a function that works like getelementbyid, but you give it a start node instead of the document. Works like a charm. It may be inefficient but I have great faith in the microprocessors running today's browsers' javascript engines.
function getelement(node, findid)
{
if (node)
if (node.id)
if (node.id == findid)
return node;
node = node.firstChild;
while(node)
{
var r = getelement(node, findid);
if (r != null)
return r;
node = node.nextSibling;
}
return null;
}
When you copy the row, don't you end up having a reference to it? At that point can't you change the ID?