Created
June 14, 2019 07:53
-
-
Save michaelcpuckett/136279dbfb2b8fa6d05cd1ac93e30ebd 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
<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> — | |
<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