Skip to content

Instantly share code, notes, and snippets.

@michaelcpuckett
Last active July 7, 2019 14:55
Show Gist options
  • Save michaelcpuckett/3dfe768d11d729267cc0754157287a09 to your computer and use it in GitHub Desktop.
Save michaelcpuckett/3dfe768d11d729267cc0754157287a09 to your computer and use it in GitHub Desktop.
<!--
<h2>WC Wish List</h2>
<ul>
<li><input type=checkbox> dynamic attributes</li>
<li><input type=checkbox checked> if/else conditionals</li>
</ul>
-->
<template id="tpl-href-link">
<style>
a {
color: red;
}
</style>
<a href="{{href}}">
<slot></slot>
</a>
</template>
<template id="tpl-show-once-loaded">
<slot></slot>
</template>
<template id="tpl-show-once-loading">
<slot name="is-loading">
<p>Loading...</p>
</slot>
</template>
<template id="tpl-news-preview">
<article>
<show-once>
<slot slot="show-once" name="headline"></slot>
<h2><slot name="headline"></slot></h2>
<show-once>
<slot slot="show-once" name="author.name"></slot>
<div slot="is-loading"></div>
<p>
By <href-link>
<slot name="author.@id" slot="href"></slot>
<slot name="author.name"></slot>
</href-link>
</p>
</show-once>
<show-once>
<slot slot="show-once" name="dateModified"></slot>
<div slot="is-loading"></div>
Last modified <slot name="dateModified"></slot>
</show-once>
<show-once>
<slot slot="show-once" name="description"></slot>
<wrap-with>
<p slot="wrap-with"></p>
<slot name="description"></slot>
</wrap-with>
</show-once>
</show-once>
</article>
</template>
<template id="tpl-event-preview">
<style>
article * {
all: unset;
display: flex;
flex-basis: auto;
flex-shrink: 0;
flex-grow: 0;
line-height: var(--line-height);
--line-height: 28px;
}
h2 {
font-size: 18px;
}
slot {
padding-right: .25em;
}
</style>
<fetch-data>
<slot slot="by-id" name="agent.@id"></slot>
<slot slot="by-id" name="object.@id"></slot>
</fetch-data>
<show-once>
<slot slot="show-once" name="agent.name"></slot>
<show-once>
<slot slot="show-once" name="object.name"></slot>
<article>
<h2>
<href-link>
<slot slot="href" name="agent.@id"></slot>
<slot name="agent.name"></slot>
</href-link>
<slot name="@type"></slot>
<href-link>
<slot slot="href" name="object.@id"></slot>
<slot name="object.name"></slot>
</href-link>
</h2>
<show-once>
<slot slot="show-once" name="body"></slot>
<wrap-with>
<p slot="wrap-with"></p>
<slot name="body"></slot>
</wrap-with>
</show-once>
</article>
</show-once>
</show-once>
</template>
<script>
(() => {
// utility components
window.customElements.define('wrap-with', class WrapWith extends window.HTMLElement {
constructor() {
super()
this.attachShadow({
mode: 'open'
})
const wrapWithSlotNode = [...this.children].find(node => node.matches('[slot="wrap-with"]'))
if (wrapWithSlotNode) {
const contentSlot = [...this.children].find(node => !node.matches('[slot="wrap-with"]'))
contentSlot.addEventListener('slotchange', () => {
this.slotChangedCallback()
})
}
}
slotChangedCallback() {
;[...this.shadowRoot.children].forEach(node => node.remove())
const wrapWithSlotNode = [...this.children].find(node => node.matches('[slot="wrap-with"]'))
const contentSlot = [...this.children].find(node => !node.matches('[slot="wrap-with"]'))
contentSlot.assignedNodes().forEach((contentSlot) => {
const clone = contentSlot.cloneNode(true)
const wrapper = wrapWithSlotNode.cloneNode(true)
clone.removeAttribute('slot')
wrapper.removeAttribute('slot')
wrapper.appendChild(clone)
this.shadowRoot.appendChild(wrapper)
})
}
})
window.customElements.define('fetch-data', class FetchData extends window.HTMLElement {
constructor() {
super()
this.attachShadow({
mode: 'open'
})
const slots = [...this.querySelectorAll('[slot="by-id"]')]
slots.forEach(async slot => {
// fetch(slot.assignedNodes()[0].innerText.trim()).then(...)
await new Promise(resolve => setTimeout(resolve))
this.parentNode.host.appendChild(window.document.createTextNode(JSON.stringify({
"agent": {
"name": "Justin Amash"
},
"object": {
"name": "Republican Party"
}
})))
slot.remove()
})
}
})
window.customElements.define('show-once', class ShowOnce extends window.HTMLElement {
constructor() {
super()
this.attachShadow({
mode: 'open'
})
this.shadowRoot.appendChild(window.document.getElementById('tpl-show-once-loading').content.cloneNode(true))
;[...this.children].find(node => node.matches('[slot="show-once"]')).addEventListener('slotchange', () => {
this.slotChangedCallback()
})
}
slotChangedCallback() {
const showIfSlot = [...this.children].find(node => node.matches('[slot~="show-once"]'))
const showIfSlotText = !showIfSlot || showIfSlot.assignedNodes && showIfSlot.assignedNodes()[0] && showIfSlot.assignedNodes()[0].innerText.trim()
;[...this.shadowRoot.children].forEach(node => node.remove())
if (showIfSlotText) {
this.shadowRoot.appendChild(window.document.getElementById('tpl-show-once-loaded').content.cloneNode(true))
if (showIfSlot) {
showIfSlot.remove()
}
} else {
this.shadowRoot.appendChild(window.document.getElementById('tpl-show-once-loading').content.cloneNode(true))
}
}
})
window.customElements.define('href-link', class HrefLink extends window.HTMLElement {
constructor() {
super()
this.attachShadow({
mode: 'open'
})
const hrefSlot = this.querySelector('[slot="href"]')
hrefSlot.addEventListener('slotchange', () => {
this.slotChangedCallback()
})
}
slotChangedCallback() {
const hrefSlot = this.querySelector('[slot="href"]')
const href = hrefSlot.innerText.trim() || (hrefSlot.assignedNodes && hrefSlot.assignedNodes()[0] && hrefSlot.assignedNodes()[0].innerText.trim())
if (href) {
hrefSlot.assignedNodes()[0].remove()
this.shadowRoot.innerHTML = [...window.document.getElementById('tpl-href-link').content.cloneNode(true).children].map(node => node.outerHTML).join(' ').replace(/{{href}}/, href)
}
}
})
// ui components
const BaseCustomElement = class BaseCustomElement extends window.HTMLElement {
constructor() {
super()
this.attachShadow({
mode: 'open'
})
this.shadowRoot.appendChild(window.document.getElementById(this.constructor.templateId).content.cloneNode(true))
const hiddenDataNode = window.document.createElement('data')
const hiddenDataSlot = window.document.createElement('slot')
hiddenDataNode.setAttribute('hidden', 'hidden')
hiddenDataNode.appendChild(hiddenDataSlot)
this.shadowRoot.appendChild(hiddenDataNode)
const defaultSlot = this.shadowRoot.querySelector('slot:not([name])')
if (defaultSlot) {
defaultSlot.addEventListener('slotchange', () => {
this.slotChangedCallback()
})
}
}
slotChangedCallback() {
const slotEl = this.shadowRoot.querySelector('slot:not([name])')
const value = slotEl.assignedNodes().map(node => node.textContent.trim()).join('')
if (value) {
Object.entries(JSON.parse(value)).forEach(this.handleSlotCreation.bind(this))
slotEl.assignedNodes().forEach(node => node.remove())
}
}
handleSlotCreation([key, value]) {
if (typeof value === 'string') {
const slotEl = this.querySelector(`[slot="${key}"]`) || window.document.createElement('data')
slotEl.setAttribute('slot', key)
slotEl.innerText = value
this.appendChild(slotEl)
this.shadowRoot.firstElementChild.setAttribute(`data-${key}`.replace(/@/g, '').replace(/([A-Z])/g, '-$1').toLowerCase(), value)
} else if (Array.isArray(value)) {
;[...this.querySelectorAll(`[slot="${key}"]`)].forEach(node => node.remove())
const slotEls = value.forEach(v => {
const slotEl = window.document.createElement('data')
slotEl.setAttribute('slot', key)
slotEl.innerText = v
this.appendChild(slotEl)
})
} else if (value) {
Object.entries(value).forEach(([innerKey, innerValue]) => {
const slot = innerKey === '@value' ? key : `${key}.${innerKey}`
if (typeof innerValue === 'string') {
const slotEl = this.querySelector(`[slot="${slot}"]`) || window.document.createElement('data')
slotEl.setAttribute('slot', `${slot}`)
slotEl.innerText = innerValue
this.appendChild(slotEl)
} else {
this.handleSlotCreation([slot, innerValue])
}
})
}
}
}
window.customElements.define('event-preview', class EventPreview extends BaseCustomElement {
constructor() {
super()
}
static get templateId() {
return 'tpl-event-preview'
}
})
window.customElements.define('news-preview', class NewsPreview extends BaseCustomElement {
constructor() {
super()
}
static get templateId() {
return 'tpl-news-preview'
}
})
})()
;(async () => {
await new Promise(resolve => setTimeout(resolve, 500))
window.document.querySelector('#newsPreview').appendChild(window.document.createTextNode(JSON.stringify({
"headline": "Justin Amash leaves Republican Party",
"author": {
"@type": "Person",
"@id": "Person/Michael-Puckett"
},
"description": [
`Justin Amash announced Thursday he was leaving the Republican Party to become an independent. He said he decided to exit the
GOP "in this current Congress."`,
`Amash in a July 4 Washington Post op-ed Amash wrote that he was departing the GOP after becoming "disenchanted
with party politics and frightened by what I see from it."`
]
})))
await new Promise(resolve => setTimeout(resolve, 500))
window.document.querySelector('#newsPreview').appendChild(window.document.createTextNode(JSON.stringify({
"dateModified": {
"@type": "DateTime",
"@value": "2019-07-04T11:30:00-07:00"
},
"author": {
"name": "Michael Puckett"
},
"about": {
"agent": {
"name": "Justin Amash"
},
"object": {
"name": "Republican Party"
}
}
})))
})()
</script>
<news-preview id="newsPreview">
{
"@type": "NewsArticle",
"@id": "NewsArticle/234567890123",
"about": {
"@type": "LeaveAction",
"@id": "LeaveAction/234567890123",
"agent": {
"@id": "Person/Justin-Amash"
},
"object": {
"@id": "Organization/Republican-Party"
}
}
}
</news-preview>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment