Skip to content

Commit b5007c8

Browse files
authored
Merge pull request #78 from github/refactor
Refactor private functions into the component
2 parents 992afd7 + 73421ea commit b5007c8

File tree

1 file changed

+109
-109
lines changed

1 file changed

+109
-109
lines changed

src/index.ts

+109-109
Original file line numberDiff line numberDiff line change
@@ -1,113 +1,10 @@
11
const privateData = new WeakMap()
22

3-
const observer = new IntersectionObserver(
4-
entries => {
5-
for (const entry of entries) {
6-
if (entry.isIntersecting) {
7-
const {target} = entry
8-
observer.unobserve(target)
9-
if (!(target instanceof IncludeFragmentElement)) return
10-
if (target.loading === 'lazy') {
11-
handleData(target)
12-
}
13-
}
14-
}
15-
},
16-
{
17-
// Currently the threshold is set to 256px from the bottom of the viewport
18-
// with a threshold of 0.1. This means the element will not load until about
19-
// 2 keyboard-down-arrow presses away from being visible in the viewport,
20-
// giving us some time to fetch it before the contents are made visible
21-
rootMargin: '0px 0px 256px 0px',
22-
threshold: 0.01
23-
}
24-
)
25-
263
// Functional stand in for the W3 spec "queue a task" paradigm
274
function task(): Promise<void> {
285
return new Promise(resolve => setTimeout(resolve, 0))
296
}
307

31-
async function handleData(el: IncludeFragmentElement) {
32-
observer.unobserve(el)
33-
return getData(el).then(
34-
function (html: string) {
35-
const template = document.createElement('template')
36-
// eslint-disable-next-line github/no-inner-html
37-
template.innerHTML = html
38-
const fragment = document.importNode(template.content, true)
39-
const canceled = !el.dispatchEvent(
40-
new CustomEvent('include-fragment-replace', {cancelable: true, detail: {fragment}})
41-
)
42-
if (canceled) return
43-
el.replaceWith(fragment)
44-
el.dispatchEvent(new CustomEvent('include-fragment-replaced'))
45-
},
46-
function () {
47-
el.classList.add('is-error')
48-
}
49-
)
50-
}
51-
52-
function getData(el: IncludeFragmentElement) {
53-
const src = el.src
54-
let data = privateData.get(el)
55-
if (data && data.src === src) {
56-
return data.data
57-
} else {
58-
if (src) {
59-
data = fetchDataWithEvents(el)
60-
} else {
61-
data = Promise.reject(new Error('missing src'))
62-
}
63-
privateData.set(el, {src, data})
64-
return data
65-
}
66-
}
67-
68-
function fetchDataWithEvents(el: IncludeFragmentElement) {
69-
// We mimic the same event order as <img>, including the spec
70-
// which states events must be dispatched after "queue a task".
71-
// https://www.w3.org/TR/html52/semantics-embedded-content.html#the-img-element
72-
return task()
73-
.then(() => {
74-
el.dispatchEvent(new Event('loadstart'))
75-
return el.fetch(el.request())
76-
})
77-
.then(response => {
78-
if (response.status !== 200) {
79-
throw new Error(`Failed to load resource: the server responded with a status of ${response.status}`)
80-
}
81-
const ct = response.headers.get('Content-Type')
82-
if (!isWildcard(el.accept) && (!ct || !ct.includes(el.accept ? el.accept : 'text/html'))) {
83-
throw new Error(`Failed to load resource: expected ${el.accept || 'text/html'} but was ${ct}`)
84-
}
85-
return response.text()
86-
})
87-
.then(
88-
data => {
89-
// Dispatch `load` and `loadend` async to allow
90-
// the `load()` promise to resolve _before_ these
91-
// events are fired.
92-
task().then(() => {
93-
el.dispatchEvent(new Event('load'))
94-
el.dispatchEvent(new Event('loadend'))
95-
})
96-
return data
97-
},
98-
error => {
99-
// Dispatch `error` and `loadend` async to allow
100-
// the `load()` promise to resolve _before_ these
101-
// events are fired.
102-
task().then(() => {
103-
el.dispatchEvent(new Event('error'))
104-
el.dispatchEvent(new Event('loadend'))
105-
})
106-
throw error
107-
}
108-
)
109-
}
110-
1118
function isWildcard(accept: string | null) {
1129
return accept && !!accept.split(',').find(x => x.match(/^\s*\*\/\*/))
11310
}
@@ -150,19 +47,19 @@ export default class IncludeFragmentElement extends HTMLElement {
15047
}
15148

15249
get data(): Promise<string> {
153-
return getData(this)
50+
return this.#getData()
15451
}
15552

15653
attributeChangedCallback(attribute: string, oldVal: string | null): void {
15754
if (attribute === 'src') {
15855
// Source changed after attached so replace element.
15956
if (this.isConnected && this.loading === 'eager') {
160-
handleData(this)
57+
this.#handleData()
16158
}
16259
} else if (attribute === 'loading') {
16360
// Loading mode changed to Eager after attached so replace element.
16461
if (this.isConnected && oldVal !== 'eager' && this.loading === 'eager') {
165-
handleData(this)
62+
this.#handleData()
16663
}
16764
}
16865
}
@@ -181,10 +78,10 @@ export default class IncludeFragmentElement extends HTMLElement {
18178

18279
connectedCallback(): void {
18380
if (this.src && this.loading === 'eager') {
184-
handleData(this)
81+
this.#handleData()
18582
}
18683
if (this.loading === 'lazy') {
187-
observer.observe(this)
84+
this.#observer.observe(this)
18885
}
18986
}
19087

@@ -204,12 +101,115 @@ export default class IncludeFragmentElement extends HTMLElement {
204101
}
205102

206103
load(): Promise<string> {
207-
return getData(this)
104+
return this.#getData()
208105
}
209106

210107
fetch(request: RequestInfo): Promise<Response> {
211108
return fetch(request)
212109
}
110+
111+
#observer = new IntersectionObserver(
112+
entries => {
113+
for (const entry of entries) {
114+
if (entry.isIntersecting) {
115+
const {target} = entry
116+
this.#observer.unobserve(target)
117+
if (!(target instanceof IncludeFragmentElement)) return
118+
if (target.loading === 'lazy') {
119+
this.#handleData()
120+
}
121+
}
122+
}
123+
},
124+
{
125+
// Currently the threshold is set to 256px from the bottom of the viewport
126+
// with a threshold of 0.1. This means the element will not load until about
127+
// 2 keyboard-down-arrow presses away from being visible in the viewport,
128+
// giving us some time to fetch it before the contents are made visible
129+
rootMargin: '0px 0px 256px 0px',
130+
threshold: 0.01
131+
}
132+
)
133+
134+
#handleData(): Promise<void> {
135+
this.#observer.unobserve(this)
136+
return this.#getData().then(
137+
(html: string) => {
138+
const template = document.createElement('template')
139+
// eslint-disable-next-line github/no-inner-html
140+
template.innerHTML = html
141+
const fragment = document.importNode(template.content, true)
142+
const canceled = !this.dispatchEvent(
143+
new CustomEvent('include-fragment-replace', {cancelable: true, detail: {fragment}})
144+
)
145+
if (canceled) return
146+
this.replaceWith(fragment)
147+
this.dispatchEvent(new CustomEvent('include-fragment-replaced'))
148+
},
149+
() => {
150+
this.classList.add('is-error')
151+
}
152+
)
153+
}
154+
155+
#getData(): Promise<string> {
156+
const src = this.src
157+
let data = privateData.get(this)
158+
if (data && data.src === src) {
159+
return data.data
160+
} else {
161+
if (src) {
162+
data = this.#fetchDataWithEvents()
163+
} else {
164+
data = Promise.reject(new Error('missing src'))
165+
}
166+
privateData.set(this, {src, data})
167+
return data
168+
}
169+
}
170+
171+
#fetchDataWithEvents(): Promise<string> {
172+
// We mimic the same event order as <img>, including the spec
173+
// which states events must be dispatched after "queue a task".
174+
// https://www.w3.org/TR/html52/semantics-embedded-content.html#the-img-element
175+
return task()
176+
.then(() => {
177+
this.dispatchEvent(new Event('loadstart'))
178+
return this.fetch(this.request())
179+
})
180+
.then(response => {
181+
if (response.status !== 200) {
182+
throw new Error(`Failed to load resource: the server responded with a status of ${response.status}`)
183+
}
184+
const ct = response.headers.get('Content-Type')
185+
if (!isWildcard(this.accept) && (!ct || !ct.includes(this.accept ? this.accept : 'text/html'))) {
186+
throw new Error(`Failed to load resource: expected ${this.accept || 'text/html'} but was ${ct}`)
187+
}
188+
return response.text()
189+
})
190+
.then(
191+
data => {
192+
// Dispatch `load` and `loadend` async to allow
193+
// the `load()` promise to resolve _before_ these
194+
// events are fired.
195+
task().then(() => {
196+
this.dispatchEvent(new Event('load'))
197+
this.dispatchEvent(new Event('loadend'))
198+
})
199+
return data
200+
},
201+
error => {
202+
// Dispatch `error` and `loadend` async to allow
203+
// the `load()` promise to resolve _before_ these
204+
// events are fired.
205+
task().then(() => {
206+
this.dispatchEvent(new Event('error'))
207+
this.dispatchEvent(new Event('loadend'))
208+
})
209+
throw error
210+
}
211+
)
212+
}
213213
}
214214

215215
declare global {

0 commit comments

Comments
 (0)