@@ -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
149166export default MySearchApiElement ;
0 commit comments