Polymer - binding core-style - javascript

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>

Related

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

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

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 target a component in svelte with css?

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.

Vue.js conditional module styles

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

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.

Categories