Skip to content

Commit 26e4705

Browse files
authored
Merge pull request #1 from github/init
Init <remote-input>
2 parents af2e957 + 570ca6f commit 26e4705

File tree

9 files changed

+404
-231
lines changed

9 files changed

+404
-231
lines changed

LICENSE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
Copyright (c) 2018 GitHub, Inc.
1+
Copyright (c) 2019 GitHub, Inc.
22

33
Permission is hereby granted, free of charge, to any person obtaining
44
a copy of this software and associated documentation files (the

README.md

Lines changed: 46 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,62 @@
1-
# &lt;custom-element&gt; element
1+
# &lt;remote-input&gt; element
22

3-
Boilerplate for creating a custom element.
3+
An input element that sends its value to a server endpoint and renders the response body.
44

55
## Installation
66

77
```
8-
$ npm install @github/custom-element-element
8+
$ npm install @github/remote-input-element
99
```
1010

1111
## Usage
1212

1313
```js
14-
import '@github/custom-element-element'
14+
import '@github/remote-input-element'
1515
```
1616

1717
```html
18-
<custom-element></custom-element>
18+
<!-- Filter a list of items from the server -->
19+
<remote-input src="/query" aria-owns="results">
20+
<input>
21+
</remote-input>
22+
<ul id="results"></ul>
23+
```
24+
25+
```html
26+
<!-- Live preview of Markdown -->
27+
<remote-input src="/preview" aria-owns="md-preview">
28+
<textarea></textarea>
29+
</remote-input>
30+
<div id="md-preview"></div>
31+
```
32+
33+
### Styling loading state
34+
35+
A boolean `[loading]` attribute is added to `<remote-input>` when a network request begins and removed when it ends.
36+
37+
```css
38+
.loading-icon { display: none; }
39+
remote-input[loading] .loading-icon { display: inline; }
40+
```
41+
42+
### Events
43+
44+
```js
45+
const remoteInput = document.querySelector('remote-input')
46+
47+
// Network request lifecycle events.
48+
remoteInput.addEventListener('loadstart', function(event) {
49+
console.log('Network request started', event)
50+
})
51+
remoteInput.addEventListener('loadend', function(event) {
52+
console.log('Network request complete', event)
53+
})
54+
remoteInput.addEventListener('load', function(event) {
55+
console.log('Network request succeeded', event)
56+
})
57+
remoteInput.addEventListener('error', function(event) {
58+
console.log('Network request failed', event)
59+
})
1960
```
2061

2162
## Browser support

examples/index.html

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,47 @@
22
<html lang="en">
33
<head>
44
<meta charset="utf-8">
5-
<title>custom-element demo</title>
5+
<title>remote-input demo</title>
6+
<style>
7+
marquee, textarea, input { width: 20rem; }
8+
remote-input, textarea { display: block; }
9+
</style>
10+
<script>
11+
function fakeFetch(url) {
12+
const urlObj = new URL(url)
13+
let html = ''
14+
if (url.pathname === '/results') {
15+
const doc = document.createElement('div')
16+
doc.innerHTML = `<li>Hubot</li><li>BB-8</li><li>Wall-E</li><li>Bender</li>`
17+
const q = url.searchParams.get('q')
18+
for (const el of doc.querySelectorAll('li')) {
19+
if (q !== '' && !el.textContent.toLowerCase().match(q.toLowerCase())) el.remove()
20+
}
21+
html = doc.innerHTML
22+
} else if (url.pathname === '/marquee') {
23+
html = `<marquee>${url.searchParams.get('q') || '🐈 Nothing to preview 🐈'}</marquee>`
24+
}
25+
const promiseHTML = new Promise(resolve => resolve(html))
26+
return new Promise(resolve => resolve({text: () => promiseHTML}))
27+
}
28+
window.fetch = fakeFetch
29+
</script>
630
</head>
731
<body>
8-
<custom-element></custom-element>
9-
32+
<remote-input aria-owns="marquee-preview" src="/marquee">
33+
<label>Marquee preview <textarea></textarea></label>
34+
</remote-input>
35+
<div id="marquee-preview"></div>
36+
37+
<remote-input aria-owns="results" src="/results">
38+
<label>Search robots<br><input type="text"></label>
39+
</remote-input>
40+
<ul id="results"></ul>
41+
1042
<!-- GitHub Pages development script, uncomment when running example locally and comment out the production one -->
1143
<!-- <script src="../dist/index.umd.js"></script> -->
12-
44+
1345
<!-- GitHub Pages demo script -->
14-
<script src="https://unpkg.com/@github/custom-element-boilerplate@latest/dist/index.umd.js"></script></body>
46+
<script src="https://unpkg.com/@github/remote-input-element@latest/dist/index.umd.js"></script>
47+
</body>
1548
</html>

index.js

Lines changed: 100 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,111 @@
11
/* @flow strict */
22

3-
class CustomElementElement extends HTMLElement {
4-
constructor() {
5-
super()
3+
class RemoteInputElement extends HTMLElement {
4+
currentQuery: ?string
5+
debounceInputChange: Event => void
6+
boundFetchResults: Event => mixed
7+
8+
static get observedAttributes() {
9+
return ['src']
10+
}
11+
12+
attributeChangedCallback(name: string, oldValue: string) {
13+
if (oldValue && name === 'src') {
14+
this.fetchResults(false)
15+
}
616
}
717

818
connectedCallback() {
9-
this.textContent = ':wave:'
19+
const input = this.input
20+
if (!input) return
21+
22+
input.setAttribute('autocomplete', 'off')
23+
input.setAttribute('spellcheck', 'false')
24+
25+
this.debounceInputChange = debounce(this.fetchResults.bind(this))
26+
this.boundFetchResults = this.fetchResults.bind(this)
27+
input.addEventListener('focus', this.boundFetchResults)
28+
input.addEventListener('change', this.boundFetchResults)
29+
input.addEventListener('input', this.debounceInputChange)
30+
}
31+
32+
disconnectedCallback() {
33+
const input = this.input
34+
if (!input) return
35+
36+
input.removeEventListener('focus', this.boundFetchResults)
37+
input.removeEventListener('change', this.boundFetchResults)
38+
input.removeEventListener('input', this.debounceInputChange)
39+
}
40+
41+
get input(): ?(HTMLInputElement | HTMLTextAreaElement) {
42+
const input = this.querySelector('input, textarea')
43+
return input instanceof HTMLInputElement || input instanceof HTMLTextAreaElement ? input : null
44+
}
45+
46+
get resultsContainer(): ?HTMLElement {
47+
return document.getElementById(this.getAttribute('aria-owns') || '')
48+
}
49+
50+
get src(): string {
51+
return this.getAttribute('src') || ''
52+
}
53+
54+
set src(url: string) {
55+
this.setAttribute('src', url)
56+
}
57+
58+
get name(): string {
59+
return this.getAttribute('name') || 'q'
60+
}
61+
62+
set name(name: string) {
63+
this.setAttribute('name', name)
1064
}
1165

12-
disconnectedCallback() {}
66+
async fetchResults(checkCurrentQuery: boolean = true) {
67+
if (!this.input) return
68+
const query = this.input.value
69+
if (checkCurrentQuery && this.currentQuery === query) return
70+
this.currentQuery = query
71+
const src = this.src
72+
if (!src) return
73+
const resultsContainer = this.resultsContainer
74+
if (!resultsContainer) return
75+
76+
const url = new URL(src, window.location.origin)
77+
const params = new URLSearchParams(url.search)
78+
params.append(this.name, query)
79+
url.search = params.toString()
80+
81+
this.dispatchEvent(new CustomEvent('loadstart'))
82+
this.setAttribute('loading', '')
83+
try {
84+
const html = await fetch(url).then(data => data.text())
85+
this.dispatchEvent(new CustomEvent('load'))
86+
resultsContainer.innerHTML = html
87+
} catch {
88+
this.dispatchEvent(new CustomEvent('error'))
89+
}
90+
this.removeAttribute('loading')
91+
this.dispatchEvent(new CustomEvent('loadend'))
92+
}
93+
}
94+
95+
function debounce(callback) {
96+
let timeout
97+
return function() {
98+
clearTimeout(timeout)
99+
timeout = setTimeout(() => {
100+
clearTimeout(timeout)
101+
callback()
102+
}, 300)
103+
}
13104
}
14105

15-
export default CustomElementElement
106+
export default RemoteInputElement
16107

17-
if (!window.customElements.get('custom-element')) {
18-
window.CustomElementElement = CustomElementElement
19-
window.customElements.define('custom-element', CustomElementElement)
108+
if (!window.customElements.get('remote-input')) {
109+
window.RemoteInputElement = RemoteInputElement
110+
window.customElements.define('remote-input', RemoteInputElement)
20111
}

0 commit comments

Comments
 (0)