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.
Related
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
How would I do something like this:
<style>
Nested {
color: blue;
}
</style>
<Nested />
i.e. How do I apply a style to a component from its parent?
You need to pass props to the parent component with export let, then tie those props to class or style in the child component.
You can either put a style tag on the element in the child you want to style dynamically and use a variable you export for the parent to determine the value of a style directly, then assign the color on the tag like this:
<!-- in parent component -->
<script>
import Nested from './Nested.svelte';
</script>
<Nested color="green"/>
<!-- in Nested.svelte -->
<script>
export let color;
</script>
<p style="color: {color}">
Yes this will work
</p>
Upside here is flexibility if you only have one or two styles to adjust, downside is that you won't be able to adjust multiple CSS properties from a single prop.
or
You can still use the :global selector but just add a specific ref to the element being styled in the child like so:
<!-- in parent component -->
<script>
import Nested from './Nested.svelte';
</script>
<Nested ref="green"/>
<style>
:global([ref=green]) {
background: green;
color: white;
padding: 5px;
border-radius: .5rem;
}
</style>
<!-- in Nested.svelte -->
<script>
export let ref;
</script>
<p {ref}>
Yes this will work also
</p>
This ensures global only affects the exact ref element inside the child it's intended for and not any other classes or native elements. You can see it in action at this REPL link
The only way I can think of is with an additional div element.
App.svelte
<script>
import Nested from './Nested.svelte'
</script>
<style>
div :global(.style-in-parent) {
color: green;
}
</style>
<div>
<Nested />
</div>
Nested.svelte
<div class="style-in-parent">
Colored based on parent style
</div>
Multiple Nested elements
You could even allow the class name to be dynamic and allow for different colors if you use multiple Nested components. Here's a link to a working example.
You could use inline styles and $$props...
<!-- in parent component -->
<script>
import Nested from './Nested.svelte';
</script>
<Nested style="background: green; color: white; padding: 10px; text-align: center; font-weight: bold" />
<!-- in Nested.svelte -->
<script>
let stylish=$$props.style
</script>
<div style={stylish}>
Hello World
</div>
REPL
using :global(*) is the simplest solution.
No need to specify a class in the child if you want to style all immediate children for example
In the parent component:
<style>
div > :global(*) {
color: blue;
}
<style>
<div>
<Nested />
<div>
Nested will be blue.
I take a look and found nothing relevant (maybe here), so here is an alternative by adding <div> around your custom component.
<style>
.Nested {
color: blue;
}
</style>
<div class="Nested">
<Nested />
</div>
Maybe you will found something but this one works.
The way I do it is like this:
<style lang="stylus">
section
// section styles
:global(img)
// image styles
</style>
This generates css selectors like section.svelte-15ht3eh img that only affects the children img tag of the section tag.
No classes or tricks involved there.
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...
I want to separate the styles in a Vue.js component in modules.
Each style module will have far more than just a class, and new classes will be added regularly. So, it will be hard to change the entire component's template. So, I'm looking for a more practical solution.
I came with the idea of, using a v-if in the styles, but not exactly sure how it should be implemented or if such thing is possible after all.
It will be way more practical, if just depending on the name sent with props, the entire styles changes.
<template>
<div class="color-text">
Text in the color of the class with just the name
</div>
</template>
<script>
export default {
name: 'comp',
props: ['name']
}
</script>
<!-- General styles -->
<style scoped>
div{
float: right;
}
</style>
<!-- red styles -->
<style module v-if="name === 'red'">
.color-text{
color: red;
}
</style>
<!-- blue styles -->
<style module v-if="name === 'blue'">
.color-text{
color: blue;
}
</style>
<!-- green styles -->
<style module v-if="name === 'green'">
.color-text{
color: green;
}
</style>
If I was tackling this I'd use a transitive class value. and not worry about props at all.
<template>
<div class="color-text">
Text in the color of the class with just the name
</div>
</template>
<script>
export default {
name: 'comp'
}
</script>
<!-- General styles -->
<style scoped>
div{
float: right;
}
.red .color-text{
color: red;
}
.blue .color-text{
color: blue;
}
.green .color-text{
color: green;
}
</style>
then you can use the class property to pass in your color type
<div id="app">
<comp class="red"></comp>
<comp class="green"></comp>
<comp class="blue"></comp>
</div>
I've put together an example jsfiddle though it may need some tweaking when it comes to scoped styles and how webpack handles the injection
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>