How to ensure CSS class styles are rendered on Custom HTML Elements? - javascript

New to using custom HTML elements. My class style is not applying to the page render, though it shows in the DOM. The only clue is that it appears in the inspector within the open Shadow DOM rather than the regular DOM, which seems undesirable if it causes CSS issues.
Note the class style in question in this example is called 'border-all'. I have tried three modern browsers. No border displays
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<title>My Test</title>
<meta charset="UTF-8" />
<link rel="stylesheet" href="styles.css" >
<script src="header.js"></script>
</head>
<body>
<header-component></header-component>
</body>
</html>
header.js
class Header extends HTMLElement {
constructor() {
super();
const template = document.createElement('template');
const h1 = document.createElement('h1')
h1.innerHTML = 'Hello World'
h1.style.color = 'green'
h1.className = 'border-all'
template.content.appendChild(h1)
const shadowRoot = this.attachShadow({ mode: 'open' });
shadowRoot.appendChild(template.content);
}
}
customElements.define('header-component', Header);

https://developers.google.com/web/fundamentals/web-components/shadowdom
ShadowDOM is styled by:
<style> within shadowDOM
Inheritable styles
https://lamplightdev.com/blog/2019/03/26/why-is-my-web-component-inheriting-styles/
(cascading) CSS properties
shadowParts (and Themes)
https://meowni.ca/posts/part-theme-explainer/
<slot> are reflected, they are NOT styled by the shadowDOM, but by its container.
See: ::slotted content
(feb 2022) Constructible StyleSheets is still a Chromium only party
https://caniuse.com/mdn-api_cssstylesheet_cssstylesheet
customElements.define("my-component",class extends HTMLElement{
constructor(){
super().attachShadow({mode:"open"})
.innerHTML = `
<style>
:host { display:inline-block; padding-left:2em }
h2 { margin:0 }
span {
color: var(--spancolor,grey);
</style>
<h2 part="wcTitle">Hello Web Component!</h2>
<span>styling shadowDOM can be a challenge</span>
<slot></slot>`;
}
})
<style>
body {
font:18px Arial; /* inheritable styles style Web Components */
color:green; /* color is an inheritable style */
--spancolor: lightcoral; /* css properties cascade, and are available in Web Components */
}
div ::part(wcTitle){ /* ::parts style ALL (nested) elements in shadowDOM */
background:gold;
}
my-component{
font-weight: bold; /* slotted content is styled by the container */
}
my-component my-component{
color:blue;
}
</style>
<my-component>But is very powerfull!</my-component>
<div>
<my-component>You should never give up!
<my-component>Those who quit after the first attempt are loosers</my-component>
</my-component>
</div>

One way of doing this is to use the Element.classList property
You can just:
document.querySelector('h1').classList.add('border-all');
I will leave you below a working example in CodePen, the only difference is that the element already have and id and we use
document.getElementById
instead of
document.querySelector
But thats pretty much the difference, I hope this helps you understand better!
https://codepen.io/Jeysoon/pen/NWwzEJG?editors=1111

Related

CSS not working properly on Custom HTML Elements

I've been trying to make a custom HTML Element by extending the HTMLElement class. I try adding some style to it by linking a CSS file that is in the same directory as my other two files - index.html and custom.css.
Main folder
index.html
custom.css
custom.js
index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link rel="nofollow" type="text/css" href=''>
</head>
<body>
<script src="./custom.js"></script>
<smooth-button text="Smooth button" no-1 = 1 no-2 = 2></smooth-button>
</body>
</html>
custom.css:
smooth-button{
display: block;
color: blue;
background-color: orange;
}
custom.js:
class SmoothButton extends HTMLElement{
constructor(){
super();
this.shadow = this.attachShadow({mode: "open"})
}
connectedCallback(){
this.render();
}
render(){
this.SumOfNo1AndNo2 = null;
if(this.getAttribute("no-1")!=null && this.getAttribute("no-2")!=null){
this.SumOfNo1AndNo2 = parseInt(this.getAttribute("no-1")) +
parseInt(this.getAttribute("no-2"));
}
else{
console.log("Invalid attribute.")
}
this.shadow.innerHTML = `<button>` + this.getAttribute("text") + " " + this.SumOfNo1AndNo2
+ "</button>"
}
}
customElements.define("smooth-button", SmoothButton);
With this, I get a button as expected, with the text, but the style is applied to the element as a whole and not to the elements it's made of. How can I apply the styles separately to each of its elements (just a <button> for now) with an external CSS file? I'm using external CSS because it's somehow better as I read it here.
In addition to the answers from Brad and Emiel,
(Brad) Bluntly add a <style> element inside shadowDOM
Do read about adopted StylesSheets (Chromium only)
(Emiel) use cascading CSS properties
There are more options to style shadowDOM:
Learn about Inheritable Styles
https://lamplightdev.com/blog/2019/03/26/why-is-my-web-component-inheriting-styles/
use shadow parts
<style>
::part(smoothButton){
display: block;
color: blue;
background-color: orange;
}
</style>
<smooth-button></smooth-button>
<smooth-button></smooth-button>
<script>
customElements.define("smooth-button", class extends HTMLElement {
constructor(){
super()
.attachShadow({mode:"open"})
.innerHTML = `<button part="smoothButton">LABEL</button>`;
}
});
</script>
https://developer.mozilla.org/en-US/docs/Web/CSS/::part
https://meowni.ca/posts/part-theme-explainer/
https://css-tricks.com/styling-in-the-shadow-dom-with-css-shadow-parts/
https://dev.to/webpadawan/css-shadow-parts-are-coming-mi5
https://caniuse.com/mdn-html_global_attributes_exportparts
But...
The first question you should ask yourself:
Do I really need shadowDOM?
If you don't want its encapsulating behavior, then do not use shadowDOM
<style>
.smoothButton{
display: block;
color: blue;
background-color: orange;
}
</style>
<smooth-button></smooth-button>
<smooth-button></smooth-button>
<script>
customElements.define("smooth-button", class extends HTMLElement {
connectedCallback(){
this.innerHTML = `<button class="smoothButton">LABEL</button>`;
}
});
</script>
shadowDOM <slot>
Another alternative is to use shadowDOM <slot> elements, because they are styled by its container element
<style>
.smoothButton{
display: block;
color: blue;
background-color: orange;
}
</style>
<smooth-button><button class="smoothButton">LABEL</button></smooth-button>
<smooth-button><button class="smoothButton">LABEL</button></smooth-button>
<script>
customElements.define("smooth-button", class extends HTMLElement {
constructor(){
super()
.attachShadow({mode:"open"})
.innerHTML = `<slot></slot>`;
}
});
</script>
When you go down the <slot> rabbithole, be sure to read the (very long) post:
::slotted CSS selector for nested children in shadowDOM slot
Additionally to Brad's answer. One of the ways you can apply styles from the Light DOM to the Shadow DOM is with CSS Variables.
smooth-button{
display: block;
--button-color: blue;
--button-background-color: orange;
}
render() {
this.shadow.innerHTML = `
<style>
button {
color: var(--button-color);
background-color: var(--button-background-color);
}
</style>
<button>
${this.getAttribute("text")} ${this.SumOfNo1AndNo2}
</button>
`;
)
With this, I get a button as expected, with the text, but the style is applied to the element as a whole and not to the elements it's made of.
This is actually how the custom element is supposed to work. You can't apply styles to the shadow DOM from the outer document. If you could, you'd have a high likelihood of breaking the custom element styling through external modification.
All is not lost however! The reason the button is a different color from its background is due to the user agent stylesheet. You can actually set some CSS to tell the background to inherit the parent background color. Try adding this to your custom element:
const style = document.createElement('style');
style.textContent = `
button {
background: inherit;
}
`;
this.shadow.append(style);
JSFiddle: https://jsfiddle.net/5t2m3bku/
(Also note that it's not really a great idea to interpolate/concatenate text directly into HTML. That text gets interpreted as HTML, which can lead to invalid HTML if reserved characters are used, and even potential XSS vulnerabilities. You might modify that line where you set innerHTML to set the text, or switch to a template engine.)

How to use CSS "target" selector in web components using lit html

I am using lit html to create custom web components in my project. And my problem is when I try to use the CSS target selector in a web component it wont get triggered, but when I am doing it without custom component the code works perfectly. Could someone shed some light to why this is happening and to what would be the workaround for this problem? Here is my code:
target-test-element.js:
import { LitElement, html} from '#polymer/lit-element';
class TargetTest extends LitElement {
render(){
return html`
<link rel="stylesheet" href="target-test-element.css">
<div class="target-test" id="target-test">
<p>Hello from test</p>
</div>
`;
}
}
customElements.define('target-test-element', TargetTest);
with the following style:
target-test-element.css:
.target-test{
background: yellow;
}
.target-test:target {
background: blue;
}
and I created a link in the index.html:
index.html(with custom component):
<!DOCTYPE html>
<head>
...
</head>
<body>
<target-test-element></target-test-element>
Link
</body>
</html>
And here is the working one:
index.html(without custom component)
<!DOCTYPE html>
<head>
...
</head>
<body>
Link
<div class="target-test" id="target-test">
Hello
</div>
</body>
</html>
LitElement uses a Shadow DOM to render its content.
Shadow DOM isolates the CSS style defined inside and prevent selecting inner content from the outide with CSS selectors.
For that reason, the :target pseudo-class won't work.
Instead, you could use a standard (vanilla) custom element instead of the LitElement.
With no Shadow DOM:
class TargetTest extends HTMLElement {
connectedCallback() {
this.innerHTML = `
<div>
<span class="test" id="target-test">Hello from test</span>
</div>`
}
}
customElements.define('target-test-element', TargetTest)
.test { background: yellow }
.test:target { background: blue }
<target-test-element></target-test-element>
Link
Alternately, if you still want to use a Shadow DOM, you should then set the id property to the custom element itself. That supposes there's only one target in the custom element.
class TargetTest extends HTMLElement {
connectedCallback() {
this.attachShadow( { mode: 'open' } ).innerHTML = `
<style>
:host( :target ) .test { background-color: lightgreen }
</style>
<div>
<span class="test">Hello from test</span>
</div>`
}
}
customElements.define('target-test-element', TargetTest)
<target-test-element id="target-test"></target-test-element>
Link
Bit in late, i've experienced the same problem! So i'm following one of two paths:
Use a lit element but without the shadowDOM, to do that in your Lit element call the method createRenderRoot()
createRenderRoot () {
return this
}
Instead handle the CSS logic with :target i'm handling the attribute on the element (easy to do with Lit) eg. active and use it in CSS:
element[active] {
/* CSS rules */
}
These are my solutions for the moment! Hope it's help...

Cannot access "custom-style" resources in Polymer Element

I am writing a basic Polymer Component that should be able also to change its style at the change of a property.
When I try to change the theme file, I use the id to retrieve it but I always get null.
Here is my code:
[other polymer imports]
<link rel="import" href="/themes/my-theme.html" id="current_theme"/>
<dom-module id="my-app">
<template>
<style>
:host{
font-family: 'Roboto', 'Noto', sans-serif;
-webkit-font-smoothing: antialiased;
}
:host ::content app-header{
background: var(--app-primary-color);
color: var(--app-text-color);
}
:host ::content user-app-toolbar{
background: var(--app-primary-color);
color: var(--app-text-color);
}
...
</style>
...
<script>
Polymer({
is: 'my-app',
properties: {
state: {
type: String,
reflectToAttribute: true,
observer: '_stateChanged',
},
},
_stateChanged: function(page) {
// get theme file
theme.href = '/theme/red.html';
}
});
</script>
</template>
</dom-module>
my-theme.html
<style is="custom-style">
my-app {
--app-primary-color: grey;
--app-secondary-color: black;
--app-text-color: white;
}
</style>
The problem is how to implement the "get theme file". I tried many things:
document.querySelector('#current_theme'):
[returns null, I think it is because it uses the main document and not the one of the element]
Polymer(dom).querySelector('current_theme'): [undefined]
this.$$["current_theme"] [undefined]
Any idea of how to do this?
P.S: the idea of changing the theme in this way is taken from this stack overflow question
Static
You need to put a style tag with your theme id (Polymer 2.0)
<link rel="import" href="/themes/my-theme.html"/>
<dom-module id="my-app">
<template>
<style include="my-theme-xxx"></style>
<style>
:host{
...
my-theme.html
<dom-module id="my-theme-xxx"><template><style>
my-app {
--app-primary-color: grey;
--app-secondary-color: black;
--app-text-color: white;
}
</style></template></dom-module>
Dynamic
Yet I found only this way to change element css by theme dynamicaly.
<link rel="import" href="my-css.html">
<dom-module id="my-app">
<template>
<style include="my-css"></style>
<button class="btn-primary-dark">Dark button</button>
<button class="btn-primary-light">Light button</button>
<button class="btn-primary-default">Default button</button>
<br><br>
<button on-click="setTheme1">Set theme 1</button>
<button on-click="setTheme2">Set theme 2</button>
</template>
<script>
class MyApp extends Polymer.Element {
static get is() { return 'my-app'; }
// Dynamicaly change vars
setTheme1() {
Polymer.updateStyles({
'--dark-primary-color' : '#689F38',
'--default-primary-color' : '#8BC34A',
'--light-primary-color' : '#DCEDC8',
'--text-primary-color' : '#212121'
});
}
setTheme2() {
Polymer.updateStyles({
'--dark-primary-color' : '#1f8f37',
'--default-primary-color' : '#818bbf',
'--light-primary-color' : '#f0e82f',
'--text-primary-color' : '#333333'
});
}
}
window.customElements.define(MyApp.is, MyApp);
</script>
</dom-module>
my-css.html
<dom-module id="my-css">
<template>
<style>
.btn-primary-dark {
background-color: var(--dark-primary-color);
color: var(--secondary-text-color);
}
.btn-primary-light {
background-color: var(--light-primary-color);
color: var(--secondary-text-color);
}
.btn-primary-default {
background-color: var(--default-primary-color);
color: var(--secondary-text-color);
}
</style>
</template>
</dom-module>
Digging into documentation of Polymer I found this quote from this official Polymer doc page
Note: You should only use custom-style to define styles for the main document. To define styles for an element's local DOM, just use a < style> block.
And moreover, from this older documentation
Frequently you want to define custom property values at the document level, to set a theme for an entire application, for example. Because custom properties aren't built into most browsers yet, you need to use a special custom-style tag to define custom properties outside of a Polymer element. Try adding the following code inside the tag of your index.html file
Moving the my-theme.html into the index.html page, I am able to retrieve it from my polymer element using
document.querySelector('#current_theme')
Beside this, I am not able to change the theme dynamically. But since the title of the question was about accessing the resource, I solved the problem moving it. As craPkit said, I am not sure what I was trying to do is actually possible.

Polymer - binding core-style

I was trying to be able to color an element based on the attribute. I know, that binding inside is not supported, so I decided to try
<core-style id="block-elem">
:host {
background-color: {{g.bgcolor}};}
</core-style>
When I tried it with:
<polymer-element name="block-elem" attributes="bgcolor" noscript>
...
<script>
CoreStyle.g.bgcolor = 'red';
</script>
everything worked. But what I really want to do is to create similar objects with different colors. So I tried
<polymer-element name="block-elem" attributes="bgcolor">
<script>
Polymer('block-elem', {
bgcolor: "",
}
);CoreStyle.g.bgcolor = bgcolor;
</script>
I am creating object with
<block-elem bgcolor="red">TEST</block-elem>
and nothing. Is it possible to implement that funcionality? Maybe there is another option which I didn't even think of.
Summary
If all you want is to style each element differently based on a
(possibly data bound) attribute. Then it is simpler to use
an observer to set the elements style element instead of using the
core-style element.
observe: {
bgcolor: function() {
console.log("Setting color to " + this.bgcolor);
this.style.backgroundColor = this.bgcolor;
}
}
But if you are want to do something more complex with core-style such as setting the theme of an element based on one attribute. Then using a css attribute selector works in both chrome and in polyfill browsers (tested with firefox).
The below example shows the difference of using:
global: changes all elements on the page at once. Good if you want a
consistent theme across elements
nested themes: my favorite but doesn't work in polyfill browsers currently
css attribute selector: works across at least chrome and firefox on linux
element styling: setting the each element style directly
Components
<link rel="import" href="../../webcomponents/bower_components/polymer/polymer.html">
<link rel="import" href="../../webcomponents/bower_components/core-style/core-style.html">
<!-- Based on ideas from https://www.polymer-project.org/0.5/docs/elements/core-style.html -->
<!-- Using global sets all core-styles that use the global variable -->
<core-style id="demo-bindingcorestyle-style-global">
:host {
display: block;
background-color: {{g.bgcolor}};
}
</core-style>
<polymer-element name="demo-bindingcorestyle-global" attributes="bgcolor">
<template>
<core-style ref="demo-bindingcorestyle-style-global"></core-style>
GLOBAL: This is a test trying to set the background to {{bgcolor}} but all elements share global and will be the last color
</template>
<script>
Polymer({
observe: {
bgcolor: function() {
console.log("Setting color to " + this.bgcolor);
// All elements get this global style
CoreStyle.g.bgcolor = this.bgcolor;
}
}
});
</script>
</polymer-element>
<!-- To specify different colors use a Common base style and then themed styles that can be changed -->
<!-- NOTE: id cannot use "-" in name if it is going to be used with list. syntax because it is treated as subtractions -->
<core-style id="demobindingcorestylestylecommon">
:host {
display: block;
}
</core-style>
<core-style id="demo-bindingcorestyle-style-red">
{{list.demobindingcorestylestylecommon.cssText}}
:host {
background-color: red;
color: yellow;
}
</core-style>
<core-style id="demo-bindingcorestyle-style-blue">
{{list.demobindingcorestylestylecommon.cssText}}
:host {
background-color: blue;
color: white;
}
</core-style>
<core-style id="demo-bindingcorestyle-style-green">
{{list.demobindingcorestylestylecommon.cssText}}
:host {
background-color: green;
color: black;
}
</core-style>
<polymer-element name="demo-bindingcorestyle-substyles" attributes="theme">
<template>
<core-style ref={{themename}}></core-style>
Themed: This is a test trying to specify the theme as {{theme}} works in latest chrome with shadowdom but fails in polyfill browsers.
</template>
<script>
Polymer({
theme: 'blue',
computed: {
themename: '"demo-bindingcorestyle-style-"+theme'
},
observe: {
theme: function() {
console.log("Setting theme to " + this.theme);
// All elements get this global style
//this.$.mystyle.ref = "demo-bindingcorestyle-style-" + this.theme;
}
}
});
</script>
</polymer-element>
<!-- Using attribute selectors works both with shadowdom and polyfill -->
<core-style id="demo-bindingcorestyle-style-themeable">
:host {
display: block;
}
:host([theme="red"]) {
background-color: red;
<!-- Probably will want other themed color here -->
}
:host([theme="green"]) {
background-color: green;
<!-- Probably will want other themed color here -->
}
:host([theme="blue"]) {
background-color: blue;
<!-- Probably will want other themed color here -->
}
</core-style>
<polymer-element name="demo-bindingcorestyle-themeable" attributes="theme" noscript>
<template>
<core-style ref="demo-bindingcorestyle-style-themeable"></core-style>
Themed: This is a test trying to specify the theme as {{theme}} it should actually work.
</template>
</polymer-element>
<!-- Set background based on bgcolor attribute -->
<polymer-element name="demo-bindingcorestyle-justdoit" attributes="bgcolor">
<template>
Just set bgcolor: This is a test trying to set the background to {{bgcolor}} but all elements share global and will be the last color
</template>
<script>
Polymer({
observe: {
bgcolor: function() {
console.log("Setting color to " + this.bgcolor);
this.style.backgroundColor = this.bgcolor;
}
}
});
</script>
</polymer-element>
Usage
<html lang="en">
<head>
<script src="../../webcomponents/bower_components/webcomponentsjs/webcomponents.min.js"></script>
<link rel="import" href="../../webcomponents/bower_components/polymer/polymer.html">
<link rel="import" href="demo-bindingCoreStyle.html">
<title>demo-bindingCoreStyle test page</title>
</head>
<body>
<h1>Demo</h1>
<h2>Using Global</h2>
<demo-bindingcorestyle-global bgcolor="red"></demo-bindingcorestyle-global>
<hr>
<demo-bindingcorestyle-global bgcolor="green"></demo-bindingcorestyle-global>
<hr>
<demo-bindingcorestyle-global bgcolor="blue"></demo-bindingcorestyle-global>
<h2>Using substyles</h2>
<demo-bindingcorestyle-substyles theme="blue"></demo-bindingcorestyle-substyles>
<hr>
<demo-bindingcorestyle-substyles theme="red"></demo-bindingcorestyle-substyles>
<hr>
<demo-bindingcorestyle-substyles theme="green"></demo-bindingcorestyle-substyles>
<h2>Using theming with attribute css selector</h2>
<demo-bindingcorestyle-themeable theme="blue"></demo-bindingcorestyle-themeable>
<hr>
<demo-bindingcorestyle-themeable theme="green"></demo-bindingcorestyle-themeable>
<hr>
<demo-bindingcorestyle-themeable theme="red"></demo-bindingcorestyle-themeable>
<h2>Just set background</h2>
<demo-bindingcorestyle-justdoit bgcolor="blue"></demo-bindingcorestyle-justdoit>
<hr>
<demo-bindingcorestyle-justdoit bgcolor="green"></demo-bindingcorestyle-justdoit>
<hr>
<demo-bindingcorestyle-justdoit bgcolor="red"></demo-bindingcorestyle-justdoit>
</body>
</html>

Append Style to DOM not Replacing Existing

How can I append style element to DOM without eliminating existing style on the item (eg color, text-align, etc)?
The event calls the function, but the problem is 'Style' gets completely replaced with the single item instead.
I have simple code triggered on the event:
function changeback(onoff) {
if(onoff) {
document.getElementById("field1").style.background="#fff";
} else
document.getElementById("field1").style.background="#000";
}
Here is how you can do it :
var elem = document.getElementById('YOUR ELEMENT ID');
elem.style.setProperty('border','1px solid black','');
elem.style is an object implementing the CSSStyleDeclaration interface which supports a setProperty function. If you check the style property in Firebug now, you will notice addition of the border property within the style attribute of the element.
Which browser are you using? In Chrome, this works for me:
<html>
<head>
<style type="text/css">
.test { background: #ff0000; font-family: "Verdana"; }
</style>
<script type="text/javascript">
function changeback(onoff)
{
if(onoff){
document.getElementById("field1").style.background="#0f0";
} else {
document.getElementById("field1").style.background="#000";
}
}
</script>
</head>
<body>
<h1>Test</h1>
<p id="field1" onclick="changeback(true);" class="test">This is a test</p>
</body>
</html>
When I click on the text, the background color changes, but the rest of the style (in this case, the font) stays the same.
Is that what you're trying to do?

Categories