Problem with using template on customElement together with <slot> - javascript

I'm trying to learn more on how to work with customElements and looking in to using together with it. When I'm trying to use with and surrounding the by another element the DOM seems to no look as expected, the shadowRoot is not getting the content for ?
I would love to know what I'm doing wrong and what is going on here? Hope the question makes sense!
Best regards
The customElement
customElements.define(
'test-element',
class extends HTMLElement {
constructor() {
super();
this.shadow = this.attachShadow( {mode: 'open'} );
}
connectedCallback() {
this.shadow.appendChild(this._template().cloneNode(true));
}
_template() {
let template = document.createElement('template');
template.innerHTML = `
<div class="surrounding-element">
<slot name="content"></slot>
</div>
`;
return template.content;
}
}
)
In the rendered DOM
<test-element>
#shadow-root
<div class="surrounding-element">
<slot name="content"></slot>
</div>
<div slot="content" class="non-template">
<p>Some kind of content!</p>
</div>
</test-element>
I have been trying to using , and customElements together to have more control of where the content is put, and I would like to be able to wrap the content given to .

Related

Using slots in WebComponents without using shadow DOM

I'm trying to build a WebComponent without using ShadowDOM - so far it mostly just worked, but now I want to build a component that wraps other components like you would do with Angular's #ViewChild / #ViewChildren. (the library I'm using here to render is uhtml similar to lit-html)
export class Dropdown extends HTMLElement {
private open: boolean = false;
static observedAttributes = ["open"]
constructor() {
super();
}
attributeChangedCallback(name: string, oldValue: string, newValue: string) {
switch (name) {
case "open":
this.open = Boolean(newValue);
break;
}
this.display()
}
connectedCallback() {
this.display()
}
display = () => {
render(this, html`
<div>
<slot name="item">
</slot>
</div>
`)
}
static register = () => customElements.define("my-dropdown", Dropdown)
}
If I now use this component
Dropdown.register();
render(document.body, html`
<my-dropdown open="true">
<strong slot="item">test</strong>
</my-dropdown>
`)
I'm expecting to see
<my-dropdown>
<div>
<strong>test</test>
</div>
</my-dropdown>
but the slot part is not working.
If I switch to ShadowDOM, it just works, but now I have to deal with ShadowDOM's sandbox with regards to styling which I'd rather not do.
constructor() {
super();
this.attachShadow({mode: "open"})
}
display = () => {
render(this.shadowRoot as Node, html`
Is it possible to make slots work without shadowDOM?
If not, is there different way to grab the content defined inside the component and use it inside display?
<my-component>
<div>some content here</div>
</my-component>
should render as
<my-component>
<header>
random header
</header>
<section>
<!-- my custom content -->
<div>some content here</div>
</section>
</my-component>
Any suggestions?
No, <slot> are part of the shadowDOM API
You can fake it, but since there is no shadowDOM you would have to store that content someplace else.
Could be a <template> you read and parse your (light)DOM content into.
That is a sh*load of DOM mutations.
Might be easier to just learn to style shadowDOM with:
CSS properties
inheritable styles
::part
constructable stylesheets

Can a text node be slotted?

Is it possible to assign a text node to a slot when using <template> and <slot>s?
Say I have a template that looks like
<template>
<span>
<slot name="mySlot"></slot>
</span>
</template>
I would like to be able to add only text to the slot, instead of having to add a <span> open and close tag each time I use the template. Is this possible? If not in pure HTML, in JavaScript?
It is also better to pass in the text content only so that no styling is applied on the way in. Currently I'm using an invalid tag <n> to avoid that issue.
Sure, you can with imperative slot assignment, but not yet in Safari.
You can not slot a text node into a named slot (declarative).
Mixing declarative and imperative slots is not possible.
::slotted(*) can not target text nodes.
https://github.com/WICG/webcomponents/blob/gh-pages/proposals/Imperative-Shadow-DOM-Distribution-API.md
https://caniuse.com/mdn-api_shadowroot_slotassignment
<script>
customElements.define("slotted-textnodes", class extends HTMLElement {
constructor() {
super().attachShadow({
mode: 'open',
slotAssignment: 'manual' // imperative assign only
}).innerHTML = `<style>::slotted(*){color:red}</style>
Click me! <slot name="title"></slot> <slot>NONE</slot>!!!`;
}
connectedCallback() {
let nodes = [], node;
setTimeout(() => { // wait till lightDOM is parsed
const nodeIterator = document.createNodeIterator(
this, NodeFilter.SHOW_TEXT, {
acceptNode: (node) => node.parentNode == this && (/\S/.test(node.data))
}
);
while ((node = nodeIterator.nextNode())) nodes.push(node);
this.onclick = (e) => {
this.shadowRoot.querySelector("slot:not([name])").assign(nodes[0]);
nodes.push(nodes.shift());
}
})
}
})
</script>
<slotted-textnodes>
Foo
<hr separator>Bar<hr separator>
<b>text INSIDE nodes ignored by iterator filter!</b>
Baz
<span slot="title">Can't mix Declarative and Imperative slots!!!</span>
</slotted-textnodes>

Slots does not work on a html web component without shadow dom

I have a html web component without shadow dom and I try to add a slot. For some reason it does not work.
I expected it to switch "Foo bar" to "Hello world" but that does not happen.
Does slots only works with shadow dom and a template?
How can I get it to work?
class HelloWorld extends HTMLElement {
constructor() {
super();
}
connectedCallback() {
this.innerHTML = `
<div>
<slot name="element-name">Foo Bar</slot>
</div>
`;
}
}
customElements.define("hello-world", HelloWorld);
<hello-world>
<span slot="element-name">Hello World</span>
</hello-world>
Yes, <slot> only works in shadowDOM
Slotted content is reflected lightDOM content
See: ::slotted CSS selector for nested children in shadowDOM slot
A Web Component without shadowDOM only has innerHTML
If you do this.innerHTML= on such a Web Component it replaces the innerHTML, just like on any other HTML tag
with shadowDOM:
<hello-world>
<b slot="none">Mighty</b>
<span slot="title">Web Components</span>
Hello!
</hello-world>
<script>
customElements.define("hello-world", class extends HTMLElement {
constructor() {
super()
.attachShadow({mode:"open"})
.innerHTML = `<div><slot></slot><slot name="title">Foo Bar</slot></div>`;
this.onclick = (evt) => this.querySelector('b').slot = "title";
}
});
</script>

how can i select an element in a template within a web component using vanilla javascript?

I'm having the hardest time selecting the ".recipe-type-menu-container" within the template in this component I built. I've tried using:
const container = typeofRecipe_template.content.querySelector(".recipe-type-menu-container");
but I keep getting an undefined error message. I'm trying to do all this with vanilla JS.
export class TypeofRecipe extends HTMLElement{
constructor(){
super();
} // end of constructor()
connectedCallback(){
var container;
var typeofRecipe_template = document.createElement('template');
typeofRecipe_template.innerHTML = `
<div class="recipe-type-menu-container" >
<div class="recipe-type-menu-row"></div>
<div class="recipe-type-menu-row"></div>
</div> <!---recipe-type-menu-container ->
`;
const container = typeofRecipe_template.content.querySelector(".recipe-type-menu-container");
this.parentNode.appendChild(typeofRecipe_template.content.cloneNode(true));
};
}

How to append or insert an HTML element in existing DOM in React

How to append or insert an HTML element in existing DOM(any element in DOM) on a click of a button? Had it appended, how to populate yet be written content after click of button in that appended element?
I know in Jquery it's a piece of cake but I want React to help me out this time.
Here is the code : i want to populate add <p></p> in 'editor' div and populate content what i will write after clicking button.
Any help would be appreciated.
export default class App extends React.Component{
constructor(props){
super(props);
}
parargraph(e){
const parargraph = '<p></p>';
alert(parargraph);
}
render(){
return(
<div className={"editorHolder " + (this.props.active ? 'show' : '' ) } onClick={this.hideEditor}>
<div className="editBar">
<ul>
<li><button onClick={this.parargraph}>Parargraph</button></li>
<li><button>H1</button><button>H2</button><button>H3</button><button>H4</button><button>H5</button><button>H6</button></li>
<li><button>Image</button></li>
<li><button>Link</button></li>
<li><button>List</button></li>
<li><button>Audio</button></li>
<li><button>Video</button></li>
</ul>
</div>
<div className="editor">
</div>
</div>
);
}
}
React offers a way to directly manipulate the innerHTML of a div.
Check out dangerouslySetHTML
For your code, try this:
export default class App extends React.Component{
constructor(props){
super(props);
this.state={
content: ''
}
this.paragraph = this.paragraph.bind(this)
}
paragraph(e){
const paragraph = '<p></p>'
let content = this.state.content
content += paragraph
this.setState({content})
}
render(){
return(
<div className={"editorHolder " + (this.props.active ? 'show' : '' ) } onClick={this.hideEditor}>
<div className="editBar">
<ul>
<li><button onClick={this.paragraph}>Paragraph</button></li>
<li><button>H1</button><button>H2</button><button>H3</button><button>H4</button><button>H5</button><button>H6</button></li>
<li><button>Image</button></li>
<li><button>Link</button></li>
<li><button>List</button></li>
<li><button>Audio</button></li>
<li><button>Video</button></li>
</ul>
</div>
<div className="editor" dangerouslySetInnerHTML={{__html: this.state.content}}>
</div>
</div>
);
}
}
Explanation: You would want to keep the content of the editor in the state, ready to change and append more content upon clicking of any of the buttons. The editor div will receive the state and updates its value upon state change.
PS: It's good practice to bind your component methods in the constructor, I've done it for you in the sample code.
You also misspelled 'paragraph'.

Categories