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!
Related
I have a custom-element with shadow DOM, which listens to attribute target change.
target is supposed to be the ID of the element which my component is supposed to be attached to.
I've tried using querySelector and getElementById to get the element of the outer DOM, but it always returns null.
console.log(document.getElementById(target));
console.log(document.querySelector('#' + target));
Both of the above return null.
Is there a way to get a reference to the element in the parent document from within shadow DOM?
You just have to call ShadowRoot.
this.shadowRoot.getElementById('target') should work.
Here's an example, the get syntax will bind an object property to a function.
get target() {
return this.shadowRoot.getElementById('target');
}
There are two use cases of shadow DOM as far as I can see:
You control the the shadow DOM solely through your hosting custom element (like in the answer of #Penny Liu). If you want make sure no other script should call and alter the nodes than this is your choice. Pretty sure some banking websites use this method. You give up on flexibility though.
You just want to scope some parts of your code for styling reasons but you like to control it via document.getElementById than you can use <slot>. After all, many libraries rely on the document object and will not work in shadow DOM.
Back to the problem, what you probably did was something like this:
shadowRoot.innerHTML = `...<script>document.getElementById('target')</script>`
// or shadowRoot.appendChild
This is NOT working! And this is not how shadow DOM was anticipated to work either.
Recalling method 2, you SHOULD fill your shadow DOM solely by <slot> tags. Most minimal example:
<!-- Custom Element -->
<scoped-playground>
<style>some scoped styling</style>
<div id="target"></div>
<script>const ☝☝☝☝ = document.getElementById('target')</script>
</scoped-playground>
<!-- Scoped playground has a shadowRoot with a default <slot> -->
...
this.shadowRoot.innerHTML = "<slot>Everything is rendered here</slot>";
...
More advanced <slot> examples can be found at:
https://developers.google.com/web/fundamentals/web-components/shadowdom#composition_slot
Having a simple custom element
document.registerElement('x-foo', {
prototype: HTMLElement.prototype;
});
I can create an HTML node
<x-foo></x-foo>
then select it in JavaScript, and attach a shadow root.
var xFoo = document.querySelector('x-foo')[0];
var root = xFoo.createShadowRoot();
root.textContent = 'I am a shadow root';
However, I would like the objects to be created with a predefined
shadow root, without any JavaScript manipulations afterwards, as it is with
<input> and other user-agent defined nodes.
How would I define a constructor or something for my element in order to achieve this?
Question is a bit old, but putting this answer here in case you haven't figured it out yet.
There are 4 lifecycle callback methods associated with custom elements. Copying from this nice tutorial on html5rocks.
So to answer you question, you can put your code to attach a shadow-root to custom element inside createdCallback and it will be executed every time your x-code element is initialized.
This is a sort of constructor for your custom element.
Hope it helps.
(Sorry for the bad title, I can't think of a better one)
I recently learned that you can do something like this in jquery:
$("<div><span>content</span></div>").css("color", "red").appendTo("body");
My question is about the following:
$("<div><span>content</span></div>")
How does jquery turn this from a string into dom elements, and how could you do the same thing in vanilla js (no jquery)?
I have attempted to look through the jquery source code, but I don't really understand it.
Any explanation is greatly appreciated!
the equivalent in pure javascript would be
var newDiv = document.createElement("DIV");
newDiv.style.color = "red";
var newSpan = document.createElement("SPAN");
newSpan.innerHTML = "content";
newDiv.appendChild(newSpan);
document.body.appendChild(newDiv);
jquery shortcuts this by defining functions that are chainable meaning the next function uses the previous functions return value as its input so in your example it is adding the css to your html code and then adding all of that code to the body
jQuery is creating a new instance of a jQuery object that contains a reference to the DOM elements created by parsing <div><span>content</span></div>.
One really useful thing jQuery does is that every invocation of jQuery or any of its API methods returns either the newly created jQuery instance or the current jQuery instance. The benefit of this is that you can chain your calls to transform a set of DOM elements.
In this case, $(...) returns a jQuery instance containing the DOM elements you want to operate on. Next, you chained css() which adds style properties to that element. Finally, you chain appendTo() which adds that to a target DOM element. In this case, that target is the <body> element.
Here's how this process would look (roughly) in JavaScript:
First, we need to create the DOM elements we wish to insert.
var node = document.createElement("div");
node.innerHTML = "<div><span>content</span></div>";
var myElement = node.children[0];
Then we'll set the style properties.
myElement.style.color = "red";
Finally, lets append it to an existing element.
document.body.appendChild(myElement);
The creation of the DOM elements from a string happens through the magic of the innerHTML property. When the JavaScript parser encounters a string being set to an elements innerHTML, it will parse that string into DOM elements and insert those elements as children.
Therefore, what jQuery is doing under the hood is creating a dummy element, setting the string you provided as a value on the dummy elements innerHTML property. This causes the DOM elements to be created and inserted as the dummy elements children. Lastly, it retrieves the reference to the children element (the elements you want).
This line creates an jQuery wrapped object from an html string, essentially creating a div with a span inside it whose text is the word content:
$("<div><span>content</span></div>")
This applies a CSS property to the created element telling it to display the text in red:
.css("color", "red")
This adds the created and styled element to the DOM at the end of the body tag:
.appendTo("body");
I'm using the webcomponentsjs polyfill. No x-tag, polymer, etc. preferably vanilla JS.
After cloning a template and appending it to the document I'm not able to remove it again since it's missing a parentNode.
var tmpl = document.getElementById('tmpl');
var clone = document.importNode(tmpl.content, true);
document.body.appendChild(clone);
console.log(clone.parentNode); // parentNode is null (not undefined!)
clone.parentNode.removeChild(clone); // fails!
You may see yourself in this jsbin
My Question is: How do I remove the element again. Am I missing something?
You are mixing up DocumentFragment vs. Node. The content of template is apparently an instance of DocumentFragment:
<template>
<div>node 1</div>
<p>node 2</p>
etc
</template>
According to the documentation, Node#appendChild accepts document fragments:
Various other methods can take a document fragment as an argument (e.g., any Node interface methods such as Node.appendChild and Node.insertBefore), in which case the children of the fragment are appended or inserted, not the fragment itself.
So, what do you expect to be a parent of document fragment? It’s obviously null, since the entity “document fragment” is virtual in this context. So, to achieve what you want you are to create a container at first, and then append nodes to it / clean it’s content up.
<body>
...
<div id='container'></div>
...
</body>
There is common approach to add template content involving ShadowDOM:
var shadow = document.querySelector('#container').createShadowRoot();
shadow.appendChild(document.querySelector('#tmpl').content, true);
or not using ShadowDOM, inserting as is:
document.querySelector('#container').appendChild(
document.querySelector('#tmpl').content, true
);
Hope it helps.
I'm using the DOM to manage a JSON response from an AJAX function I'm running. The script I'm writing needs to be completely portable, so I am defining the styles for the created elements on the fly (meaning, no linking to an external CSS, and no providing CSS in the HTML doc itself, because I won't have control of the doc).
I'd like to create a hover effect on some of the elements.
example:
#myDiv:hover { background:#000000; }
Is there a way to define that in the DOM? Or do I have to use mouseover?
You can dynamically create and manipulate stylesheets. See here for some of the cross-browser issues with this approach.
I've got a wrapper function lying around which works around some of them; using it, the code would read
document.createStyleSheet().addRule('#myDiv:hover', 'background:#000000;');
you may create element with predefined class:
.h:hover{color: #c00}
var elem = document.createElement('div');
elem.className = 'h'