Created
April 14, 2023 16:33
-
-
Save evilbuck/4d3ddcf1a04e10511e4c5f840f07800f to your computer and use it in GitHub Desktop.
Web Component
This file contains hidden or 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
| import _ from 'lodash'; | |
| export default class BaseWebComponent extends HTMLElement { | |
| store = null; | |
| events = []; | |
| _previouslyRendered = false; | |
| constructor({ controller, model, store, template }) { | |
| super(); | |
| this.store = store; | |
| this.controller = controller; | |
| this.model = model; | |
| // TODO: enable this to allow passing in a string or a dom element | |
| // if (typeof template === string) { | |
| // this.template = document.querySelector(template); | |
| // } | |
| this.template = template; | |
| const instance = this.template.content.cloneNode(true); | |
| this.instance = instance; | |
| const shadowRoot = this.attachShadow({ mode: 'open' }); | |
| shadowRoot.appendChild(instance); | |
| } | |
| // called when the component is attached to the dom | |
| connectedCallback() { | |
| this._connectEvents(); | |
| } | |
| // called when the component is removed from the dom | |
| disconnectedCallback() { | |
| this._cleanupEvents(); | |
| } | |
| _connectEvents() { | |
| // get all the elements with a data-oh_onclick attribute | |
| const onClickEls = this.shadowRoot.querySelectorAll('[data-oh_onclick]'); | |
| onClickEls.forEach((el) => { | |
| const [directive, argsString] = el.dataset.oh_onclick.split(':').map((s) => s.trim()); | |
| const args = argsString ? argsString.split(',').map((s) => s.trim()) : []; | |
| const func = this.methods[directive].bind(this, ...args); | |
| // we're saving the event listener so we can remove it during cleanup | |
| this.events.push({ el, func, directive, type: 'click' }); | |
| el.addEventListener('click', func); | |
| }); | |
| } | |
| _cleanupEvents() { | |
| this.events.forEach(({ el, func, type }) => { | |
| el.removeEventListener(type, func); | |
| }); | |
| // clear the events since we removed them | |
| this.events = []; | |
| } | |
| // manually called, generally by setting up a reaction to the store | |
| // sole purpose is to update the shadowRoot | |
| render() { | |
| this._cleanupEvents(); | |
| // get all the elements with a data-oh_bind attribute for binding the textNode to the dom element | |
| this.shadowRoot.querySelectorAll('[data-oh_bind]').forEach((element) => { | |
| // parse the data-oh_bind attribute to get the property name | |
| const dataBind = element.dataset.oh_bind; | |
| const [directive, property] = dataBind.split(':').map((s) => s.trim()); | |
| switch (directive) { | |
| case 'text': | |
| element.textContent = _.get(this, property) ?? _.get(this.store, property, ''); | |
| console.log('text', element.textContent, this.model, property); | |
| break; | |
| default: | |
| if (process.env.NODE_ENV === 'development') { | |
| console.warn(`Unknown directive ${directive} in data-oh_bind`); | |
| } | |
| break; | |
| } | |
| }); | |
| this._connectEvents(); | |
| } | |
| _get(object, path, defaultValue) { | |
| return _.get(object, path, defaultValue); | |
| } | |
| } | |
| // Usage | |
| class Button extends BaseWebComponent { | |
| buttonText = 'change me'; | |
| constructor(...args) { | |
| super(...args); | |
| makeObservable(this, { | |
| buttonText: observable | |
| }); | |
| // watch for changes to buttonText | |
| reaction(() => this.buttonText, (value, oldValue) => { | |
| if (oldValue !== value) { | |
| this.render(); | |
| } | |
| }); | |
| } | |
| methods: { | |
| handleClick: () => { | |
| console.log('clicked'); | |
| runInAction(() => { | |
| this.buttonText = "I changed"; | |
| }); | |
| } | |
| } | |
| } | |
| // html | |
| <!DOCTYPE> | |
| <html> | |
| <body> | |
| <template id="button-template"> | |
| <button data-oh_bind="text: buttonText" data-oh_onclick="handleClick">change me</button> | |
| </template> | |
| </body> | |
| </html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment