Skip to content

Instantly share code, notes, and snippets.

@renoirb
Last active May 5, 2021 18:52
Show Gist options
  • Save renoirb/9b7dcda264b3f3b7dff23e11fd6dbefa to your computer and use it in GitHub Desktop.
Save renoirb/9b7dcda264b3f3b7dff23e11fd6dbefa to your computer and use it in GitHub Desktop.
Example components Single SPA

Example using Single SPA

Serve as is

npx serve --cors

Then make reference to the main.mjs in an import statement

import { components, customElementsDefineOn } from 'http://localhost:5000/main.mjs'

// components are the classes extending HTMLElement
// customElementsDefineOn is to use for registering them to the current DOM

Bundle

npx rollup --format=umd --file dist.js --name SomewhereElse -i localhost.mjs

Or instead use UMD format from http://localhost:5000/dist.js instead (TODO: Make it work)

/**
* Most of this comes from
* https://jsbin.com/jegati/13/edit?html,css,js,console,output
*/
// https://raw.githubusercontent.com/7kfpun/flag-css/master/dist/flags/png/abw.png
const GITHUB_DATA_BASE_URL = 'https://min.gitcdn.link/repo/'
function getFlagImageUrl(slug = '') {
const resourceBaseUrl = `${GITHUB_DATA_BASE_URL}7kfpun/flag-css/master/dist/flags/png/`
return `${resourceBaseUrl}${slug.toLowerCase()}.png`
}
/** ********** Custom Elements ************ **/
export class FlagIcon extends HTMLElement {
static get TAG_NAME() {
return 'flag-icon'
}
static get observedAttributes() {
return ['country']
}
constructor() {
super()
const country = 'can'
this._countryName = ''
this._countryCode = country
}
get template() {
const code = this.country.toLowerCase()
let title = this._countryName
return `<img src="${getFlagImageUrl(code)}" title="${title}" /> `
}
attributeChangedCallback(name, oldVal, newVal) {
if (typeof newVal === 'string' && oldVal !== newVal && newVal.length == 3) {
this._countryCode = newVal
this._countryName = this.hasCode(newVal) ? newVal : ''
this._update()
}
}
hasCode(code = '') {
const key = code.toLowerCase().substring(0, 3)
let outcome = false
if ('countries' in this.ownerDocument.defaultView) {
outcome = Reflect.has(this.ownerDocument.defaultView.countries, key)
} else {
console.log('hasCode no countries')
}
return outcome
}
connectedCallback() {
this._update()
}
get country() {
return this._countryCode
}
set country(v) {
this.setAttribute('country', v)
}
_update() {
this.innerHTML = this.template
}
}
const happinessToDonger = new Map([
['saddest', '( ຈ ﹏ ຈ )'],
['sadder', '(-_-。)'],
['sad', '(///_-)'],
['neutral', '(╭ರ_⊙)'],
['good', '( ͡↑ ͜ʖ ͡↑)'],
['happy', '☆*:. o(≧▽≦)o .:*☆'],
])
const toMoodState = state => {
let selected = 'neutral'
let val = +state
if (Number.isNaN(val) === false && Number.isInteger(val)) {
if (val > 100) {
selected = 'happy'
} else if (val > 70) {
selected = 'good'
} else if (val < 50) {
selected = 'sad'
} else if (val < 40) {
selected = 'neutral'
} else if (val < 30) {
selected = 'sadder'
} else if (val < 10) {
selected = 'saddest'
}
} else {
selected = 'saddest'
}
return selected
}
const toDonger = state => {
const selected = toMoodState(state)
const pick = happinessToDonger.get(selected)
return String(pick)
}
export class SmileyFace extends HTMLElement {
static get TAG_NAME() {
return 'smiley-face'
}
static get observedAttributes() {
return ['happiness']
}
constructor() {
super()
this._happiness = 51
}
get template() {
const title = toMoodState(this.happiness)
const donger = toDonger(this.happiness)
return `<pre title="${title}" style="font-size:2em;">${donger}</pre>`
}
attributeChangedCallback(name, oldVal, newVal) {
if (name === 'happiness' && oldVal !== newVal) {
this._happiness = newVal
this._update()
}
}
async connectedCallback() {
this._update()
}
get happiness() {
return this._happiness
}
set happiness(v) {
this.setAttribute('happiness', v)
}
_update() {
this.innerHTML = this.template
}
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
</head>
<body>
<div id="localhost"></div>
<script type="module" src="main.mjs"></script>
<script type="module" src="localhost.mjs"></script>
</body>
</html>
import singleSpaHtml from 'https://cdn.skypack.dev/single-spa-html'
import * as singleSpa from 'https://cdn.skypack.dev/single-spa'
import { createCountriesList, customElementsDefineOn } from './main.mjs'
export const localhost = singleSpaHtml({
template: `
<div>
<h1>Example from Somewhere Else</h1>
<h2>Flag icon</h2>
<flag-icon country="can"></flag-icon>
<div>
<input type=text value="can" autocomplete=country-name title="Change my value, see the text below update!" />
</div>
<h2>Emoji</h2>
<smiley-face happiness="100"></smiley-face>
</div>
`,
domElementGetter: () => window.document.getElementById('localhost'),
})
export default (async () => {
window.singleSpa = singleSpa
customElementsDefineOn(window)
singleSpa.start()
localhost.mount('#localhost').then(e => {
window.document.querySelector('[autocomplete=country-name]').addEventListener('input', event => {
const inputValue = event.target.value
document.querySelector('flag-icon').setAttribute('country', inputValue)
if (['can', 'aus', 'fra', 'bra', 'usa'].includes(inputValue)) {
document.querySelector('smiley-face').setAttribute('happiness', 1000)
} else if (['are', 'prk', 'sau'].includes(inputValue)) {
// Places with dictatorships? just an example for different, when chosing another country
document.querySelector('smiley-face').setAttribute('happiness', 1)
} else {
document.querySelector('smiley-face').setAttribute('happiness', 70)
}
})
})
const a = await createCountriesList()
console.log('localhost', { countriesList: a })
})()
import * as components from './components.mjs'
export { components }
/**
* Most of this comes from
* https://jsbin.com/jegati/13/edit?html,css,js,console,output
*
* Bookmarks:
* - https://single-spa.js.org/docs/configuration/
* - https://jsbin.com/jegati/13/edit?html,css,js,output
*
* Scratch pad
*
* ```js
* (() => { const s = document.createElement('script'); s.type='module'; s.src='http://localhost:5000/main.mjs'; document.head.appendChild(s); })()
* ```
*
*/
// https://raw.githubusercontent.com/7kfpun/flag-css/master/dist/flags/png/abw.png
const GITHUB_DATA_BASE_URL = 'https://min.gitcdn.link/repo/'
const countryNamesLanguageCode = 'en'
function fetchJson(url) {
return fetch(url)
.then(recv => recv.json())
.then(payload => payload)
}
function fetchDataset() {
const names = fetchJson(
`${GITHUB_DATA_BASE_URL}michaelwittig/node-i18n-iso-countries/master/langs/${countryNamesLanguageCode}.json`,
)
const codes = fetchJson(`${GITHUB_DATA_BASE_URL}michaelwittig/node-i18n-iso-countries/master/codes.json`)
console.log('fetchDataset', codes)
return Promise.all([names, codes]).then(recv => {
const packaged = { names: recv[0], codes: recv[1] }
return packaged
})
}
function userAgentSupports(featureName = '') {
let isSupported = false
if (featureName === 'localStorage') {
try {
isSupported = window.localStorage instanceof Storage
} catch (e) {
/* fail silently, this is an isser anyway */
}
}
return isSupported
}
function fromStorage() {
return Promise.resolve().then(() => {
const recv = window.localStorage.getItem('countries')
if (typeof recv === 'string' || recv instanceof String) {
console.log(`fromStorage: localStorage('countries') had something, we will return`)
return JSON.parse(recv)
}
console.log(`fromStorage: localStorage('countries') had nothing, we will throw`)
})
}
function handleNothingFromStorage() {
console.log(`handleNothingFromStorage: Nothing was found in storage`)
return fetchDataset().then(countries => {
console.log(
`handleNothingFromStorage: doing fetchDataset(), we just downloaded from GitHub and we will add to localStorage('countries') and resume`,
)
window.localStorage.setItem('countries', JSON.stringify(countries))
return countries
})
}
// Clumsy detail here is that we support User-Agents without localStorage
// but we assume blindly Promise suport.
// localStorage is older than Promises.
function loadDataset() {
// TODO: Make this asynchronous.
// because we can't either return undefined, or data.
// we gotta get the data the same way,
// regardless of if we had it locally or not.
if (userAgentSupports('localStorage') === true) {
// How to handle errors.
// Here we only have
console.log(`loadDataset: localStorage is supported`)
return fromStorage()
.then(handleNothingFromStorage)
.then(c => {
console.log(`loadDataset, when supporting localStorage: attach to window.countries`)
return c
})
} else {
console.log(`loadDataset: localStorage is supported`)
return fetchDataset().then(c => {
console.log(
`loadDataset: doing fetchDataset(), when NOT supporting localStorage: attach to window.countries like in the other case`,
)
return c
})
}
}
function toCountriesGlobalObj(recv) {
let newList = Object.create(null)
const { codes = [], names = { countries: {} } } = recv
codes.map(c => {
const k = c[1].toLowerCase()
const v = names.countries[c[0]]
newList[k] = v
})
return newList
}
export const createCountriesList = () =>
loadDataset().then(e => {
const countries = toCountriesGlobalObj(e)
let dataListObj = document.createElement('datalist')
dataListObj.setAttribute('id', 'countries_list')
const option = (label, value) => `<option value="${value}" label="${label}">${label}</option>`
let out = []
for (let [key, label] of Object.entries(countries)) {
out.push(option(label, key))
}
dataListObj.innerHTML = out.join('')
document.querySelector('input').parentNode.appendChild(dataListObj)
document.querySelector('input').setAttribute('list', 'countries_list')
document.defaultView.countries = Object.freeze(countries)
console.log('done')
return e
})
export const customElementsDefineOn = w => {
if ('customElements' in w === false) {
throw new Error('Must pass window object to initialize')
}
for (const [_a, b] of Object.entries(components)) {
let alreadyRegistered = false
try {
w.customElements.define(b.TAG_NAME, b)
alreadyRegistered = true
} catch (_e) {
console.warn(_e)
// caught
}
console.log('customElementsDefineOn', {
alreadyRegistered,
tagName: b.TAG_NAME,
})
}
}
@renoirb
Copy link
Author

renoirb commented Apr 29, 2021

Screen Shot 2021-04-29 at 18 47 05

@renoirb
Copy link
Author

renoirb commented May 5, 2021

TODO: check this out

import { components, createCountriesList, customElementsDefineOn } from 'https://gist.githack.com/renoirb/9b7dcda264b3f3b7dff23e11fd6dbefa/raw/main.mjs'
export default from 'https://gist.githack.com/renoirb/9b7dcda264b3f3b7dff23e11fd6dbefa/raw/localhost.mjs'

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment