Skip to content

Instantly share code, notes, and snippets.

@peerreynders
Last active April 2, 2019 18:39
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save peerreynders/084079c213e9da2abfcbd613fdeb6abc to your computer and use it in GitHub Desktop.
Save peerreynders/084079c213e9da2abfcbd613fdeb6abc to your computer and use it in GitHub Desktop.
Web Components in Action v6: 7.5 Entering the Shadow DOM with Slots (p.152)
// file: components/biz-card/biz-card.mjs
import { patchContentMap } from '../../helpers/templateLoader.mjs'
/*
#1:
See patchContentMap call below which creates
static get _contentMap
static set _templateUri
#2:
- private (static) fields are at stage 3
- public (static) fields are available in Chrome 72
- alternately store in module scoped variables
https://github.com/tc39/proposal-static-class-features#user-content-static-private-fields
https://developers.google.com/web/updates/2018/12/class-fields#conclusion
#3:
https://developers.google.com/web/fundamentals/web-components/best-practices#create-your-shadow-root-in-the-constructor
https://developer.mozilla.org/en-US/docs/Web/API/Element/shadowRoot
> The Element.shadowRoot read-only property represents the shadow root hosted by the element.
> Use Element.attachShadow() to add a shadow root to an existing element.
#4:
- Waiting for connectedCallback in order to access the attribute value
- 'templateFile' isn't observed as it doesn't react to changes.
The first BizCard._templateUri value set resolves the promise
for the uri - then the fetch can start.
#5:
No class constructor/static block yet
https://github.com/tc39/proposal-class-static-block
*/
class BizCard extends HTMLElement {
static get observedAttributes() {
return ['layout']
}
// #1
// #2
static _noContent = document.createDocumentFragment()
// #3
constructor() {
super()
this._populateCard = this._populateCard.bind(this)
this.attachShadow({mode: 'open'});
}
attributeChangedCallback(name, _oldValue, newValue) {
if (name === 'layout') {
Promise.all([newValue, BizCard._contentMap])
.then(this._populateCard)
}
}
connectedCallback() {
if(this.hasAttribute('templateFile')) {
// #4
BizCard._templateUri = this.getAttribute('templateFile')
}
}
_populateCard([name, contentMap]) {
const content = contentMap.get(name) || BizCard._noContent
const clone = document.importNode(content, true)
this._removeChildren()
this.shadowRoot.appendChild(clone)
}
_removeChildren() {
let root = this.shadowRoot
for(let child = root.lastChild; child; child = root.lastChild) {
root.removeChild(child)
}
}
}
// #5
patchContentMap(BizCard, '_templateUri', '_contentMap')
if (!customElements.get('biz-card')) {
customElements.define('biz-card', BizCard)
}
<html>
<head>
<!--
file: index.html
Web Components in Action MEAP v6
https://livebook.manning.com/#!/book/web-components-in-action/chapter-7/v-6/comment-487676
original:
- https://github.com/bengfarrell/webcomponentsinaction/tree/master/chapter7/7.5-slotsandtemplates
- https://github.com/bengfarrell/webcomponentsinaction/blob/master/chapter7/7.5-slotsandtemplates/background-pattern.png
- https://github.com/bengfarrell/webcomponentsinaction/blob/master/chapter7/7.5-slotsandtemplates/biz-card-logo.png
#1:
prevent undefined custom element FOUC (flash of unstyled content)
polyfill alternative:
https://polymer-library.polymer-project.org/3.0/docs/devguide/style-shadow-dom#style-undefined-elements
-->
<title>Business Card</title>
<style>
biz-card:not(:defined) {
display: none; /* #1 */
}
</style>
</head>
<body>
<p>
<select>
<option value="none">none</option>
<option value="default-card">default</option>
<option value="variation">variation</option>
</select>
</p>
<biz-card layout="none" templateFile="./templates.html">
<span slot="firstname">Emmet</span>
<span slot="lastname">Brown</span>
<span slot="title">Student of all Sciences</span>
<span slot="phone">555.555.4385</span>
<span slot="email">emmett@docbrown.flux</span>
<span slot="website">www.docbrown.flux</span>
</biz-card>
<script type="module" src="./components/biz-card/biz-card.mjs"></script>
<script>
function updateLayout({ target: { value } }) {
bizcard.setAttribute('layout', value)
}
const bizcard = document.querySelector('biz-card')
document.querySelector('select').addEventListener('change', updateLayout)
</script>
</body>
</html>
// file: helpers/templateLoader.mjs
export function fetchContentMap(uri) {
const init = {
method: 'GET',
headers: {
'Accept': 'text/html'
}
}
const request = new Request(uri, init)
return fetch(request)
.then(checkResponse)
.then(makeContentMap)
}
function checkResponse(response) {
if (!response.ok) {
throw new Error('HTTP error, status = ' + response.status)
}
return response.text()
}
function makeContentMap(html) {
let element = document.createElement('div')
element.innerHTML = html
const contentPairs = Array.prototype.reduce.call(
element.children,
appendContent,
[]
)
return new Map(contentPairs)
}
function appendContent(pairs, template) {
const name = template.classList.item(0)
if (name) {
pairs.push([name, template.content])
}
return pairs
}
export function patchContentMap(classObject, uriSetter, mapGetter) {
let contentMap
// function to accept the Uri for the template file
function uriExecutor(resolve, _reject) {
Object.defineProperty(classObject, uriSetter, {set: resolve})
}
// function to return the content map extracted from the templates
function getContentMap() {
return contentMap
}
Object.defineProperty(classObject, mapGetter, {get: getContentMap})
contentMap =
new Promise(uriExecutor) // URI Promise
.then(fetchContentMap) // contentMap Promise
}
<template class="default-card">
<style>
.biz-card {
font-size: 16px;
font-family: sans-serif;
color: white;
width: 700px;
height: 400px;
display: inline-block;
border-color: #9a9a9a;
background-size: 5%;
background-image: url("background-pattern.png");
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
}
.biz-card .logo {
height: 100px;
margin-top: 10%;
text-align: center;
background-image: url("biz-card-logo.png");
background-size: contain;
background-position-x: center;
background-repeat: no-repeat;
}
.biz-card .top-text {
text-align: center;
}
.biz-card .top-text h1 {
font-size: 2.5em;
margin-bottom: 0;
}
.biz-card .top-text h3 {
margin: 0;
}
.biz-card .bottom-text {
text-align: center;
margin-top: 10%;
}
.biz-card .bottom-text h3 {
margin: 0;
}
</style>
<div class="biz-card">
<div class="logo"></div>
<div class="top-text">
<h1>
<slot name="firstname">First</slot> <slot
name="lastname">LastName</slot>
</h1>
<h3><slot name="title">Job Title</slot></h3>
</div>
<div class="bottom-text">
<h3>phone: <slot name="phone">#xxx.xxx.xxxx</slot></h3>
<h3>
<slot name="email">email@email.com</slot> / <slot
name="website">http://website.com</slot>
</h3>
</div>
</div>
</template>
<template class="variation">
<style>
.biz-card {
position: relative;
font-size: 16px;
font-family: sans-serif;
color: white;
width: 700px;
height: 400px;
display: inline-block;
border-color: #9a9a9a;
background-size: 5%;
background-image: url("background-pattern.png");
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
}
.biz-card .logo {
width: 100px;
height: 100px;
position: absolute;
top: 10px;
left: 10px;
background-image: url("biz-card-logo.png");
background-size: contain;
background-position-x: center;
background-repeat: no-repeat;
}
.biz-card .name {
margin-bottom: 0;
margin-top: 50px;
margin-left: 130px;
}
.biz-card .title {
font-size: 48px;
margin-top: 100px;
text-align: center;
}
.biz-card .info {
text-align: right;
width: 95%;
}
</style>
<div class="biz-card">
<div class="logo"></div>
<h2 class="name"><slot name="firstname">First</slot> <slot name="lastname">LastName</slot></h2>
<h1 class="title"><slot name="title">Job Title</slot></h1>
<h4 class="info">phone: <slot name="phone">#xxx.xxx.xxxx</slot></h4>
<h4 class="info"><slot name="email">email@email.com</slot></h4>
<h4 class="info"><slot name="website">http://website.com</slot></h4>
</div>
</template>
<template class="none">
<style>
.biz-card {
position: relative;
font-size: 16px;
font-family: sans-serif;
color: white;
width: 700px;
height: 400px;
display: inline-block;
border-color: #9a9a9a;
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
}
</style>
<div class="biz-card"></div>
</template>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment