Style sharing and object oriented CSS - craftkit code pattern
Tags: craftkit, javascript, shadowdom, webdev
Published: 2020-11-05 15:03:41

craftkit encapsulates its CSS by ShadowDOM. So you can use simple CSS class name for your component. 

And the CSS is defined by `style()` instance method. So you can append definition to its super class. 

This spec allows you to implement object oriented styling. Now the time to say goodbye to CSS name convention. 

This post describes how to inherit style. In other words, how to share style sheet across Craft.UI.View in your application.

## Style Method overriding

```javascript 
class Hello extends Craft.UI.View {
	style(componentId){
		return `
			.root {
				margin: 50px;
			}
			.text {
				color: black;
			}
		`;
	}
	template(componentId){
		return `
			<div id="root" class="root">
				<h1>Hello</h1>
				<p class="text">World!</p>
			</div>
		`;
	}
}

class HelloRed extends Hello {
	style(componentId){
		return super.style(componentId) + `
			.text {
				color: red;
			}
		`;
	}
}
``` 

In HelloRed instance, `margin:50px` is cascaded, and `color:red` is applied as usual CSS rule. 

```javascript 
class Aloha extends Craft.UI.View {
	style(componentId){
		return `
			.root {
				margin: 70px;
			}
			.text {
				color: blue;
			}
		`;
	}
	template(componentId){
		return `
			<div id="root" class="root">
				<h1>Aloha</h1>
				<p class="text">World!</p>
			</div>
		`;
	}
}

class AlohaRed extends Example {
	style(componentId){
		return super.style(componentId) + `
			.text {
				color: red;
			}
		`;
	}
}
``` 

`Hello` and `Aloha` have same CSS class definition, but its instance can exist simultaneously without conflict.

## Pseudo multiple inheritance

Craft.UI.View is also a plain JavaScript class. So you can't define multiple inheritance for you view class. 

But style method is defined dynamically, and resolved by same principle of CSS. So you can implement pseudo multiple style inheritance something like this:

```Javascript 
// file: HeaderStyling.js

export class HeaderStyling {
	static css(){
		return `
			h1 { color:red; }
		`;
	}
}

//file: ParagraphStyling.js

export class ParagraphStyling {
	static css(){
		return `
			p { line-height: 2em; }
		`;
	}
}
``` 

Import and load them in your style method.

```javascript 
import { HeaderStyling } from 'HeaderStyling.js';
import { ParagraphStyling } from 'ParagraphStyling.js';

class Hello extends Craft.UI.View {
	style(componentId){
		return HeaderStyling.css() + ParagraphStyling.css() + `
			p { color: blue; }
		`;
	}
	template(componentId){
		return `
			<div id="root" class="root">
				<h1>Hello</h1>
				<p>World!</p>
			</div>
		`;
	}
}
``` 

## Dynamic style injection

Above example imports style classes statically. Of course, it can be injected dynamically something like this:

```javascript 
class HelloController extends Craft.UI.View {
	viewDidLoad(callback){
		this.appendSubView(new Hello({
			styles: [HeaderStyling,ParagraphStyling]
		}))
	}
}

class Hello extends Craft.UI.View {
	constructor(options){
		super(options);
		this.injectedStyles = options.styles;
	}
	style(componentId){
		return this.injectedStyles.map( css => css.css() ).join('') + `
			p { color: blue; }
		`;
	}
	template(componentId){
		return `
			<div id="root" class="root">
				<h1>Hello</h1>
				<p class="text">World!</p>
			</div>
		`;
	}
}
``` 

## Load external CSS

`adoptedStyleSheets` is no yet supported by Safari. To load external CSS file from remote, at first you have to append it to header, and then append it to this.shadow. 

```javascript 
var link = document.createElement('link');
link.href = 'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.12.1/css/all.min.css';
link.rel = 'stylesheet';
document.head.appendChild(link)
``` 

This is for console test. (You may preload in your `index.html`)

Then, define view that is appending CSS to its this.shadow in constructor.

```javascript 
class Hello extends Craft.UI.View {
	constructor(options){
		super();
		
		const link = document.createElement('link');
		link.href = 'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.12.1/css/all.min.css';
		link.rel = 'stylesheet';
		this.shadow.appendChild(link);
	}
	template(componentId){
		return `
			<div id="root" class="root">
				<h1>Hello</h1>
				<i class="fas fa-globe"></i>
			</div>
		`;
	}
}
``` 

## NOTE

Above examples are runnable on playground.

```javascript 
var view = new Hello();
view.loadView();
Craft.Core.Context.getRootViewController().appendSubView(view);
``` 

🛺 Try CraftKit Playground:
https://github.com/craftkit/craftkit-playground