I recently started getting into WebComponents and I must say I have found it ground breaking so far! To get a better grasp of the concept, I am trying to adopt designs from my earlier project into reusable web components. I started by trying to create a Table element with some added features.
First of, I declared a minimal template for the component:
<template id="data-table-template">
<table>
<thead></thead>
<tbody></tbody>
<tfoot></tfoot>
</table>
</template>
I use this template to create tables by taking table header values and row values as attributes. The element is used in the following manner:
<eur-data-table
headers="getHeaders()",
rowdata="getRowData()"
host="#data-table-host"
template="#data-table-template"
></eur-data-table>
In the component prototype connectedCallback
method, I add the headers, rows and set style for the component. The generated shadow DOM fragment from Chrome Element inspector is as follows:
<eur-data-table headers="getHeaders()" ,="" rowdata="getRowData()" host="#data-table-host" template="#data-table-template" theme="firebrick">
<table>
<style id="styleBlock"></style>
<thead>
<th>Name</th>
<th>Age</th>
</thead>
<tbody>
<tr>
<td>Babu</td>
<td>60</td>
</tr>
<tr>
<td>Shyam</td>
<td>35</td>
</tr>
</tbody>
<tfoot></tfoot>
</table>
</eur-data-table>
The problem lies in adding style rules. In the component class, I create a style
element and I append it to the shadow DOM before adding rules to the style.sheet
property. I checked the console output of style.sheet
and it looks like all the rules were added. However, after inserting the style element to the shadow DOM, there are no rules as can be seen from the rendered output above.
In the component class, style is added in the following way:
connectedCallback() {
let template = document.querySelector(DataTable._parseId(this.template));
if(!template) throw new DOMException('Host and/or template ids are undefined');
if(!this.hasAttribute('theme')) this.setAttribute('theme', 'firebrick');
let style = document.createElement('style');
style.setAttribute('id', 'styleBlock');
// WebKit Hack
style.appendChild(document.createTextNode(""));
this.shadowRoot.appendChild(style);
this._table = template.content.cloneNode(true);
this._setupHeaders();
this._setupBody();
this._setupStyle(style.sheet); // style is not being applied!!
console.dir(style.sheet); // logs a CSSStyleSheet object
this.shadowRoot.appendChild(this._table);
}
In the connectedCallback
method, _setupStyle
is passed a reference to the style sheet where, it adds the rules in the following manner (the rules are expressed as javascript object):
_setupStyle(sheet) {
if(!sheet) throw new DOMException('sheet is undefined', 'StylesheetNotFoundException');
let rules = window.themes[this.theme];
if(!rules) throw new DOMException('Theme is undefined');
Object.keys(rules)
.sort((r1, r2) => rules[r1].order - rules[r2].order)
.forEach(rule => {
let o = _.omit(rules[rule], 'order');
let i = rule.order;
let s = this._makeRule(o, rule);
sheet.insertRule(s, i);
});
}
The order
value is an integer number used for sorting the style rules in order. Once sorted, the order
key and its value is omitted from the object before converting it to a DOMString:
_makeRule(value, rule) {
return `${rule} ${JSON.stringify(value).replace(new RegExp(/,/g), ';')}`;
}
Logging the style.sheet
property prints:
CSSStyleSheet
cssRules: CSSRuleList
0: CSSStyleRule {selectorText: "tfoot", style: CSSStyleDeclaration, styleMap: StylePropertyMap, type: 1, cssText: "tfoot { }", …}
1: CSSStyleRule {selectorText: "tr:last-child > td", style: CSSStyleDeclaration, styleMap: StylePropertyMap, type: 1, cssText: "tr:last-child > td { }", …}
2: CSSStyleRule {selectorText: "td:last-child", style: CSSStyleDeclaration, styleMap: StylePropertyMap, type: 1, cssText: "td:last-child { }", …}
3: CSSStyleRule {selectorText: "td", style: CSSStyleDeclaration, styleMap: StylePropertyMap, type: 1, cssText: "td { }", …}
4: CSSStyleRule {selectorText: "tr:first-child", style: CSSStyleDeclaration, styleMap: StylePropertyMap, type: 1, cssText: "tr:first-child { }", …}
5: CSSStyleRule {selectorText: "tr:last-child", style: CSSStyleDeclaration, styleMap: StylePropertyMap, type: 1, cssText: "tr:last-child { }", …}
6: CSSStyleRule {selectorText: "tr", style: CSSStyleDeclaration, styleMap: StylePropertyMap, type: 1, cssText: "tr { }", …}
7: CSSStyleRule {selectorText: "th:last-child", style: CSSStyleDeclaration, styleMap: StylePropertyMap, type: 1, cssText: "th:last-child { }", …}
8: CSSStyleRule {selectorText: "th", style: CSSStyleDeclaration, styleMap: StylePropertyMap, type: 1, cssText: "th { }", …}
9: CSSStyleRule {selectorText: "thead", style: CSSStyleDeclaration, styleMap: StylePropertyMap, type: 1, cssText: "thead { }", …}
10: CSSStyleRule {selectorText: "thead tbody tfoot", style: CSSStyleDeclaration, styleMap: StylePropertyMap, type: 1, cssText: "thead tbody tfoot { }", …}
11: CSSStyleRule {selectorText: "table", style: CSSStyleDeclaration, styleMap: StylePropertyMap, type: 1, cssText: "table { }", …}
My question is: since the component gets rendered without error and since, I am adding all the rules as expected and attaching both, the style and table elements to the shadow DOM renders an element as expected then, why aren't my CSS rules being applied to the rendered content?