@kevinbalicot/custom-element
v1.3.0
Published
Tiny library to make custom elements
Downloads
2
Readme
Custom Element
Tiny library to make custom HTML elements
Live demo https://jsfiddle.net/kevinbalicot/L08q4k21/
Installation
$ npm install --save @kevinbalicot/custom-element
Get started
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>My site</title>
<script src="/node_modules/@kevinbalicot/custom-element/dist/custom-element.min.js" charset="utf-8"></script>
</head>
<body>
<my-component></my-component>
<script type="application/javascript">
class MyComponent extends CustomElement {
constructor() {
super();
this.name = 'Jean';
}
static get template() {
return '<h1>Hello <span [innerHTML]="this.name"></span> !</h1>';
}
static get styles() {
return [':host(span) { color: red; }'];
}
}
window.customElements.define('my-component', MyComponent);
</script>
</body>
</html>
Features
Input binding
class DemoComponent extends CustomElement {
constructor() {
super();
this.text = null;
}
/**
* Call when custom element call connectedCallback (see HTML custom element life cycle)
*/
onConnected() {
this.element('input[name=text]').on('input', event => {
this.text = event.target.value;
this.update(); // Refresh template
// You can also to use this.update({ text: event.target.value });
});
}
static get template() {
return `
<h2>Demo</h2>
<hr>
<h3>Input binding</h3>
<label id="text">Type something</label><input type="text" name="text">
<p [innerHTML]="'You tipping: ' + this.text"></p>
`;
}
}
window.customElements.define('demo-component', DemoComponent);
For binding
class DemoComponent extends CustomElement {
constructor() {
super();
this.items = [];
}
onConnected() {
this.element('form[rol=list]').on('submit', event => {
event.preventDefault();
const input = event.target.querySelector('input[name=item]');
if (input.value) {
this.items.push(input.value);
input.value = null;
this.update();
}
});
}
onDeleteItem(index) {
this.items.splice(index, 1);
this.update();
}
static get template() {
return `
<h2>Demo</h2>
<hr>
<h3>For binding</h3>
<form rol="list">
<input type="text" name="item" required>
<button type="submit">Add</button>
</form>
<ul>
<!-- use #for attribute to make a loop -->
<li #for="let i of this.items">
<span [innerHTML]="i" [class.stronger]="i.length > 5"></span>
<button (click)="this.onDeleteItem($index)">Delete</button>
</li>
</ul>
`;
}
static get styles() {
return [
'.stronger { font-weight: bold }'
];
}
}
window.customElements.define('demo-component', DemoComponent);
If binding
class DemoComponent extends CustomElement {
constructor() {
super();
this.show = true;
}
toggle(value) {
this.update({ show: value });
}
static get template() {
return `
<h2>Demo</h2>
<hr>
<h3>If binding</h3>
<button (click)="this.toggle(true)">Show</button>
<button (click)="this.toggle(false)">Hide</button>
<!-- use #if attribute to make a condition -->
<p #if="this.show">
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
</p>
`;
}
}
window.customElements.define('demo-component', DemoComponent);
Attribute binding
class ColorComponent extends CustomElement {
constructor() {
super();
this.color = '#fff';
}
/**
* Call every time observed attribute change
*/
onChanges(attributeName, oldValue, newValue) {
if ('color' === attributeName) {
// this.color is already set with new value, but you can abort and set the old value
this.update();
}
}
// Define ouputs
static get observedAttributes() {
return ['color'];
}
static get template() {
return '<span [style.background]="this.color" [innerHTML]="this.color"></span>';
}
}
class DemoComponent extends CustomElement {
constructor() {
super();
this.color = '#000';
}
onConnected() {
this.el('input[name=color]').on('change', event => {
this.update({ color: event.target.value });
});
}
static get template() {
return `
<h2>Demo</h2>
<hr>
<h3>Attribute binding</h3>
<label [style.color]="this.color">Choose your color</label>
<input [value]="this.color" type="color" name="color">
<h5>Sub component</h5>
<!-- use another custom component with outputs -->
<color-component [attr.color]="this.color"></color-component>
`;
}
}
window.customElements.define('color-component', ColorComponent);
window.customElements.define('demo-component', DemoComponent);
Container and Injectable services
class Http {
get(url) {
return fetch(url).then(result => result.text());
}
}
class MarkdownDecoder {
constructor() {
this.converter = new showdown.Converter();
}
decode(markdown) {
return this.converter.makeHtml(markdown);
}
}
// Define class is injectable in another class with injects() static method
class ReadmeLoader extends Injectable {
constructor(http, markdownDecoder) {
super();
this.http = http;
this.decoder = markdownDecoder;
}
load(url) {
return this.http.get(url).then(data => this.decoder.decode(data));
}
static get injects() {
return [Http, MarkdownDecoder]; // Another services
}
}
class ReadmeComponent extends CustomElement {
constructor() {
super();
this.url = 'https://raw.githubusercontent.com/kevinbalicot/custom-element/master/README.md';
this.readmeLoader = this.get('ReadmeLoader');
this.content = null;
}
onConnected() {
this.readmeLoader.load(this.url).then(content => {
this.update({ content });
});
}
static get template() {
return '<div [innerHTML]="this.content"></div>';
}
static get injects() {
return [Http, MarkdownDecoder, ReadmeLoader]; // Need to inject all services
}
}
Event binding
Sometime to use AddEventListener
into onConnected
can't work after the template refreshing, because target element is not the same after the refresh. So instead of AddEventListener
, please use event binding.
class MyComponent extends CustomElement {
onClick(event) {
// No need to use event.stopPropagation() with (*.stop)
// make something with button
}
onInput(event) {
// make something with input
}
onSumbit(event) {
// No need to use event.preventDefault() with (*.prevent)
// make something with form
}
onSelect(event) {
// make something with select
}
static get template() {
return `
<button (click.stop)="this.onClick($event)">Click on me <3</button>
<form (submit.prevent)="this.onSumbit($event)">
<input (input)="this.onInput($event)" type="text" name="text">
<select (change)="this.onSelect($event)">
<option value="1">One</option>
<option value="2">Two</option>
<option value="3">Three</option>
</select>
</form>
`;
}
}
Slots
You can use slot system, explanation on MDN web doc.
class ChildComponent extends CustomElement {
constructor() {
super();
this.childParam = 'child';
}
static get template() {
return `
<span [innerHTML]="this.childParam"></span>
<slot name="title">Default title</slot>
<slot>Default content</slot>
`;
}
}
window.customElements.define('child-component', ChildComponent);
class ParentComponent extends CustomElement {
static get template() {
return `
<child-component>
<h4 slot="title">Override default title of child component</h4>
<ul>
<li>Override default content of child component</li>
<li>Can get child scope <span [innerHTML]="this.childParam"></span></li>
<li>But we can always get parent scope <span [innerHTML]="$parent.parentParam"></span></li>
</ul>
</child-component>
`;
}
}
window.customElements.define('parent-component', ChildComponent);
API
class CustomElement extends HTMLElement {
constructor() {}
connectedCallback() {
// Call this.onConnected()
}
/**
* @param {string} name - Attribute name
* @param {string|number} oldValue - old attribute value
* @param {string|number} newValue - new attribute value
*/
attributeChangedCallback(name, oldValue, newValue) {
// Call this.onChanges()
}
disconnectedCallback() {
// Call this.onDisconnected()
}
/**
* @param {Object} [details={}] - this attributes to update
*/
update(details = {}) {}
/**
* @param {string} key - Get element into container
* @return {*}
*/
get(key) {}
/**
* @param {string} key - Key of element to store into container
* @param {*} value - Element to store into container
* @param {Array<*>} [parameters=[]] - Parameters to inject into element constructor
*/
set(key, value, parameters = []) {}
/**
* @param {string} key - Element key into container
* @return {boolean}
*/
has(key) {}
/**
* @param {string} event - Event name to attach at this custom element
* @param {function} callback
* @param {boolean} [options=false] - Option for AddEventListener function
*/
on(event, callback, options = false) {}
/**
* @param {string} selector - CSS selector to get HTML Element
* @param {HTMLElement|null}
*/
el(selector) {}
/**
* @param {string} selector - CSS selector to get a list of HTML Elements
* @param {Array<HTMLElement>|[]}
*/
all(selector) {}
/**
* @param {string} event - Event name to emit from this custom element
*/
emit(event) {}
/**
* @return {CustomElement|null} Return CustomElement parent is exists. $parent into template
*/
get parent() {}
/**
* @return {CustomElement} Return the top level of CustomElement tree. $root into template
*/
get root() {}
static get template() {
return '<slot></slot>';
}
static get injects() {
return [];
}
static get observedAttributes() {
return [];
}
static get styles() {
return [];
}
}
class Injectable {
static get injects() {
return [];
}
}