Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save patrickml/d41482238f354b26e63a8e0359f85465 to your computer and use it in GitHub Desktop.
Save patrickml/d41482238f354b26e63a8e0359f85465 to your computer and use it in GitHub Desktop.
Reactive Web Component Demo Library 200bytes (gz) -- https://jsfiddle.net/patrickml/6h9uz4g3/2/
const ProxyStore = (values={}, options={}) => new Proxy(
{
__registry: {},
subscribe: function(key, fn) {
this.__registry[key] = [...(this.__registry[key] || []), fn];
},
unsubscribe: function(key, fn) {
this.__registry[key] = this.__registry[key].filter(fn);
},
...values,
},
{
get: function(target, name, receiver) {
return Reflect.get(target, name, receiver);
},
set: function(target, name, value, receiver) {
Reflect.set(target, name, value, receiver);
target.__registry[name].forEach(fn => fn());
return true;
},
...options,
}
);
const store = ProxyStore({ users: [] });
const NodeOf = (type) => (
class Node extends type {
constructor() { super(); }
componentDidRender() {}
componentDidMount() {}
componentDidUnmount() {}
connectedCallback() {
this.componentDidMount();
this.renderNode();
}
disconnectedCallback() {
this.componentDidUnmount();
}
renderNode() {
let html = this.render();
if (Array.isArray(html)) {
html = html.join('');
}
if (this._shadow) this._shadow.innerHTML = html;
else this.innerHTML = html;
this.componentDidRender();
}
}
)
const Node = NodeOf(HTMLElement);
class ProxyNode extends Node {
set data(data) {
this._data = data;
this.renderNode();
}
get data() {
return this._data;
}
}
class ProxyProvider extends HTMLElement {
get subscribe() { return this.hasAttribute('subscribe'); }
get storeKey() { return this.getAttribute('store'); }
get data() { return store[this.storeKey]; }
set data(value) { return store[this.storeKey] = value; }
_subscribe() {
store.subscribe(this.storeKey, this.propagateChanges.bind(this));
}
_unsubscribe() {
store.unsubscribe(this.storeKey, this.propagateChanges.bind(this));
}
connectedCallback() {
if(this.subscribe) this._subscribe();
this.propagateChanges();
}
disconnectedCallback() {
if(this.subscribe) this._unsubscribe();
}
append(item) {
this.data = [...this.data, item]
}
merge(item) {
this.data = {...this.data, item}
}
set(item) {
this.data = item
}
propagateChanges() {
for (var child of this.children) {
child.data = this.data;
child[this.storeKey] = {
append: this.append.bind(this),
merge: this.merge.bind(this),
set: this.set.bind(this),
}
}
}
}
class ListItem extends Node {
get text() { return this.getAttribute('text'); }
render() {
return `<li>${this.text}</li>`;
}
}
class List extends ProxyNode {
render() {
if (!this.data) {
return '';
}
return this.data.map((user) => (
`<list-item text="${user.name} is age ${user.age}"></list-item>`
));
}
}
class AddUserButton extends NodeOf(HTMLButtonElement) {
createUser() {
this.users.append({
name: `Random User`,
age: Math.round(Math.random() * 100),
});
}
componentDidMount() {
this.addEventListener('click', this.createUser.bind(this));
}
render() {
return `Add User`;
}
}
customElements.define('list-item', ListItem);
customElements.define('add-user-button', AddUserButton, { extends: 'button' });
customElements.define('user-list', List);
customElements.define('proxy-provider', ProxyProvider);
<!DOCTYPE html>
<html>
<body>
<h1>Testing</h1>
<proxy-provider store="users">
<button is="add-user-button"></button>
</proxy-provider>
<proxy-provider store="users" subscribe>
<user-list></user-list>
</proxy-provider>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment