Twitter: @matthewcp
GitHub: matthewp
The best web component library.
Matthew Phillips
the condition of matter with respect to structure, form, constitution,phase, or the like
Todos
/todos
State
{
"page": "todos",
"todos": [
"walk the dog",
"go to the groceries"
]
}
Naked Objects
ViewModel
firstName
lastName
fullName
Other
ViewModels
View
Actions
State
Reducers
events
Parent
Child
Properties & Attributes
Events
shared vs. project
<my-tabs>
<my-panel>First</my-panel>
<my-panel>Second</my-panel>
</my-tabs>
<shopping-cart></shopping-cart>
var img = new Image();
img.addEventListener('load', _ => {
console.log('Image loaded');
});
img.addEventListener('error', _ => {
console.error('Failed to load image');
});
// Setting the property triggers a fetch
img.src = 'https://bramjs.org/images/bram.svg';
class MyImage extends HTMLElement {
get src() {
return this._src;
}
set src(val) {
this._src = val;
this._fetch();
}
_fetch() {
fetch(this.src)
.then(_ => new Event('load'))
.catch(_ => new Event('error'))
.then(ev => {
this.dispatchEvent(ev);
});
}
}
<progress value="0.5"></progress>
HTML
let progress = document.querySelector('progress');
progress.value = '0.3';
JavaScript
<progress value="20" max="100"></progress>
HTML
let progress = document.querySelector('progress');
progress.max = 30;
JavaScript
class MyProgress extends HTMLElement {
static get observedAttributes() {
return ['value', 'max'];
}
constructor() {
super();
this._value = this.getAttribute('value');
this._max = this.getAttribute('max');
}
get value() {
return this._value;
}
set value(val) {
this._value = val;
this.setAttribute('value', val);
}
get max() {
return this._max;
}
set max(val) {
this._max = val;
}
}
customElements.define('my-progress', MyProgress);
Supporting Attribute/Property pairs
static get observedAttributes() {
return ['value', 'max'];
}
progress.setAttribute('value', '0.4');
attributeChangedCallback(name, oldVal, newVal) {
// what do?
}
attributeChangedCallback(name, oldVal, newVal) {
this[name] = newVal;
}
set max(val) {
// ok now what?
}
set max(val) {
this._max = val;
this.setAttribute('max', val);
}
constructor() {
super();
this._max = this.getAttribute('max');
}
<progress value="0.8"></progress>
HTML
JavaScript
let progress = document.querySelector('progress');
progress.max = 2;
<progress value="0.8" max="2"></progress>
HTML
When to use attributes, properties, and events
<my-accordion orientation="landscape">
<my-panel>Panel one</my-panel>
<my-panel open>Panel two</my-panel>
<my-panel>Panel three</my-panel>
</my-accordion>
<input type="text" value="Hello world" disabled>
HTML
input[disabled] {
background-color: WhiteSmoke;
}
CSS
let userForm = document.querySelector('user-form');
userForm.user = {
name: 'Matthew',
gh: 'matthewp'
};
<label for="user">Username:</label>
<input type="text" name="user" value="Matthew">
let input = document.querySelector('[name=user]');
input.value = 'Wilbur';
HTML
JavaScript
<audio controls src="http://ia801400.us.archive.org/33/items/frankenstein_shelley/frankenstein_00_shelley_64kb.mp3">
</audio>
let audio = document.querySelector('audio');
audio.play();
HTML
JavaScript
<time datetime="2017-04-29T19:00"></time>
HTML
var time = document.querySelector('time');
time.date.getMonth();
JavaScript
class MyTime extends HTMLElement {
set datetime(val) {
this.date = new Date(val);
this.setAttribute('datetime', val);
}
}
customElements.define('my-time', MyTime);
class MyModal extends HTMLElement {
static get observedAttributes() {
return ['open'];
}
constructor() {
super();
this._open = this.hasAttribute('open');
}
get open() {
return this._open;
}
set open(val) {
val = Boolean(val);
if(this._open !== val) {
this._open = val;
if(val) {
this.setAttribute('open', '');
} else {
this.removeAttribute('open');
}
}
}
attributeChangedCallback(name, oldVal, newVal) {
this.open = newVal === "";
}
}
Bidirectional Attribute + Property
let modal = document.querySelector('modal-window');
modal.addEventListener('close', e => {
// Do something when this closes
});
class EveryFiveSeconds extends HTMLElement {
connectedCallback() {
this._id = setInterval(_ => {
this.dispatchEvent(
new CustomEvent('tick', {
detail: new Date()
})
);
}, 5000);
}
}
customElements.define('every-five-seconds', EveryFiveSeconds);
let el = document.querySelector('every-five-seconds');
el.addEventListener('tick', e => console.log('Time:', e.detail));
class UserForm extends HTMLElement {
constructor() {
super();
let root = this.attachShadow({ mode: 'open' });
renderTemplate(root);
let form = root.querySelector('form');
form.addEventListener('submit', e => {
this.dispatchEvent(
new CustomEvent('user-change', {
detail: this.user
})
);
});
}
}
customElements.define('user-form', UserForm);
let modal = document.querySelector('modal-window');
modal.onclose = () => alert('Modal closed');
let el = document.querySelector('a');
el.onclick = e => {
e.preventDefault();
// do stuff
};
Built-in elements
Custom elements
<div id="parent">
<input type="text" name="user" value="">
</div>
<script>
parent.onchange = function(){
console.log("Something changed.");
}
</script>
class ModalWindow extends HTMLElement {
get onclose() {
return this._onclose;
}
set onclose(handler) {
if(this._onclose) {
this.removeEventListener('close', this._onclose);
}
this._onclose = handler;
this.addEventListener('close', this._onclose);
}
connectedCallback() {
if(this._onclose)
this.addEventListener('close', this._onclose);
}
disconnectedCallback() {
if(this._onclose)
this.removeEventListener('close', this._onclose);
}
}
customElements.define('modal-window', ModalWindow);
Verbose to implement 😵
Libraries can help
Do not work on other elements
No bubbling
Convenient
Interoperability (kind of? maybe?)
Many awesome state patterns
Be a good web component citizen
Properties and attributes go down
Attributes are a subset of properties
Events come back up
The more events the merrier
onevent for convenience
maybe not worth the hassle