Skip to content

Instantly share code, notes, and snippets.

@EndangeredMassa
Last active June 22, 2017 19:09
Show Gist options
  • Save EndangeredMassa/6af3c6b745a32d6932bcfe53cfb0e5bf to your computer and use it in GitHub Desktop.
Save EndangeredMassa/6af3c6b745a32d6932bcfe53cfb0e5bf to your computer and use it in GitHub Desktop.
enhanced glimmer components, with some surface-level workarounds
// NOTE: modify this file from this commit on this PR:
// https://github.com/glimmerjs/glimmer-web-component/pull/18/commits/8bc4b27f2b78aa2cf6ce0f246dbbb55e88718007
import Application from '@glimmer/application';
function glimmerElementFactory(app: Application, componentName: string) {
function GlimmerElement() {
return Reflect.construct(HTMLElement, [], GlimmerElement);
}
GlimmerElement.prototype = Object.create(HTMLElement.prototype, {
constructor: { value: GlimmerElement },
connectedCallback: {
value: function connectedCallback(): void {
let shadowRoot = this.attachShadow({ mode: 'open' });
app.renderComponent(componentName, shadowRoot);
}
},
attributeChangedCallback: {
value: function attributeChangedCallback(attr, oldValue, newValue): void {
if (this.onAttributeChangedCallback) {
this.onAttributeChangedCallback(attr, newValue);
}
}
}
});
Object.defineProperty(GlimmerElement, 'observedAttributes', {
get: function getObservedAttributes() {
// NOTE: we'll want to pass these in to glimmerElementFactory
// from initializeCustomElements so that the user app code
// can specify what goes here; or, inpect the user-defined
// component for tracked attributes somehow
return ['show', 'other'];
}
});
return GlimmerElement;
}
export default glimmerElementFactory;
// NOTE: In a consumer glimmer app, create a component like the following.
import Component, { tracked } from '@glimmer/component';
/*
Assigns the top-level dom element's attributes to the
glimmer component instance as `this.htmlAttrs`.
*/
function setAttributes(glimmerComponent, webComponent) {
const attrs = Array.prototype.slice.call(webComponent.attributes);
glimmerComponent.htmlAttrs = attrs.reduce((newAttrs, node) => {
newAttrs[node.name] = node.nodeValue;
return newAttrs;
}, {});
}
// NOTE: This should go into the core glimmer-component
class AttrComponent extends Component {
@tracked
htmlAttrs: any;
didInsertElement() {
const webComponent = this.element.parentNode.host;
// glimmer's web component's attribute changed callback
// will invoke this for us, if it exists
webComponent.onAttributeChangedCallback = (attr, newValue) => {
const attrs = Array.prototype.slice.call(webComponent.attributes);
setAttributes(this, webComponent);
};
setAttributes(this, webComponent);
}
dispatchEvent(name, args) {
const webComponent = this.element.parentNode.host;
const event = new CustomEvent(name, { detail: args });
webComponent.dispatchEvent(event);
}
doWork() {
this.dispatchEvent('work', 4);
}
}
export default class MyControl extends AttrComponent {
@tracked('htmlAttrs')
get show() {
if (this.htmlAttrs) {
return parseInt(this.htmlAttrs.show, 10);
}
return -1;
}
};
<div>
Hello from my-control! Showing {{this.show}}!
<slot></slot>
<button onclick={{action doWork}}>Do Work</button>
</div>
<!DOCTYPE html>
<html>
<body>
<my-control show="1" other="thing">
<p>some block content</p>
</my-control>
<script src="app.js"></script>
</body>
</html>
<!-- Web Component Definition Code-->
<template id="hello-element-template">
Hi {{name}}!
<button id="action">Do It!</button>
</template>
<script>
class HelloElement extends HTMLElement {
constructor() {
super();
// properties
this.name = null;
this.clickCount = 0;
// setup web component's shadow root
this.attachShadow({mode: 'open'});
this.template = document.querySelector('#hello-element-template');
// initial template render
this.render();
}
// Monitor the 'name' attribute for changes.
static get observedAttributes() { return ['name']; }
// Respond to attribute changes.
attributeChangedCallback(attr, oldValue, newValue) {
if (attr === "name") {
this.name = newValue;
}
this.render();
}
render(){
this._removeChildren();
this._setupTemplate();
this._interpolateAttributes();
this._setupEventHandlers();
}
_removeChildren() {
while (this.shadowRoot.hasChildNodes()) {
this.shadowRoot.removeChild(this.shadowRoot.lastChild);
}
}
_setupTemplate() {
const instance = this.template.content.cloneNode(true);
this.shadowRoot.appendChild(instance);
}
_interpolateAttributes() {
this.shadowRoot.innerHTML = this.shadowRoot.innerHTML.replace('{{name}}', this.name);
}
_setupEventHandlers() {
this.shadowRoot.querySelector('#action').addEventListener("click", (clickEvent) => {
this.clickCount += 1;
const event = new CustomEvent('do-something', { detail: this.clickCount });
this.dispatchEvent(event);
}, false);
}
}
// Define the new element
customElements.define('hello-element', HelloElement);
</script>
<!-- Consumer Code -->
<hello-element name="Sean"></hello-element>
<hr>
Name: <input type="text" id="new-name"></input>
<button id="change-name">Change</button>
<br>
Click Count: <span id="output">0</span>
<script>
const helloElement = document.querySelector('hello-element');
helloElement.addEventListener('do-something', function(event) {
document.querySelector('#output').textContent = event.detail;
});
const nameButton = document.querySelector('#change-name');
nameButton.addEventListener('click', function(event){
const newName = document.querySelector('#new-name').value;
document.querySelector('hello-element').setAttribute('name', newName);
});
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment