Skip to content

Commit 3c75817

Browse files
committed
feat: Improvements to MySearchApiElement (#66)
1 parent 8808728 commit 3c75817

1 file changed

Lines changed: 56 additions & 39 deletions

File tree

src/components/MySearchApiElement.js

Lines changed: 56 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -13,25 +13,38 @@ export class MySearchApiElement extends HTMLElement {
1313
#form;
1414
#resultElem;
1515
#data;
16+
#priv = {};
1617
#response;
1718

1819
get #searchId () { return this.getAttribute('search-id'); }
20+
1921
// https://developers.google.com/custom-search/v1/introduction#identify_your_application_to_google_with_api_key
20-
get #apiKey () { return this.getAttribute('key'); }
22+
#getApiKey () {
23+
this.#priv.apiKey = this.getAttribute('key');
24+
this.setAttribute('key', '');
25+
}
26+
2127
get #buttonText () { return this.getAttribute('button-text') ?? 'Search'; }
28+
get #resultTemplate () { return this.getAttribute('result-template') ?? '%s results found'; }
2229

23-
#searchUrl (query) {
24-
const encq = encodeURIComponent(query);
25-
return `https://customsearch.googleapis.com/customsearch/v1?key=${this.#apiKey}&cx=${this.#searchId}&q=${encq}`;
30+
get #query () { return this.#form.elements.query.value; }
31+
get #encQuery () { return encodeURIComponent(this.#query); }
32+
33+
get #searchUrl () {
34+
return `https://customsearch.googleapis.com/customsearch/v1?key=${this.#priv.apiKey}&cx=${this.#searchId}&q=${this.#encQuery}`;
2635
}
2736

37+
get #resultItems () { return this.#data.items ?? []; }
38+
get #count () { return this.#resultItems.length; }
39+
2840
#assertRequired () {
2941
console.assert(this.#searchId, 'search-id is required');
30-
console.assert(this.#apiKey, 'api-key is required');
42+
console.assert(this.#priv.apiKey, 'api-key is required');
3143
console.assert(this.#buttonText, 'button-text is required');
3244
}
3345

3446
connectedCallback () {
47+
this.#getApiKey();
3548
this.#assertRequired();
3649
const root = this.attachShadow({ mode: 'open' });
3750

@@ -53,31 +66,35 @@ export class MySearchApiElement extends HTMLElement {
5366
this.dataset.loading = true;
5467
ev.preventDefault();
5568

56-
const query = ev.target.elements.query.value;
57-
58-
const { items } = await this.#fetchResults(query);
59-
const elems = items.map((it) => this.#createListItem(it));
69+
await this.#fetchResults();
70+
const elems = this.#resultItems.map((it) => this.#createListItem(it));
6071

6172
elems.forEach((el) => { this.#resultElem.appendChild(el); });
6273

74+
this.#markResults();
75+
this.#summarizeResults();
76+
6377
this.dataset.loading = false;
6478
}
6579

66-
async #fetchResults (query) {
67-
const resp = this.#response = await fetch(this.#searchUrl(query));
68-
this.dataset.query = query;
80+
#summarizeResults () {
81+
this.#form.elements.output.value = this.#resultTemplate.replace('%s', this.#count);
82+
this.dataset.count = this.#count;
83+
}
84+
85+
async #fetchResults () {
86+
const resp = this.#response = await fetch(this.#searchUrl);
87+
this.dataset.query = this.#query;
6988
this.dataset.httpStatus = resp.status;
7089

7190
if (!resp.ok) {
72-
console.error('Fetch Error:', resp.status, resp.url, resp); // 400 Bad Request.
91+
console.error('Search Fetch Error:', resp.status, resp.url, resp); // 400 Bad Request.
7392
return { items: [], resp };
7493
}
7594
this.#data = await resp.json();
7695
const { context, items, kind, queries, searchInformation } = this.#data;
7796

78-
console.debug('Fetch OK:', context, items, kind, queries, searchInformation, resp, [this]);
79-
80-
this.dataset.count = items.length;
97+
console.debug('Search Fetch OK:', context, items, kind, queries, searchInformation, resp, [this]);
8198

8299
return { context, items, kind, queries, searchInformation, resp };
83100
}
@@ -103,14 +120,14 @@ export class MySearchApiElement extends HTMLElement {
103120
}
104121

105122
#createElements () {
106-
const form = document.createElement('form');
107-
const results = document.createElement('ul');
108-
const labelElem = document.createElement('label');
109-
const inputElem = document.createElement('input');
110-
const outputElem = document.createElement('output');
111-
const buttonElem = document.createElement('button');
112-
const buttonTextElem = document.createElement('span');
113-
const slotElem = document.createElement('slot');
123+
const form = this.#createElement('form', false);
124+
const results = this.#createElement('ul', 'ul results');
125+
const labelElem = this.#createElement('label', -1, [['for', 'q']]);
126+
const inputElem = this.#createElement('input', -1, [['id', 'q'], ['type', 'search'], ['name', 'query'], ['required', '']]);
127+
const outputElem = this.#createElement('output', -1, [['name', 'output']]);
128+
const buttonElem = this.#createElement('button');
129+
const buttonTextElem = this.#createElement('span', 'buttonText');
130+
const slotElem = this.#createElement('slot', false);
114131

115132
form.appendChild(labelElem);
116133
form.appendChild(inputElem);
@@ -124,26 +141,26 @@ export class MySearchApiElement extends HTMLElement {
124141
buttonTextElem.textContent = this.#buttonText;
125142

126143
buttonElem.setAttribute('aria-label', this.#buttonText);
127-
128-
labelElem.setAttribute('part', 'label');
129-
inputElem.setAttribute('part', 'input');
130-
buttonElem.setAttribute('part', 'button');
131-
buttonTextElem.setAttribute('part', 'buttonText');
132-
outputElem.setAttribute('part', 'output');
133-
results.setAttribute('part', 'ul results');
134-
135-
labelElem.setAttribute('for', 'q');
136-
inputElem.id = 'q';
137-
inputElem.type = 'search';
138-
inputElem.name = 'query';
139-
outputElem.name = 'output';
140-
141-
inputElem.setAttribute('required', '');
142144
inputElem.setAttribute('minlength', 2);
143145
inputElem.setAttribute('maxlength', 40);
144146

145147
return { form, results };
146148
}
149+
150+
#createElement(tagName, partAttr, attributes = []) {
151+
const elem = document.createElement(tagName);
152+
const part = typeof partAttr === 'undefined' || partAttr === -1 ? tagName : partAttr;
153+
if (partAttr !== false) {
154+
elem.setAttribute('part', part);
155+
}
156+
attributes.forEach(([attr, value]) => { elem.setAttribute(attr, value); });
157+
return elem;
158+
}
159+
160+
#markResults () {
161+
const boldElems = this.shadowRoot.querySelectorAll('b');
162+
boldElems.forEach((el) => { el.setAttribute('part', 'mark'); });
163+
}
147164
}
148165

149166
export default MySearchApiElement;

0 commit comments

Comments
 (0)