I'm trying to use ShadowDomv1 (with https://github.com/webcomponents/webcomponentsjs and https://github.com/webcomponents/shadycss) but it's not working.
The ShadowDom by itself works, but the css is not encapsulated (as we can see with the h2 css rule).
It works as intended on Chrome and Safari (but they both support ShadowDomv1 natively).
Am I missing something or is it impossible ?
Here the jsbin : http://jsbin.com/maqohoxowu/edit?html,output
And the code :
<script type="text/javascript" src="https://rawgithub.com/webcomponents/webcomponentsjs/master/webcomponents-hi-sd-ce.js"></script>
<style type="text/css">
h2 {
color: red;
border-bottom: 1px black dotted;
}
</style>
<h2>h2 red and dotted</h2>
<my-element>
</my-element>
<template id="myElementTemplate">
<style scope="my-element">
h2 {color: blue}
</style>
<div>
<h2>h2 blue and not dotted !</h2> <!-- Should not be dotted because of the encapsulation -->
</div>
</template>
<script type="text/javascript">
ShadyCSS.prepareTemplate(myElementTemplate, 'my-element');
class MyElement extends HTMLElement {
connectedCallback() {
ShadyCSS.styleElement(this);
if (!this.shadowRoot) {
this.attachShadow({mode: 'open'});
this.shadowRoot.appendChild(document.importNode(myElementTemplate.content, true));
}
ShadyCSS.styleElement(this);
}
}
customElements.define("my-element", MyElement);
</script>
You could use the CustomStyleInterface to apply document level styles only to non Shadow DOM:
const CustomStyleInterface = window.ShadyCSS.CustomStyleInterface;
CustomStyleInterface.addCustomStyle(document.querySelector('style.doc-level'));
class MyElement extends HTMLElement {
connectedCallback() {
this.attachShadow({ mode: 'open' });
this.shadowRoot.appendChild(document.importNode(myElementTemplate.content, true));
}
}
customElements.define("my-element", MyElement);
<script src="https://rawgithub.com/webcomponents/webcomponentsjs/master/webcomponents-hi-sd-ce.js"></script>
<script src="https://rawgit.com/webcomponents/shadycss/master/custom-style-interface.min.js"></script>
<style class="doc-level">
h2 {
color: red;
border-bottom: 1px black dotted;
}
</style>
<h2>h2 red and dotted</h2>
<my-element></my-element>
<template id="myElementTemplate">
<style>
h2 {color: blue}
</style>
<div>
<h2>h2 blue and not dotted !</h2>
</div>
</template>
As per Mozillas platform status page, Shadow DOM is still under development.
https://platform-status.mozilla.org/#shadow-dom
The polyfill can not emulate the encapsulation of CSS that is handled natively by true ShadowDOM.
Instead, if you plan to use both then avoid using simple CSS selectors. Instead try using a CSS naming pattern like BEM: http://getbem.com/introduction/
This will allow your CSS to work, most of the time, in either true ShadowDOM and in ShadyDOM.
Related
I would like to add a property directly to a class instead of objects that have given class.
It should work for dynamically added elements as well.
I tried to do it by using $(".myElement").css("background", "green"); but it works only for already existing elements, the new elements are created with default class properties.
My code is:
<html>
<head>
<script src="https://code.jquery.com/jquery-1.12.4.js"></script>
<style>
.myElement{
width: 20px;
height: 20px;
background: red;
margin: 5px;
}
</style>
</head>
<body>
<div id="elementsContainer">
<div class="myElement"></div>
<div class="myElement"></div>
</div>
<button id="addClassProperty">Add class property</button>
<button id="addNewElement">Add new element</button>
<script>
$("#addClassProperty").click(function(){
$(".myElement").css("background", "green");
});
$("#addNewElement").click(function(){
$("#elementsContainer").append("<div class='myElement'></div>");
});
</script>
</body>
</html>
The expected result should add a new property to all existing element and to every newly created elements without cast change property for newly created element.
Is there any way to solve this problem?
This is far simpler by adding a class to the parent container and a corresponding css rule for .myElement when that class exists
$("#addClassProperty").click(function() {
$('#elementsContainer').addClass('active')
});
$("#addNewElement").click(function() {
$("#elementsContainer").append("<div class='myElement'></div>");
});
.myElement {
width: 20px;
height: 20px;
background: red;
margin: 5px;
}
.active .myElement {
background: green;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="elementsContainer">
<div class="myElement"></div>
<div class="myElement"></div>
</div>
<button id="addClassProperty">Add class property</button>
<button id="addNewElement">Add new element</button>
Rather than "change class property", you want to change/add a css rule.
EDIT: better link --> Changing a CSS rule-set from Javascript
Or you could have one/multiple classes with existing css and change your objects' classes instead.
When you apply a green background color to elements, the style of the background color is entered into the style attribute. This is how the css() method works. Therefore, each new element has a background color of red, taken from the css.
To solve your problem, you can use the method with adding a class, or change the color of the background rule in the CSS itself. This can be done using cssRules by referring to the document. Like this:
[...document.styleSheets[0].cssRules].find((currentSel) => (currentSel.selectorText = ".myElement")).style.background = "green";
By placing this code inside the click event of the selector #addClassProperty.
$("#addClassProperty").click(function () {
[...document.styleSheets[0].cssRules].find((currentSel) => currentSel.selectorText = ".myElement").style.background = "green";
});
$("#addNewElement").click(function () {
$("#elementsContainer").append("<div class='myElement'></div>");
});
.myElement {
width: 20px;
height: 20px;
background: red;
margin: 5px;
}
<html>
<head>
<script src="https://code.jquery.com/jquery-1.12.4.js"></script>
<style></style>
</head>
<body>
<div id="elementsContainer">
<div class="myElement"></div>
<div class="myElement"></div>
</div>
<button id="addClassProperty">Add class property</button>
<button id="addNewElement">Add new element</button>
</body>
</html>
I'm learning the firsts concepts of js web components. I pretty nob in these and try to make a simple example. My component is just a button that pretends to change color to a div.
My example work I expected but I noticed that my approach is not too much "component way", if the element that I attempt to change is in my web component to instead outside of it.
This is my html file:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<link rel="stylesheet" href="index.css" />
</head>
<body>
<div class="main">
<h1>Yeah Web Components!</h1>
<my-component></my-component>
</div>
<script src="myComponent.js"></script>
</body>
</html>
This is my component .js:
const template = `
<style>
.container{
display: flex;
flex-direction: column;
align-items: center;
}
.box{
width: 100px;
height: 100px;
background: red;
}
.hi-button{
margin-top: 10px;
}
</style>
<div class="container">
<div class="box"></div>
<button class="hi-button">Change</button>
</div>
`;
class MyFirstTest extends HTMLElement{
constructor(){
super()
const shadowRoot = this.attachShadow({ mode: 'open' });
shadowRoot.innerHTML = template;
}
changeButtonColor(){
const box = this.shadowRoot.querySelector(".box");
if(box.style.background === 'red'){
box.style.background = 'blue';
}else{
box.style.background = 'red';
}
}
connectedCallback(){
const event = this.shadowRoot.querySelector(".hi-button");
event.addEventListener('click', () => this.changeButtonColor());
}
disabledCallback(){
const event = this.shadowRoot.querySelector(".button-test");
event.removeEventListener();
}
}
customElements.define('my-component', MyFirstTest);
As I said the functionality works fine but I don't want my div to be in my web component but in my html file and mi component be only the button.
For example, my html file would be something like this:
.......
<link rel="stylesheet" href="index.css" />
</head>
<body>
<div class="main">
<h1>Yeah Web Components!</h1>
<div class="my-div-to-change"></div>
<my-component></my-component>
</div>
</body>
.......
Is possible for web components to work in this way?
There are some notes about the published code:
CSS & shadowDOM
.container{
display: flex;
flex-direction: column;
align-items: center;
}
.hi-button{
margin-top: 10px;
}
This global CSS is never applied to HTML inside the Element shadowDOM.
Note you do not have to use shadowDOM.
Templates
Custom Elements API
shadowDOM
are 3 distinct technologies you can use without each other.
To apply the style, declare it inside the shadowDOM:
const template = `
<style>
.container{
display: flex;
flex-direction: column;
align-items: center;
}
.hi-button{
margin-top: 10px;
}
</style>
<div class="container">
<button class="hi-button">Change</button>
</div>
Templates
It is just a string, you are not using or declaring a <template>
Click event
The click event on the button, by default, bubbles up the DOM.
There is no need to set the handler on the <button> itself, you can set in on any parent.
Garbage Collection
No need to remove Listeners inside/on the Custom Element. The JavaScript Garbage Collection process will do that for you
RemoveListeners are required when you set Listeners on other DOM elements.
eg. When a Component sets a Listener on document
disabledCallback
Does not exist; its called disconnectedCallback()
composed = true
this.dispatchEvent(new CustomEvent("button-clicked", {
bubbles: true,
}));
This only works because the Event is not dispatched from shadowDOM.
But dispatch from inside shadowDOM, or wrap the Element in another Element with shadowDOM and the code will no longer work, because Custom Events, by default, do not 'escape' shadowDOM(s).
For that, you need to use:
this.dispatchEvent(new CustomEvent("button-clicked", {
bubbles: true,
composed: true
}));
Note: default Events like click do escape shadowDOM by default
Solutions
Without shadowDOM the only code required is:
global CSS now styles the button
customElements.define('my-component', class MyFirstTest extends HTMLElement {
connectedCallback() {
this.innerHTML = `<button class="hi-button">Change</button>`;
this.onclick = (evt) => document.querySelector(".box").classList.toggle("blue");
}
});
<style>
.box {
width: 80px;
height: 80px;
background: red;
}
.blue {
background: blue;
}
.hi-button {
margin-top: 10px;
}
</style>
<div class="main">
<h1>Yeah Web Components!</h1>
<div class="box"></div>
<my-component></my-component>
</div>
With shadowDOM
There is less need for CSS classes, because all HTML is isolated in the shadowDOM
<template> can be declared as HTML (so your IDE neatly formats everything inside) Referenced by this.nodeName, so you can re-use code
super() & attachShadow() are functions setting and returning this scope and this.shadowRoot; so they can be chained
Custom Events are great; but this code can do with the default click event; the listener checks if the correct button was clicked
These are just Web Component technical enhancements, loads more functional enhancements possible, all depends on the use case
Do read ::slotted CSS selector for nested children in shadowDOM slot before you continue with <slot> content
Code could look something like this:
<template id="MY-COMPONENT">
<style>
div { display: flex; flex-direction: column; align-items: center }
button { margin-top: 10px }
</style>
<div>
<button><slot></slot></button>
</div>
</template>
<div class="main">
<my-component color="blue">Blue!</my-component>
<my-component color="gold">Gold!</my-component>
<my-component color="rebeccapurple">Purple!</my-component>
</div>
<script>
document.addEventListener("click", (evt) => {
if (evt.target.nodeName == 'MY-COMPONENT')
document.querySelector(".main").style.background = evt.target.getAttribute("color");
});
customElements.define('my-component', class extends HTMLElement {
constructor() {
let template = (id) => document.getElementById(id).content.cloneNode(true)
super()
.attachShadow({mode: 'open' })
.append( template(this.nodeName) );
}
});
</script>
The best way to maintain the independence between components is use events.
Web components dispatch an event that is listened by the parent container. And then, parent do the necessary action.
If you want to talk "directly" between component and parent, the system is pretty coupled and is not a good way. Of course you can, but is not recommended.
You can check this answer which is clearer.
Also, answering your question using events. Is as simple as this:
First, into your component you have to dispatch the event in this way:
this.dispatchEvent(new CustomEvent("button-clicked", {
bubbles: true,
}));
You can check here the use of this.
Also, the parent will listen using:
document.addEventListener("button-clicked", changeColor);
So, when the button is clicked, the parent will trigger the function changeColor with the logic inside you want.
In this way you are using a Web Component to do actions into parent container but the system is not coupled. Parent can work without child and child can work without parent. Both can be used separately. Of course, the event will not be dispatch or listen but there is no a dependency between components.
Also this is an example how it works.
const template = `
<div class="container">
<button class="hi-button">Change</button>
</div>
`;
class MyFirstTest extends HTMLElement{
constructor(){
super()
const shadowRoot = this.attachShadow({ mode: 'open' });
shadowRoot.innerHTML = template;
}
changeButtonColor(){
//Call parent
this.dispatchEvent(new CustomEvent("button-clicked", {
bubbles: true,
}));
}
connectedCallback(){
const event = this.shadowRoot.querySelector(".hi-button");
event.addEventListener('click', () => this.changeButtonColor());
}
disabledCallback(){
const event = this.shadowRoot.querySelector(".button-test");
event.removeEventListener();
}
}
customElements.define('my-component', MyFirstTest);
<!DOCTYPE html>
<html lang="en">
<head>
<style>
.container{
display: flex;
flex-direction: column;
align-items: center;
}
.box{
width: 100px;
height: 100px;
background: red;
}
.hi-button{
margin-top: 10px;
}
</style>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<link rel="stylesheet" href="index.css" />
</head>
<body>
<div class="main">
<h1>Yeah Web Components!</h1>
<div class="box"></div>
<my-component></my-component>
</div>
<script src="myComponent.js"></script>
<script>
//Add listener to do the action
document.addEventListener("button-clicked", changeColor);
function changeColor(){
var box = document.querySelector(".box");
if(box.style.backgroundColor === 'red'){
box.style.backgroundColor = 'blue';
}else{
box.style.backgroundColor = 'red';
}
}
</script>
</body>
</html>
The thing is for js purpose I want a particular <style> tag to be removed from my document on an event. So for that, within my knowledge, I have added a class for it and removed on my event, eg:
<style class="custome_for_remove">
.selected_par>td,
.footer-tr>td {
position: relative;
display: table-cell!important
}.....
</style>
<script>
function customeRemove() {
$('.custome_for_remove').remove()
}
</script>
My concern is this HTML standard, is this a proper method.? I couldn't find any questions or answer related to this.
Yes! This totally works and it also seems to be valid syntax. Here's a little demonstration. According to https://validator.w3.org/ having a class in your style tag is considered valid html (you can also use an id if you want).
$("#test").click(() => {
$(".customClass").remove();
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<style class="customClass">
p {
color: red;
}
</style>
<p>
Test
</p>
<button id="test">
remove
</button>
You can try the below code. It removes CSS perfectly.
function removeJs(){
$(".custome_for_remove").remove();
}
<script src="https://ajax.aspnetcdn.com/ajax/jQuery/jquery-3.4.1.min.js"></script>
<style class="custome_for_remove">
p {
color: red;
cursor: pointer;
}
</style>
<p onclick="removeJs()">
Click here!
</p>
Just learning polymer. I want to grab the color of fixed-header when it is clicked.
<link rel="import" href="../polymer/polymer.html">
<polymer-element name="fixed-header" attributes="height" style="background-color:blue">
<template>
<style>
:host {
display: block;
background-color: red;
}
::content * {
list-style-type: none;
}
</style>
<header layout horizontal on-click="{{changeColor}}">
<content select="li"></content>
</header>
</template>
<script>
Polymer('fixed-header', {
changeColor: function() {
var color = this.style.backgroundColor
console.log(color)
}
});
</script>
</polymer-element>
When I don't use the inline style on polymer-element, I can't use this.style.backgroundColor, even though it is definitely changing color to be red. Why can't I use this.style.backgroundColor when it is just through the template style tag?
Also, I'm trying to set the backgroundColor, but I can't do that either.
Returning an object representation of the contents of a node's style attribute is the expected behavior of the style property. What you want is getComputedStyle():
var color = getComputedStyle(this).backgroundColor;
Here's a working jsbin.
To your second comment, setting style works fine for me in Chrome 36 and 38.
<!DOCTYPE html>
<head>
<meta charset="UTF-8">
<title>Working With DOM</title>
<script src="jquery.js"></script>
<script type="text/javascript">
$(document).ready(function()
{
$("#gold").addClass("highlight");
});
</script>
<style type="text/css">
body{background-color:#FFCC66;}
#wrap
{margin:0 auto;
border:2px solid #CC8320;
height:500px;}
h1{font-style:italic;
color:#A48713; padding-left:10px;}
#gold{width:200px;
background-color:#D49F55;
height:150px; margin:20px; float:left;height:200px}
input{border:1px solid black; width:150px; margin:0 20px;
background-color:#AA9F55; color:#553F00;font-weight:bolder;text-align:center; }
.info{border:1px solid black; width:150px;background-color:#AA9F55; color:#553F00;font-weight:bolder;text-align:center;margin:0 20px; }
.highlight{background-color:green;}
</style>
</head>
<body>
<div id="wrap">
<h1> Learning Web Engineering Online</h1>
<div data-price="399.99" id="gold">
<h3>Gold Member</h3>
<ul class="course">
<li>HTML5</li>
<li>css3</li>
<li>jquery</li>
</ul>
<form>
<input type="button" value="GET PRICE"/>
</form>
</div>
</div>
</body>
I am having problem with the code above that when using jquery i add class highlight to element with id=gold and inspect it in chrome, although the class is being added to the code the style rule mentioned in highlight class doesn't output in browser. the element is being selected but not styled. what am i doing wrong please help someone.
You should use !important to work it:
.highlight{background-color:green !important;}
Note:
Browser uses ID with higher importance than a class name.
change your css to
#gold.highlight{background-color:green;}
You need to change the priority style for .highlight. Just add #gold before the .highlight style
#gold.highlight{background-color:green;}
The problem here is due to the precendence of CSS selectors. An id selector will override a class selector, so you need to either make the class selector more specific (preferred method):
#gold.highlight { background-color: green; }
Example fiddle
Or aleternatively add !important to it:
.highlight { background-color: green !important; }
However the latter can lead to issues when you have competing !important rules, so it's best to avoid it where possible.
highlight gets applied but as there is background-color property defined in ID it will not be overridden by class value.
As mentioned by #cocco you can use #gold.highlight to override it.
Id has greater precision due to conflict resolution, class css is overridden by your #gold id css
change your class
.highlight{background-color:green !important;}