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.
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
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!
I am using Polymer 1.0, and I'm having a problem retrieving and inserting into the dom and showing. I can get the HTML "li" to print to the console, but I am unable to get polymer to render the HTML.
<example-app appjs="appJsFile.js">
<ul id="content">
//I want Content Here from Polymer
</ul>
</example-app>
appJsFile.js gets imported into "example-app". I have a function for this:
for(var i in this.listData){
var _ele = parent.document.createElement('li');
_ele.innerHTML = this.listData[i].listDataName;
return _ele;
}
Any ideas?
Your code doesn't show where you add the element to the DOM.
You create the element and then return it. In your case this would leave the function after the first li element was created.
Where do you return it to?
parent.document.createElement('li') just creates an element. For it to be shown you need to add it to the DOM (like someElement.append(_ele)).
Direct DOM manipulation should be avoided.In your case you could use <template is="dom-repeat">. If you want to support browsers that don't yet allow <template> inside <ul> you can of course do direct DOM manipulation.
For performance reasons, Polymer 1 doesn't do full DOM API polyfill. You have to use Polymer DOM API to access DOM elements to ensure the polyfills are used.
Form the code in your question it's not clear what the context is.
Do you want to add the elements into the template of a Polymer element or add it as child elements or even outside Polymer elements.
In your case this could look like
var content = this.$.content;
for(var i in this.listData) {
var ele = parent.document.createElement('li');
ele.innerHTML = this.listData[i].listDataName;
content.append(_ele);
}
I've always wondered how this jQuery feature works: $('<span>Hello world</span>')[0]
That is supposed to return a reference to the newly created span element. How can I achieve the same result using the native DOM methods? insertAdjacentHTML? innerHTML? documentFragment?
I need to insert a HTML fragment and hold a reference to the outer element without the need of using createElement/appendChild.
Thanks.
It's possible to create an element, set its innerHTML, and return the first child. The container element is never added to the DOM:
var el = document.createElement('div');
el.innerHTML = '<span>Hello world</span>';
console.log(el.firstChild);
If that's wrapped in a function, I believe the original container will be eligible for garbage collection as soon as the child is appended somewhere else.
jQuery seems to be doing something more sophisticated, checking if the string contains a single tag or not, and creating a fragment for more complicated strings. See the parseHTML method on jQuery's source code.
this is what an html structure of the webpage looks like:
<body>
<form>
<input type='file'/>
</form>
<div id='list'>
<div>value here<input id='delete' type='button'/></div>
</div>
</body>
i have found javascript code that triggers on 'delete' button click and removes input 'file' element. it uses this piece of code where element is input 'file' mentioned above:
deleteButton.onclick=function(){this.parentNode.element.parentNode.removeChild(
this.parentNode.element );}
i am trying to understand logic(rules) behind 'this.parentNode.element' ? why not accessing element directly 'element.parentNode.remove...'
many thanks
i am trying to understand logic(rules) behind 'this.parentNode.element' ?
There's no element property on the Node, Element, HTMLElement, or HTMLDivElement interfaces. So my guess would be that elsewhere in that code, you'll find something that's explicitly adding that property to the element instance of the div containing the button. You can do that, add arbitrary properties to element instances. These are frequently called "expando" properties and should be done very, very, very carefully.
Not the answer to the question, just opinion. It's better avoid constructions like
this.parentNode.element.parentNode
Because in case when you change your DOM structure, you will need rewrite you JS. So I think it's better to give id attributes to tags, and use next construction to get DOM element:
document.getElementById('element_id')
or if you will use some js framework (like jQuery) you can use even easier construction to get DOM element
$("#ement_id")
Ok, "removeChild" is a strange method, and quite probably, ill-conceived. It should look like:
<div>value here<input id='deleteMe' type='button'/></div>
var node = document.getElementById('deleteMe');
node.remove(); // <--- does not exist, but sure would be nice!!!
No, instead we have to do these shenanigans:
var node = document.getElementById('deleteMe');
node.parentNode.removeChild(node); // verbose! Convoluted!
We have to get the node's parent, call the method, then refer to the node again. This doesn't look like any other DOM methods as far as I recall. The good news is you can make it happen all in one line, chained, like a jQuery method.
You are best served to start over or copy somebody else's code. The use of "this" means it was within an object (or class), referring to other methods or properties within that object. You should stick to non-object variables and functions for now.
Hope that helps.