Skip to content

Instantly share code, notes, and snippets.

@o0101
Last active November 10, 2023 09:37
Show Gist options
  • Save o0101/274d1f677c688160f9427947324ec746 to your computer and use it in GitHub Desktop.
Save o0101/274d1f677c688160f9427947324ec746 to your computer and use it in GitHub Desktop.
Simple Custom Element Base Class With Neat Implicit Templating
{
const $ = Symbol(`[[state]]`);
class Base extends HTMLElement {
static get observedAttributes() {
return ['state'];
}
constructor(state) {
super(state);
this.shadow = this.attachShadow({ mode: 'open' });
if ( state ) {
this.state = state;
} else if ( this.hasAttribute('state') ) {
try {
this.state = JSON.parse(this.getAttribute('state'));
} catch(e) {
this.state = this.getAttribute('state');
}
} else {
this[$] = Object.create(null);
}
}
connectedCallback() {
this.render();
}
attributeChangedCallback(name, oldValue, newValue) {
if (name === 'state') {
let val;
try {
val = JSON.parse(newValue);
} catch (e) {
val = newValue;
}
this.state = val;
}
}
set state(newState) {
this[$] = newState;
this.render();
}
get state() {
return this[$];
}
// override
template() {
return `<!-- ${__} element -->`;
}
render() {
this.shadow.innerHTML = this.preprocessTemplate(this.getTemplate());
}
preprocessTemplate(templateString) {
// Improved regex to account for optional quotes, whitespace, and decoupling event names from handler names
const handlerRegex = /\s(on\w+)=['"]?(?!this\.getRootNode\(\)\.host\.)([^\s('";>/]+)(?:\([^)]*\))?;?['"]?/g;
return templateString.replace(handlerRegex, (match, event, handlerName) => {
// Check if the handler name is a function on the current element
if (typeof this[handlerName] === 'function') {
// Construct the replacement with a bound method call and proper quotes
const quote = "'";
return ` ${event}=${quote}this.getRootNode().host.${handlerName}(event)${quote}`;
} else {
// If the function doesn't exist, throw an error or handle accordingly
console.error(`Handler function '${handlerName}' not found in element`);
return match; // or throw new Error(...) if you prefer
}
});
}
}
Base.prototype.getTemplate = function() {
this.state['__'] = this.constructor.name;
with(this.state) {
return eval(`(function ${this.template.toString()}())`);
}
}
Object.defineProperty(globalThis, 'Base', {
get() {
return Base;
}
});
globalThis.customElements.define('base-el', Base);
}
<script src=base.js></script>
<script>
class Cool extends Base {
showAlert(event) {
console.log('event', event);
alert('got an event');
}
showPos(mouseMove) {
const {clientX,clientY} = mouseMove;
console.log({clientX,clientY});
}
template() {
return `
<progress value=${myProgress} max=25 onclick=showAlert(event);></progress>
<p onpointermove=showPos>${myWords}</p>
`;
}
}
</script>
<script>
// example
{
customElements.define('cool-el', Cool);
m = document.createElement('cool-el');
m.state = {
myWords: 'hi i am word',
myProgress: 11
}
document.addEventListener('DOMContentLoaded', () => {
document.body.appendChild(m);
});
}
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment