Encapsulate CSS into a shadow DOM
- @published: May 2023
- @author: Elmar Hinz
- @workspace:
conf/www/tutor
- @name:
toggle-card-with-shadow-dom
- @id:
tcwsd
You learn:
- how to attach a shadow dom inside the constructor
- how to clean up the lifecycle
- how to get rid of the prefixes
- how to migrate BEM methodology with nested CSS modifiers
Introducing the usage of the shadow dom. It encapsulates the dom of the card, the nodes, the CSS, even the ids. This gives you some advantages. Organizing the CSS is getting easier. Also the lifecycle is getting easier to handle. The full shadow dom can already be created in the constructor, so that the elements are accessible early.
- tutorial 04: Plain Vanilla JavaScript Toggle Card
- you know how to register the
card.js
as a resource - you know how to create the helper entity of type
boolean
akatoggle
- you know how to add and edit a card
- you know how to reload
card.js
after editing
Take the same steps as in the previous tutorial. Name the helper entity
tcwsd
this time.
The code of the previous tutorial is only slightly modified. The function
doAttach()
is modified to attach a shadow dom. The CSS is simplified.
It's basically a single line of code making a huge difference.
Before:
doAttach() {
this.append(this._elements.style, this._elements.card);
}
After:
doAttach() {
this.attachShadow({ mode: "open" });
this.shadowRoot.append(this._elements.style, this._elements.card);
}
The attachment of the shadow dom is straight forward. Now you are allowed to do the attachment already from the constructor.
constructor() {
super();
this.doCard();
this.doStyle();
this.doAttach();
this.doQueryElements();
this.doListen();
}
setConfig()
can now focus upon it's own tasks.
Before:
setConfig(config) {
console.log("ToggleCardVanillaJs.setConfig()")
this._config = config;
if (!this._isAttached) {
this.doAttach();
this.doQueryElements();
this.doListen();
this._isAttached = true;
}
this.doCheckConfig();
this.doUpdateConfig();
}
After:
setConfig(config) {
this._config = config;
this.doCheckConfig();
this.doUpdateConfig();
}
The CSS is encapsulated now. We can drop the block aka prefix and stay with elements and modifiers. Exploiting recent CSS advancements we can drop composed class names at all.
In CSS the BEM class .tcws-error-hidden
becomes .error.hidden
. I still
recommend to use the combination of element and modifier .error.hidden
over
the naked modifier .hidden
. Even the latter becomes less error prone as the
shadow dom is rather small.
.error { text-color: red; }
.error.hidden { display: none; }
Some browsers already support nested CSS. CSS will become pretty organized.
.error {
text-color: red;
&.hidden { display: none; }
}
The HTML is adjusted accordingly.
doCard() {
this._elements.card = document.createElement("ha-card");
this._elements.card.innerHTML = `
<div class="card-content">
<p class="error error hidden">
<dl class="dl">
<dt class="dt"></dt>
<dd class="dd">
<span class="toggle">
<span class="button"></span>
</span>
<span class="value">
</span>
</dd>
</dl>
</div>
`;
}