-
Notifications
You must be signed in to change notification settings - Fork 1
/
celo.js
129 lines (111 loc) · 4.38 KB
/
celo.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
/* Basically the same as the mjs, except no backSearch() and no export default */
(function (){
// Defining variables
let config = window.celoConfig ? window.celoConfig : {}
const componentsPath = config.componentsPath ? config.componentsPath : "/components"
const containerId = config.containerId ? config.containerId : "_celo"
// A list to keep track of loaded components, so we don't fetch them twice.
const loadedComponents = []
// Sets up an observer that reacts to elements being added to the DOM.
function setUpObserver () {
const observer = new MutationObserver(parseElements)
observer.observe(document, { childList: true, subtree: true })
return observer
}
// If the element is <body>, set up a container and cache. If it has a hyphen,
// then its a custom: we must fetch it if we don't have done it already.
// This is a recursive function, so it will allow deeply parsing (necessary
// for use in React).
function parseElements (elementList) {
elementList.forEach(el => {
if (el.addedNodes) el = el.addedNodes[0]
if (el instanceof HTMLElement) parseElements( el.childNodes )
else return false
if( !el.tagName.includes('-')) return false
const tagName = el.tagName.toLowerCase()
if( loadedComponents.includes(tagName)) return
setUpContainer()
markDefined(tagName)
getMarkUp(tagName)
.then(markUp => injectMarkup(markUp))
.then(frag => recreateScripts(frag))
.then(frag => addToDOM(frag))
.then(() => el.shadowRoot && parseElements(el.shadowRoot.childNodes))
})
}
// A hidden div is needed to hold the components' templates. Let's check if it
// exists, and create it if it doesn't.
function setUpContainer () {
if( !document.querySelector(`#${containerId}`) && document.querySelector('body') ){
const container = document.createElement('div')
container.id = containerId
document.body.appendChild(container)
}
}
// Checks if component hasn't been loaded yet.
function isNew( tagName ){
return !loadedComponents.includes( tagName.toLowerCase() )
}
// Make a note of loaded web component
function markDefined( tagName ){
loadedComponents.push( tagName )
}
// Fetches the needed markup from the server
function getMarkUp( tagName ){
return fetch(`${componentsPath}/${ tagName }.html`)
.then( res => {
if( !res.ok )
console.warn(`Component <${ tagName }> not found. Is it a subcomponent?`)
else
return res.text()
})
}
// Append the html text to a DOM element. This will parse the element, but
// the scripts won't run.
function injectMarkup( markUp ){
if( !markUp ) return false
let markUpFragment = document.createDocumentFragment()
let container = document.createElement('div')
container.innerHTML = markUp
while( container.firstChild ) markUpFragment.appendChild( container.firstChild )
return markUpFragment
}
// Oftentimes scripts are inserted/cloned with an "already started" flag on,
// so they just don't run. To deal with that, we recreate them as new
// elements.
function recreateScripts( element ){
if( !element ) return false
element.querySelectorAll( 'script' ).forEach( script => {
let scriptElement = document.createElement( 'script' )
scriptElement.appendChild(document.createTextNode(script.innerHTML))
let container = script.parentElement || element
container.appendChild( scriptElement )
container.removeChild( script )
})
return element
}
// Adds the component to the DOM
function addToDOM( fragment ){
if( !fragment ) return false
document.querySelector(`#${containerId}`).append( fragment )
}
// Remove an element and attach it to the same position, forcing it to be reparsed.
function reparseElement( el ){
let parent = el.parentNode
let sibling = el.nextSibling
el.parentNode.removeChild(el)
if( sibling )
parent.insertBefore( el, sibling )
else
parent.appendChild( el )
}
// Extends customElements.define to keep synchrounous tag on the custom elements
const originalCustomElementDefinition = customElements.define
customElements.define = function(){
loadedComponents.push( arguments[0] )
return originalCustomElementDefinition.apply( customElements, arguments )
}
setUpContainer()
let watcher = setUpObserver()
return watcher
})()