Skip to content

Instantly share code, notes, and snippets.

@michaelcpuckett
Created June 14, 2019 07:53
Show Gist options
  • Save michaelcpuckett/136279dbfb2b8fa6d05cd1ac93e30ebd to your computer and use it in GitHub Desktop.
Save michaelcpuckett/136279dbfb2b8fa6d05cd1ac93e30ebd to your computer and use it in GitHub Desktop.
<script>
class XAdInner extends HTMLElement {
constructor() {
super()
this
.attachShadow({ mode: 'open' })
.appendChild(window.document.getElementById('x-ad-inner-template').content.cloneNode(true))
}
connectedCallback() {
window.requestAnimationFrame(() => {
window.requestAnimationFrame(() => {
[...this.children].map(el => {
if (el.slot === 'potentialAction') {
const potentialAction = JSON.parse(el.innerHTML)
const linkEl = this.shadowRoot.querySelector('[href="${potentialAction.target}"]')
if (linkEl) {
linkEl.setAttribute('href', potentialAction.target)
}
}
})
})
})
}
}
class XAd extends HTMLElement {
constructor() {
super()
this.$data = new Proxy({}, {
deleteProperty: (target, prop, newVal, receiver) => {
const slottedProp = this.shadowRoot.querySelector(`[slot="${prop}"]`)
if (slottedProp) {
slottedProp.remove()
}
return Reflect.deleteProperty(target, prop, newVal, receiver)
},
set: (target, prop, newVal, receiver) => {
const slottedProp = this.shadowRoot.querySelector(`[slot="${prop}"]`)
if (slottedProp) {
slottedProp.remove()
}
const newSlottedProp = window.document.createElement('data')
newSlottedProp.setAttribute('slot', prop)
newSlottedProp.innerHTML = typeof newVal === 'string' ? ((new Date(newVal).getTime() === new Date(newVal).getTime()) ? new Date(newVal).toDateString() : newVal) : (newVal || {}).name || JSON.stringify(newVal)
this.shadowRoot.querySelector('x-ad-inner').appendChild(newSlottedProp)
return Reflect.set(target, prop, newVal, receiver)
}
})
const adInner = window.document.createElement('x-ad-inner')
this.attachShadow({ mode: 'open' }).appendChild(adInner)
}
connectedCallback() {
window.requestAnimationFrame(() => {
window.requestAnimationFrame(() => {
if (this.dataset.data) {
Object.assign(this.$data, JSON.parse(this.dataset.data))
window.setTimeout(() => {
Object.assign(this.$data, JSON.parse(this.dataset.data))
}, 4000)
}
})
})
}
}
class XForEach extends HTMLElement {
constructor() {
super()
this.attachShadow({ mode: 'open' })
}
connectedCallback() {
window.requestAnimationFrame(() => {
const forRoot = this.querySelector('[data-for]')
const forSlot = forRoot.querySelector('slot')
const forEl = forSlot.assignedNodes()[0]
const eachRoot = this.querySelector('[data-each-root]')
const eachEl = eachRoot.querySelector('[data-each]')
this.$handleSlotChange()
forSlot.addEventListener('slotchange', this.$handleSlotChange)
})
}
$handleSlotChange() {
const forRoot = this.querySelector('[data-for]')
const forEl = forRoot && forRoot.querySelector('slot') && forRoot.querySelector('slot').assignedNodes()[0]
const eachRoot = this.querySelector('[data-each-root]')
const eachEl = eachRoot && eachRoot.querySelector('[data-each]')
;[...((this.shadowRoot || {}).children || [])].forEach(el => el.remove())
if (forEl && eachEl) {
const forArray = JSON.parse(forEl.innerHTML)
if (forArray.length) {
forArray.forEach((item) => {
if (item) {
const data = JSON.stringify(item)
const itemEl = eachRoot.cloneNode(true)
itemEl.dataset.data = data
this.shadowRoot.appendChild(itemEl)
}
})
}
}
}
}
class XIf extends HTMLElement {
constructor() {
super()
this.attachShadow({ mode: 'open' })
}
connectedCallback() {
window.requestAnimationFrame(() => {
this.$handleSlotChange()
const ifSlot = this.querySelector('[data-if] slot')
if (ifSlot) {
ifSlot.addEventListener('slotchange', this.$handleSlotChange)
}
})
}
$handleSlotChange() {
;[...((this.shadowRoot || {}).children || [])].forEach(el => remove())
const ifEl = this.querySelector('[data-if]')
if (ifEl) {
const attachedEl = ifEl.querySelector('slot') && ifEl.querySelector('slot').assignedNodes()[0]
if (attachedEl) {
const slotEl = window.document.createElement('slot')
if (!attachedEl || (attachedEl && !attachedEl.innerText.trim() && !attachedEl.datase.data)) {
slotEl.setAttribute('name', 'x-inert')
}
this.shadowRoot.appendChild(slotEl)
}
}
}
}
class XArticleInner extends HTMLElement {
constructor() {
super()
this.$data = new Proxy({}, {
set: (target, prop, newVal, receiver) => {
switch (prop) {
case '@meta': {
const meta = JSON.parse(newVal)
Object.entries(meta).forEach(([ key, value ]) => {
this.shadowRoot.querySelector('article').classList[value ? 'add' : 'remove'](`is-${key}`)
})
}
}
return Reflect.set(target, prop, newVal, receiver)
}
})
this
.attachShadow({ mode: 'open' })
.appendChild(window.document.getElementById('x-article-inner-template').content.cloneNode(true))
}
connectedCallback() {
window.requestAnimationFrame(() => {
window.requestAnimationFrame(() => {
const slots = [...this.querySelectorAll('[slot]')]
slots.forEach((slot) => {
if (slot.assignedSlot) {
Object.assign(this.$data, {
[slot.assignedSlot.name]: slot.innerHTML
})
slot.assignedSlot.addEventListener('slotchange', ({ target, target: { name } }) => {
if (target.assignedNodes()[0]) {
Object.assign(this.$data, {
[name]: target.assignedNodes()[0].innerHTML
})
} else {
delete this.$data[name]
}
})
}
})
})
})
}
}
class XArticle extends HTMLElement {
constructor() {
super()
this
.attachShadow({ mode: 'open' })
.appendChild(window.document.createElement('x-article-inner'))
this.$data = new Proxy({}, {
deleteProperty: (target, prop, newVal, receiver) => {
const slottedProp = this.shadowRoot.querySelector(`[slot="${prop}"]`)
if (slottedProp) {
slottedProp.remove()
}
return Reflect.deleteProperty(target, prop, newVal, receiver)
},
set: (target, prop, newVal, receiver) => {
const slottedProp = this.shadowRoot.querySelector(`[slot="${prop}"]`)
if (slottedProp) {
slottedProp.remove()
}
const newSlottedProp = window.document.createElement('data')
newSlottedProp.setAttribute('slot', prop)
newSlottedProp.innerHTML = typeof newVal === 'string' ? ((new Date(newVal).getTime() === new Date(newVal).getTime()) ? new Date(newVal).toDateString() : newVal) : (newVal || {}).name || JSON.stringify(newVal)
this.shadowRoot.querySelector('x-article-inner').appendChild(newSlottedProp)
return Reflect.set(target, prop, newVal, receiver)
}
})
}
connectedCallback() {
window.requestAnimationFrame(() => {
this.$handleSlotChange(this.querySelector('script').innerHTML)
this.querySelector('script').addEventListener('slotchange', this.$handleSlotChange)
})
}
$handleSlotChange(data) {
const parsedData = JSON.parse(data)
console.log(parsedData)
Object.assign(this.$data, {
...parsedData
})
}
}
customElements.define('x-if', XIf)
customElements.define('x-for-each', XForEach)
customElements.define('x-article', XArticle)
customElements.define('x-article-inner', XArticleInner)
customElements.define('x-ad', XAd)
customElements.define('x-ad-inner', XAdInner)
;((async function () {
await new Promise(done => window.setTimeout(done, 2000))
window.document.querySelector('x-article').$data.headline = 'Replaced headline!'
delete window.document.querySelector('x-article').$data.hasPart
window.document.querySelector('x-article:nth-of-type(2)').$data.datePublished = '2020-04-01'
console.log(window.document.querySelector('x-article').$data.headline)
window.document.querySelector('x-article:nth-of-type(1)').$data['@meta'] = JSON.stringify({
...(window.document.querySelector('x-article:nth-of-type(1)').$data['@meta'] || {}),
breaking: false
})
window.document.querySelector('x-article:nth-of-type(2)').$data['@meta'] = JSON.stringify({
...(window.document.querySelector('x-article:nth-of-type(2)').$data['@meta'] || {}),
breaking: true
})
})())
</script>
<template id="x-ad-inner-template">
<style>
:host>* {
--padding: 12px;
padding: var(--padding);
}
</style>
<aside>
<a href="${potentialAction.target}">
<h2><slot name="headline"></slot></h2>
</a>
</aside>
</template>
<template id="x-article-inner-template">
<style>
:host > * {
--padding: 12px;
padding: var(--padding);
}
.is-breaking {
background: red;
}
* {
margin: 0;
padding: 0;
}
h1 {
font-style: italic;
border-bottom: 1px solid;
padding-bottom: var(--padding);
margin-bottom: var(--padding);
}
</style>
<div hidden>
<slot name="@meta"></slot>
</div>
<article tabindex="0" aria-labelledby="h1">
<h1 id="h1">
<slot name="headline"></slot>
</h1>
<div>
Published on <slot name="datePublished"></slot>
</div>
<div role="presentation">
<x-if>
By <span data-if><slot name="author"></slot></span>
</x-if>
</div>
<slot name="dateline"></slot> &mdash;
<x-for-each>
<x-if>
<div data-for data-if>
<slot name="hasPart"></slot>
</div>
<x-ad data-each-root>
<div data-each></div>
</x-ad>
</x-if>
</x-for-each>
<slot name="articleBody"></slot>
</article>
</template>
<h1>PickPuck.com</h1>
<x-article>
<script type="application/ld+json">
{
"@context": "http://schema.org/",
"@meta": {
"breaking": true
},
"@type": "NewsArticle",
"headline": "Lorem ipsum sigma fidelitdas sin Dolor policia",
"datePublished": "2019-06-12",
"articleBody": [
"Foo bar",
"Foo bar",
"Foo bar"
],
"dateline": "WASHINGTON",
"author": {
"@type": "Person",
"name": "Michael Puckett"
},
"hasPart": [{
"@type": "WPAdBlock",
"headline": "Try Spotify for free for 30 days",
"sourceOrganization": {
"@type": "Organization",
"name": "Spotify",
"logo": "http://www.spotify.com/logo.png",
"url": "http://www.spotify.com"
},
"provider": {
"@type": "Organization",
"name": "Google Ads",
"url": "http://ads.google.com"
},
"potentialAction": {
"@type": "ViewAction",
"target": "http://googleadnetwork.com/?id=123"
}
}]
}
</script>
</x-article>
<x-article>
<script type="application/ld+json">
{
"@context": "http://schema.org/",
"@meta": {
"breaking": false
},
"type": "NewsArticle",
"headline": "Foo bar",
"datePublished": "2019-06-12",
"articleBody": [
"Foo bar",
"Foo bar",
"Foo bar"
],
"dateline": "WASHINGTON",
"author": {
"type": "Person",
"name": "Michael Puckett"
}
}
</script>
</x-article>
<style>
h1 {
color: green;
}
</style>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment