JavaScript and CSS both use its own DOM tree when traversing through HTML elements.
In JavaScript, we can use its own DOM traversal methods such as
element.parentNode
element.nextSibling
However, this way is very unintuitive because JavaScript's DOM Tree contains things other than the HTML elements which can be easy to mess up for a developer.
i.e.
<div>
<p>
</p>
</div>
JavaScript's DOM Tree has <div> as the parent and 3 children:
text node: \n\t
element node: <p>
text node: \n
This becomes increasingly more difficult to keep track as the HTML document structure increases in complexity because not all HTML elements have a newline before and after among other things.
Thankfully, JavaScript allows us to use CSS's DOM traversal using:
element.querySelector("CSSselectorHere")
Since CSS's DOM Tree only contains the HTML elements, that makes it much easier to implement.
The case only I could think of where JavaScript's DOM Tree would be advantageous is if you are trying to color the text "cdf" red in the following:
In HTML:
<p> abc link cdf </p>
In JavaScript:
pElementVariable.firstChild.nextSibling.nextSibling.classList.add("CSSclassRed");
However, wouldn't a better HTML practice be to just enclose the unique text like so?
<p> abc link <span>cdf</span> </p>
Thus, styling the span using CSS's DOM traversal is possible. (assuming we're using traversal methods only, no ids)
If so, does .querySelector, an enabler in JavaScript for CSS's DOM traversal, make JavaScript's own built-in DOM traversal methods obsolete?
No. CSS is more limited, because it can only select elements (and pseudo-elements, but not through querySelector, but that might change).
Using the DOM you can traverse arbitrary nodes in the tree. That's more powerful. And if you want to restrict to elements, you can:
node.childNodes; // All child nodes
parentNode.children; // Only element child nodes
node.firstChild; // First node child
parentNode.firstElementChild; // First element child
node.lastChild; // Last node child
parentNode.lastElementChild; // Last element child
node.previousSibling; // The previous sibling node
nonDoctypeChildNode.previousElementSibling; // The previous sibling element
node.nextSibling; // The next sibling node
nonDoctypeChildNode.nextElementSibling; // The next sibling element
node.parentNode; // The parent node
node.parentElement; // The parent node, only if it's an element
So you don't need CSS APIs for things like that. And in particular you shouldn't modify your HTML to be able to use CSS APIs.
Related
I am running some tests on a DOM element,
the result of the tests is one of the element descendants.
for example:
<div id="myelement" class="some-class">
<div class="some-child-class"></div>
<div class="some-other-child-class">
<div class="grandchild-class"></div>
<div class="another-grandchild"></div>*
</div>
</div>
let's assume that:
test(document.getElementById('myelement'));
will return the Element marked with asterisk
Now my problem is:
The test procedure is heavy and resource consuming.
I don't want to run it when i don't have to.
And sometimes, I clone an element that has already been tested (meaning - i KNOW the result of the test), but since I am getting an object reference as a result I can't use it to access the relevant child on the cloned element.
Is there any efficient way of somehow "save" the relative path from a parent Element to a specific descendant DOM element and then "apply" it on another element?
So you could assign unique ids to each element and cache the test results in an Object at the Elements id. However, i dont know if this is useful. An example implementation:
var test=function(el){
return this[el.id] || (this[el.id]=advancedtesting(el));
}.bind({});
So you could do:
test(document.getElementById('myelement'));//caches
test(document.getElementById('myelement'));//returns the cached
You could use jQuery for that.
The selectors they use can take the form of 'nth-child' or 'nth-of-type' (see documentation here).
What is does is that targets child element from the position they have relative from where you start from.
In your case if you want to start from your first element and go down to the starred one you can do:
$('#myelement').find('div:nth-child(2) > div:nth-child(2)')
What this does is that it takes #myelement as a base from which you will begin the search, and after that it goes down to the second child element that is a div, and again into this div's second child element.
You could reuse that selector with a different base.
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 have a curiosity with a plugin I have written for Knockout, called knockout-fast-foreach, namely what is the fastest way to repeatedly clone a set of nodes and inject them into the DOM.
There are two things that need to happen in the cloning, namely copying out the source template nodes, and injecting them back into the DOM.
Now there are some design choices that apply, that include:
The source nodes will be children of a single DOM entity;
The target may have siblings unaffected by the DOM injection i.e. not all the child nodes may change;
The source may be a <template>, <script> or regular HTML DOM node.
So for example:
<template id='src'>ø</template>
<div id='target' data-bind='fastForEach: $data'>
</div>
When one applies the binding with ko.applyBindings([1, 2, 3], document.getElementById('target')) the result will be:
<template id='src'>ø <span data-bind='text: $data'></span></template>
<div id='target' data-bind='fastForEach: $data'>
ø <span data-bind='text: $data'>1</span>
ø <span data-bind='text: $data'>2</span>
ø <span data-bind='text: $data'>3</span>
</div>
While that example is KO-specific, the performance of the DOM manipulation ought to be a relatively universal characteristic.
As you can see from the linked source code above, the way I have come up with so far is to copy the source nodes into an array, then clone + inject them into the target at the desired position.
Is is possible there is a faster way to clone and copy multiple elements (e.g. perhaps using document fragments)?
You are using data-binding. This is in itself going to be slow. The best performance is always going to be to manipulate a string off the dom and then insert it into the dom in one go - element.innerHTML = "your new html". Even better to have it not positioned inline because this slows the rendering of the browser. Itereratively adding to the dom is thrashing the browser renderer. See - http://davidwalsh.name/css-js-animation.
I have:
<k-myelement>
<k-anotherelement></k-anotherelement>
</k-myelement>
When I define the template like this:
<polymer-element name="k-myelement">
<template>
<content select="k-anotherelement" id="anotherelement"></content>
</template>
</polymere-element>
I can access the inner element with this.$['anotherelement']
But with this approach I have to predefine, which inner elements can be used.
What I want is a template technique, that allows me to access all the inner elements.
<content> (insertion points) are for rendering elements in the light DOM at specific locations in the Shadow DOM. Using <content select="k-anotherelement"></content> says "render any <k-anotherelement> elements here. If you want all light DOM nodes to be invited into the rendering party, simply use <content></<content>.
The other issues with your snippet:
The name of the element needs to be defined on <polymer-element>, not as <template name="k-myelement">
To get the list of nodes that pass through a <content>, use content.getDistributedNodes(). You may also want to consider if you even need <content>. Light DOM children nodes can be access with .children and the other accessors. From the Polymer docs:
For a <content>, you can iterate through content.getDistributedNodes() to get the list of nodes distributed at the insertion point.
In Polymer, the best place to call this method is in the attached() callback so you’re guaranteed that the element is in the DOM tree.
Also remember that you can access the light DOM as the element’s normal children (i.e. this.children, or other accessors). The difference with this approach is that it’s the entire set of potentially distributed nodes; not those actually distributed.
I'm trying to target a class element (appearing more than once in the DOM) by its index or iteration, strictly using CSS.
I know of several different ways of achieving this: I could toss the element(s) in an array and retrieve a specific index of said element (Javascript). I could label the element I'm trying to target with an #ID. I could refine the targeted element by specifying an attribute (.element[href="link"]). The pseudo-class :first-child and :last-child only target the children of that (parent) element (Duh!).
Example: I have a .class appearing 5 times within my DOM and I want to affect the CSS properties of the 3rd iteration of that .class element.
I wish there was a pseudo-class of .element:index-3. There's probably something out there that let's you do just that.
If I understand you correctly, you want to style the element with the content "Me!" in this example:
<body>
<p>Not me.</p>
<p class="me">Not me.</p>
<div><p class="me">Not me.</p></div>
<p class="me">Me!</p>
<div><div><p class="me">Not me.</p></div></div>
<p class="me">Not me.</p>
</body>
This should be possible In some cases (see below) it is possible with the pseudo-class :nth-of-type:
.me:nth-of-type(3) {color:red;}
As ScottS noted in the comments, this is only possible if all classes are on the same element type (e.g. p).
I have a .class appearing 5 times within my DOM and I want to affect the CSS properties of the 3rd iteration of that .class element.
I'm reading this as "target third element that uses .class across the entire DOM", e.g.:
<h1 class="class">...</h1> #1
<div>
<ul class="class"> #2
<li>...</li>
<li class="class">...</li> #3, target this one!
<li>...</li>
</ul>
<div class="class">...</div> #4
</div>
<p class="class">...</div> #5
Any :nth- pseudo-class notation represents an element qualified by the number of siblings matching a certain criteria (:nth-child(an+b) has an+b-1 siblings before it etc.).
CSS specification (including selectors level 4 draft) does not provide means of qualifying elements by index outside of sibling context (i.e. only individual nodes can be traversed, not the entire DOM tree). There is a performance reason behind this (traversing DOM is tough, imagine async content update, this is not how rendering engines work); but also something like :nth-element-ever(3) would be a very, very arbitrary criteria and would be better targeted by introduction of another class that means exactly what it does, preferably during code generation.
If the elements are not placed as adjacent siblings, or at least in the same "scope" (container element), there's no way for you to find out existence, iteration, or actually nothing about "related" elements.
Your best shot is either use a very ugly solution,
based on the assumption the elements are siblings, which might look like so: li.class:first-child + li + li { color: black; } to find the 3rd iteration,
or just use some JS to calculate occurance of an instace and insert it as a special class, like "gimme-css-here".