Last active
November 10, 2023 09:37
-
-
Save o0101/274d1f677c688160f9427947324ec746 to your computer and use it in GitHub Desktop.
Simple Custom Element Base Class With Neat Implicit Templating
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
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); | |
} | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<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