Last active
July 7, 2019 14:55
-
-
Save michaelcpuckett/3dfe768d11d729267cc0754157287a09 to your computer and use it in GitHub Desktop.
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
<!-- | |
<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