Web component template filling multiple named slots - web-component

Given the following html template:
<div class="Page">
Hello <slot name="personName"></slot>. Your name is <slot name="personName"></slot>.
</div>
How is it possible (if at all) to fill both slots with one value using custom elements?
The below demo code will produce:
Hello Bob, Your name is .
Is this intended? Is this the wrong way of displaying a single value in multiple locations of a template?
let tmpl = document.createElement("template");
tmpl.innerHTML = `
<div>
Hello <slot name="personName"></slot>. Your name is <slot name="personName"></slot>.
</div>
`;
class MyElement extends HTMLElement {
constructor() {
super();
let shadowRoot = this.attachShadow({ mode: "open" });
shadowRoot.appendChild(tmpl.content.cloneNode(true));
}
}
customElements.define("x-myelement", MyElement);
<x-myelement>
<span slot="personName">Bob</span>
</x-myelement>

It's the normal behavior.
If you want to reuse a variable multiple times, a good solution is to use template literals with placeholders.
In a template literal string (that is a string delimited with back-ticks), placeholders are replaced by the value of the matching variable.
let name = "Bob"
let template = `Hello ${name}. Your name is ${name}`
// template will contain "Hello Bob. Your name is Bob"
You can get the content of the light DOM using querySelector() on the slot attribute, or on any other attribute you prefer. To get the value, use property textContent, innerHTML, or even outerHTML if you wan to keep the surrounding <span> element.
class MyElement extends HTMLElement {
connectedCallback() {
var person_name = this.querySelector( '[slot=personName]' ).outerHTML
this.attachShadow({ mode: "open" })
.innerHTML = `Hello ${person_name}. Your name is ${person_name}`
}
}
customElements.define("x-myelement", MyElement)
<x-myelement>
<span slot="personName">Bob</span>
</x-myelement>

take a look at LitHtml and MDN
make name as an attribute of element, then inject it into the template.
const tmpl = document.createElement('template');
tmpl.innerHTML = `
<style>span{font-weight:bold}</style>
<p>Hello <span id="name"></span> Your name is <span id="name2"></span></p>`;
class MyElement extends HTMLElement {
connectedCallback() {
const shadow = this.attachShadow({ mode: "open" });
shadow.appendChild(tmpl.content.cloneNode(true));
const name = this.getAttribute('personName');
shadow.querySelector('#name').innerText = name;
shadow.querySelector('#name2').innerText = name;
}
}
customElements.define("x-myelement", MyElement);
<x-myelement personName="Bob"></x-myelement>
slots are not supported by all browser, especially named slots.
second, slotted content are not within the scope of the component (shadow dom), unless that was what you where going for?

Related

Add css class to 3rd component div by role

How I can (using react way) add a myCustomClass in div by role attribute (presentation)? Component don't expose any way to add my custom class and I dont change all componets (using css way), only this component.
<div class="css-1dozdou">
<div role="presentation" class="myCustomClass css-l0iinn"><div ..
Well, as I understood you, you have some third-party component, so you can't change its code. Then you should add some "magic" to overcome the problem.
First of all you should define a ref. Add it to the troubled component or its parent div as parameter ref={ref}. From now on you have a DOM object. Next you should search for div with role via native JS and add the class.
const App = () => {
const ref = useRef(null);
useEffect(() => {
const divs = ref.current.querySelector('[role="presentation"]');
if (divs.length) {
divs[0].add('myCustomClass');
}
})
return (
<div>
<h1>Change a class of a div inside child</h1>
<div ref={ref}><ThirdPartyComponent /></div>
</div>
);
};
const ThirdPartyComponent = () => {
return (
<div class="css-1dozdou">
<div role="presentation" class="myCustomClass css-l0iinn">
Some component
</div>
</div>
);
};

Change css variables dynamically in angular

In my angular project, I have some css variables defined in top level styles.scss file like this. I use these variable at many places to keep the whole theme consistent.
:root {
--theme-color-1: #f7f7f7;
--theme-color-2: #ec4d3b;
--theme-color-3: #ffc107;
--theme-color-4: #686250;
--font-weight: 300
}
How can I update values of these variables dynamically from app.component.ts ? And What is the clean way to do this in angular ?
You can update them using
document.documentElement.style.setProperty('--theme-color-1', '#fff');
If u want to update many values, then create a object
this.styles = [
{ name: 'primary-dark-5', value: "#111" },
{ name: 'primary-dark-7_5', value: "#fff" },
];
this.styles.forEach(data => {
document.documentElement.style.setProperty(`--${data.name}`, data.value);
});
The main thing here is document.documentElement.style.setProperty. This line allows you to access the root element (HTML tag) and assigns/overrides the style values.
Note that the names of the variables should match at both places(css and js files)
if you don't want to use document API, then you can use inline styles on HTML tag directly
const styleObject = {};
this.styles.forEach(data => {
styleObject[`--${data.name}`] = data.value;
});
Then In your template file using ngStyle (https://angular.io/api/common/NgStyle)
Set a collection of style values using an expression that returns
key-value pairs.
<some-element [ngStyle]="objExp">...</some-element>
<html [ngStyle]="styleObject" >...</html> //not sure about quotes syntax
Above methods do the same thing, "Update root element values" but in a different way.
When you used :root, the styles automatically got attached to HTML tag
Starting with Angular v9 you can use the style binding to change a value of a custom property
<app-component-name [style.--theme-color-1="'#CCC'"></app-component-name>
Some examples add variables directly to html tag and it seem in the element source as a long list. I hope this helps to you,
class AppComponent {
private variables=['--my-var: 123;', '--my-second-var: 345;'];
private addAsLink(): void {
const cssVariables = `:root{ ${this.variables.join('')}};
const blob = new Blob([cssVariables]);
const url = URL.createObjectURL(blob);
const cssElement = document.createElement('link');
cssElement.setAttribute('rel', 'stylesheet');
cssElement.setAttribute('type', 'text/css');
cssElement.setAttribute('href', url);
document.head.appendChild(cssElement);
}
}

receiving style and dynamically applying it to elements selected with class, using Vue

I'm making a web service with Vue.js. What I'm trying to do is to change styles of elements which receive from parent component's property. For example:
(there are omitted lines)
Parent component
<template>
<div>
<child v-bind:styles="styles"></child>
<div>
</template>
<script>
export default {
components: { child },
data() {
return {
styles: { width:'100px', height:'70px' }
};
}
}
</script>
The width and height of styles object can be changed, i.e. decided by anyone who use Parent Component.
Chlid component
<template>
<div>
<div class="iterDiv" v-for="(item, index) in arr" v-key="index">{{ item }}</div>
</div>
</template>
<script>
export default {
name: 'Child',
props: [ 'styles' ],
data() {
return {
arr: [1, 2, 3, 4, 5]
};
}
}
</script>
And the Child Component is consist of iterated elements which have a class iterDiv for selection like document.getElementsByClassName('iterDiv').
Now, I want to change Child Component's div elements, having iterDiv class name, style with styles dynamically.
Is there any way for it? Thanks.
If there is a answer already, I missed it and let me know it. I will delete this question.

Vue.js: How to change a class when a user has clicked on a link?

I am trying to add a class to an element depending on whether the user has clicked on a link. There is a similar question here but it is not working as I wanted it to be.
I created a component which has its own internal data object which has the property, isShownNavigation: false. So when a user clicks on the a I change isShownNavigation: true and expect my css class isClicked to be added. Alas that is not happening - isShownNavigation stays false in the component when I displayed it {{isShownNavigation}} but I can see in the console that my method is working when clicked.
I imported my header component to the App. Code is below.
Header Component
<template>
<header class="header">
<a
href="#"
v-bind:class="{isClicked: isShowNavigation}"
v-on:click="showNavigation">
Click
</a>
</header>
</template>
<script>
export default {
name: 'header-component',
methods: {
showNavigation: () => {
this.isShowNavigation = !this.isShowNavigation
}
},
data: () => {
return {
isShowNavigation: false
}
}
}
</script>
Application
<template>
<div id="app">
<header-component></header-component>
</div>
</template>
<script>
import HeaderComponent from './components/Header.vue'
export default {
name: 'app',
components: {
'header-component': HeaderComponent
}
}
</script>
I am using the pwa template from https://github.com/vuejs-templates/pwa.
Thanks.
Don't use fat arrow functions to define your methods, data, computed, etc. When you do, this will not be bound to the Vue. Try
export default {
name: 'header-component',
methods: {
showNavigation(){
this.isShowNavigation = !this.isShowNavigation
}
},
data(){
return {
isShowNavigation: false
}
}
}
See VueJS: why is “this” undefined? In this case, you could also really just get rid of the showNavigation method and set that value directly in your template if you wanted to.
<a
href="#"
v-bind:class="{isClicked: isShowNavigation}"
v-on:click="isShowNavigation = true">
Click
</a>
Finally, if/when you end up with more than one link in your header, you will want to have a clicked property associated with each link, or an active link property instead of one global clicked property.

createElement on Custom Element breaks template

I am creating two custom elements, both are added to index.html using link rel="import". One is a container with slots and the other is something to put a number in the slots. Both elements each have an HTML file with the template and a link to a js file that defines them as custom elements. To link the custom element class declaration to the HTML template I am using:
class PuzzlePiece extends HTMLElement{
constructor(){
super();
console.dir(document.currentScript);
const t = document.currentScript.ownerDocument.getElementById('piece-puzzle');
const shadowRoot = this.attachShadow({mode: 'open'});
shadowRoot.appendChild(t.content.cloneNode(true));
}
This puzzle-piece element and the container render properly and it all works when you manually put them in the index.html light dom
<special-puzzle id="grid">
<puzzle-piece id="hello"></puzzle-piece>
</special-puzzle>
However, once I try and create and append a puzzle-piece using JS in index.html:
<script>
const addToGrid = document.createElement("puzzle-piece");
document.getElementById("grid").appendChild(addToGrid);
</script>
I see a new puzzle-piece in the special-puzzle light dom but it is not taking up a slot, doesn't render, and the console has the error:
Uncaught TypeError: Cannot read property 'content' of null
at new PuzzlePiece (puzzle-piece.ts:8)
at HTMLDocument.createElement (:3:492)
at (index):37
As far as I can tell the problem is when using document.createElement the browser is getting to the class define but the document.currentScript.ownerDocument is different from when just manually using HTML tags. I believe because of this, the component can't find it's template. This is my first Stack Overflow question so any feedback/help would be appreciated!
Solved thanks to the awesome #Supersharp and their Stack Overflow post
Basically, in order to preserve the correct document.currentScript.ownerDocument I need to declare it in a var before the class then use that var in the class.
Old:
class PuzzlePiece extends HTMLElement{
constructor(){
super();
const t = document.currentScript.ownerDocument.getElementById('piece-puzzle');
const shadowRoot = this.attachShadow({mode: 'open'});
shadowRoot.appendChild(t.content.cloneNode(true));}
New:
var importedDoc = document.currentScript.ownerDocument;
class PuzzlePiece extends HTMLElement{
constructor(){
super();
const t = importedDoc.getElementById('piece-puzzle');
const shadowRoot = this.attachShadow({mode: 'open'});
shadowRoot.appendChild(t.content.cloneNode(true));}

Resources