Skip to content

Instantly share code, notes, and snippets.

@brunovcosta
Last active November 30, 2021 00:23
Show Gist options
  • Save brunovcosta/0007a2c82ee0c7e16bb5918a213532de to your computer and use it in GitHub Desktop.
Save brunovcosta/0007a2c82ee0c7e16bb5918a213532de to your computer and use it in GitHub Desktop.
Abstra Custom Element
const eventListeners = [];
function define({
name,
initialState,
template,
listeners
}) {
customElements.define(name,
class extends HTMLElement {
$state = initialState();
$refresh(partialSelector) {
if (partialSelector) {
const clone = this.cloneNode();
clone.shadowRoot.innerHTML = template(this.$state);
const newPartials = Array.from(clone.shadowRoot.querySelectorAll(partialSelector));
const oldPartials = Array.from(this.shadowRoot.querySelectorAll(partialSelector));
if (newPartials.length !== oldPartials.length) throw new Error(`Can't partially refresh when selection count changes ${newPartials.length} !== ${oldPartials.length}`);
oldPartials.forEach((item,index) => item.outerHTML = newPartials[index].outerHTML);
} else {
this.shadowRoot.innerHTML = template(this.$state);
}
Object.keys(listeners ?? {}).forEach(key => {
const [selector, eventName] = key.split(" ");
const elements = Array.from(this.shadowRoot.querySelectorAll(selector));
const listener = event => listeners[key](event, this);
elements.forEach(element => eventListeners.forEach(l => element.removeEventListener(eventName, l)));
elements.forEach(element => element.addEventListener(eventName, listener));
eventListeners.push(listener);
});
}
constructor() {
super();
this.attachShadow({mode: 'open'});
this.$refresh();
}
}
);
}
const name = "todo-app"
const initialState = () => ({
items: []
})
const listeners = {
".add click": (evt, instance) => {
const value = instance.shadowRoot.querySelector("#new-item").value;
instance.$state.items.push(value)
instance.$refresh();
},
".remove click": (evt, instance) => {
const index = evt.target.dataset.index;
instance.$state.items.splice(index, 1);
instance.$refresh(".items, .counter");
}
}
const template = state => `
<div class="title" style="box-shadow: 0 10px 10px rgba(0,0,0,0.2);padding: 50px">
<h1>Todo list</h1>
<div class="items" style="display: flex; align-items: stretch;flex-direction: column">
${state.items.map((item,index) =>
`<div class="item" style="display: flex;">
<label style="flex-grow: 1">${item}</label>
<button class="remove" data-index="${index}">&times;</button>
</div>`
).join("")}
</div>
<div class="new-item">
<input id="new-item"/><button class="add">add</button>
</div>
<small class="counter">${state.items.length} items</small>
</div>`
define({
name,
initialState,
template,
listeners
});
{
"key": "todo-app",
"thumbnail": "https://place-hold.it/300",
"label": "Example button",
"description": "Lorem ipsum",
"component": "todo-app.js",
"parametersDefinition": {
"parameters": []
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment