@@ -54,10 +54,19 @@ export default class extends Controller {
5454 }
5555
5656 initialize ( ) {
57- // The endpoint for searching parts
57+ // The endpoint for searching parts or assemblies
5858 const base_url = this . element . dataset . autocomplete ;
5959 // The URL template for the part detail pages
6060 const part_detail_uri_template = this . element . dataset . detailUrl ;
61+ // The URL template for the assembly detail pages
62+ const assembly_detail_uri_template = this . element . dataset . assemblyDetailUrl ;
63+ // The URL template for the project detail pages
64+ const project_detail_uri_template = this . element . dataset . projectDetailUrl ;
65+
66+ const hasAssemblyDetailUrl =
67+ typeof assembly_detail_uri_template === "string" && assembly_detail_uri_template . length > 0 ;
68+ const hasProjectDetailUrl =
69+ typeof project_detail_uri_template === "string" && project_detail_uri_template . length > 0 ;
6170
6271 //The URL of the placeholder picture
6372 const placeholder_image = this . element . dataset . placeholderImage ;
@@ -72,6 +81,43 @@ export default class extends Controller {
7281 limit : 5 ,
7382 } ) ;
7483
84+ // Cache the last query to avoid fetching the same endpoint twice (parts source + assemblies source)
85+ let lastQuery = null ;
86+ let lastFetchPromise = null ;
87+
88+ const fetchMixedItems = ( query ) => {
89+ if ( query === lastQuery && lastFetchPromise ) {
90+ return lastFetchPromise ;
91+ }
92+
93+ lastQuery = query ;
94+
95+ const urlString = base_url . replace ( '__QUERY__' , encodeURIComponent ( query ) ) ;
96+ const url = new URL ( urlString , window . location . href ) ;
97+ if ( hasAssemblyDetailUrl || hasProjectDetailUrl ) {
98+ url . searchParams . set ( 'multidatasources' , '1' ) ;
99+ }
100+
101+ lastFetchPromise = fetch ( url . toString ( ) )
102+ . then ( ( response ) => response . json ( ) )
103+ . then ( ( items ) => {
104+ //Iterate over all fields besides the id and highlight them (if present)
105+ const fields = [ "name" , "description" , "category" , "footprint" ] ;
106+
107+ items . forEach ( ( item ) => {
108+ for ( const field of fields ) {
109+ if ( item [ field ] !== undefined && item [ field ] !== null ) {
110+ item [ field ] = that . _highlight ( item [ field ] , query ) ;
111+ }
112+ }
113+ } ) ;
114+
115+ return items ;
116+ } ) ;
117+
118+ return lastFetchPromise ;
119+ } ;
120+
75121 this . _autocomplete = autocomplete ( {
76122 container : this . element ,
77123 //Place the panel in the navbar, if the element is in navbar mode
@@ -102,7 +148,7 @@ export default class extends Controller {
102148 } ,
103149
104150 // If the form is submitted, forward the term to the form
105- onSubmit ( { state, event, ...setters } ) {
151+ onSubmit ( { state, event, ...setters } ) {
106152 //Put the current text into each target input field
107153 const input = that . inputTarget ;
108154
@@ -119,68 +165,146 @@ export default class extends Controller {
119165 input . form . requestSubmit ( ) ;
120166 } ,
121167
122-
123168 getSources ( { query } ) {
124- return [
125- // The parts source
169+ const sources = [
170+ // Parts source (filtered from mixed endpoint results)
126171 {
127172 sourceId : 'parts' ,
128173 getItems ( ) {
129- const url = base_url . replace ( '__QUERY__' , encodeURIComponent ( query ) ) ;
130-
131- const data = fetch ( url )
132- . then ( ( response ) => response . json ( ) )
133- ;
134-
135- //Iterate over all fields besides the id and highlight them
136- const fields = [ "name" , "description" , "category" , "footprint" ] ;
137-
138- data . then ( ( items ) => {
139- items . forEach ( ( item ) => {
140- for ( const field of fields ) {
141- item [ field ] = that . _highlight ( item [ field ] , query ) ;
142- }
143- } ) ;
144- } ) ;
145-
146- return data ;
174+ return fetchMixedItems ( query ) . then ( ( items ) =>
175+ items . filter ( ( item ) => item . type !== "assembly" )
176+ ) ;
147177 } ,
148178 getItemUrl ( { item } ) {
149179 return part_detail_uri_template . replace ( '__ID__' , item . id ) ;
150180 } ,
151181 templates : {
152182 header ( { html } ) {
153183 return html `< span class ="aa-SourceHeaderTitle "> ${ trans ( "part.labelp" ) } </ span >
154- < div class ="aa-SourceHeaderLine " /> ` ;
184+ < div class ="aa-SourceHeaderLine " /> ` ;
155185 } ,
156- item ( { item, components, html} ) {
186+ item ( { item, components, html } ) {
157187 const details_url = part_detail_uri_template . replace ( '__ID__' , item . id ) ;
158188
159189 return html `
160- < a class ="aa-ItemLink " href ="${ details_url } ">
161- < div class ="aa-ItemContent ">
162- < div class ="aa-ItemIcon aa-ItemIcon--picture aa-ItemIcon--alignTop ">
163- < img src ="${ item . image !== "" ? item . image : placeholder_image } " alt ="${ item . name } " width ="30 " height ="30 "/>
164- </ div >
165- < div class ="aa-ItemContentBody ">
166- < div class ="aa-ItemContentTitle ">
167- < b >
168- ${ components . Highlight ( { hit : item , attribute : 'name' } ) }
169- </ b >
190+ < a class ="aa-ItemLink " href ="${ details_url } ">
191+ < div class ="aa-ItemContent ">
192+ < div class ="aa-ItemIcon aa-ItemIcon--picture aa-ItemIcon--alignTop ">
193+ < img src ="${ item . image !== "" ? item . image : placeholder_image } " alt ="${ item . name } " width ="30 " height ="30 "/>
170194 </ div >
171- < div class ="aa-ItemContentDescription ">
172- ${ components . Highlight ( { hit : item , attribute : 'description' } ) }
173- ${ item . category ? html `< p class ="m-0 "> < span class ="fa-solid fa-tags fa-fw "> </ span > ${ components . Highlight ( { hit : item , attribute : 'category' } ) } </ p > ` : "" }
174- ${ item . footprint ? html `< p class ="m-0 "> < span class ="fa-solid fa-microchip fa-fw "> </ span > ${ components . Highlight ( { hit : item , attribute : 'footprint' } ) } </ p > ` : "" }
195+ < div class ="aa-ItemContentBody ">
196+ < div class ="aa-ItemContentTitle ">
197+ < b >
198+ ${ components . Highlight ( { hit : item , attribute : 'name' } ) }
199+ </ b >
200+ </ div >
201+ < div class ="aa-ItemContentDescription ">
202+ ${ components . Highlight ( { hit : item , attribute : 'description' } ) }
203+ ${ item . category ? html `< p class ="m-0 "> < span class ="fa-solid fa-tags fa-fw "> </ span > ${ components . Highlight ( { hit : item , attribute : 'category' } ) } </ p > ` : "" }
204+ ${ item . footprint ? html `< p class ="m-0 "> < span class ="fa-solid fa-microchip fa-fw "> </ span > ${ components . Highlight ( { hit : item , attribute : 'footprint' } ) } </ p > ` : "" }
205+ </ div >
175206 </ div >
176207 </ div >
177- </ div >
178- </ a >
179- ` ;
208+ </ a >
209+ ` ;
180210 } ,
181211 } ,
182212 } ,
183213 ] ;
214+
215+ if ( hasAssemblyDetailUrl ) {
216+ sources . push (
217+ // Assemblies source (filtered from the same mixed endpoint results)
218+ {
219+ sourceId : 'assemblies' ,
220+ getItems ( ) {
221+ return fetchMixedItems ( query ) . then ( ( items ) =>
222+ items . filter ( ( item ) => item . type === "assembly" )
223+ ) ;
224+ } ,
225+ getItemUrl ( { item } ) {
226+ return assembly_detail_uri_template . replace ( '__ID__' , item . id ) ;
227+ } ,
228+ templates : {
229+ header ( { html } ) {
230+ return html `< span class ="aa-SourceHeaderTitle "> ${ trans ( STATISTICS_ASSEMBLIES ) } </ span >
231+ < div class ="aa-SourceHeaderLine " /> ` ;
232+ } ,
233+ item ( { item, components, html } ) {
234+ const details_url = assembly_detail_uri_template . replace ( '__ID__' , item . id ) ;
235+
236+ return html `
237+ < a class ="aa-ItemLink " href ="${ details_url } ">
238+ < div class ="aa-ItemContent ">
239+ < div class ="aa-ItemIcon aa-ItemIcon--picture aa-ItemIcon--alignTop ">
240+ < img src ="${ item . image !== "" ? item . image : placeholder_image } " alt ="${ item . name } " width ="30 " height ="30 "/>
241+ </ div >
242+ < div class ="aa-ItemContentBody ">
243+ < div class ="aa-ItemContentTitle ">
244+ < b >
245+ ${ components . Highlight ( { hit : item , attribute : 'name' } ) }
246+ </ b >
247+ </ div >
248+ < div class ="aa-ItemContentDescription ">
249+ ${ components . Highlight ( { hit : item , attribute : 'description' } ) }
250+ </ div >
251+ </ div >
252+ </ div >
253+ </ a >
254+ ` ;
255+ } ,
256+ } ,
257+ }
258+ ) ;
259+ }
260+
261+ if ( hasProjectDetailUrl ) {
262+ sources . push (
263+ // Projects source (filtered from the same mixed endpoint results)
264+ {
265+ sourceId : 'projects' ,
266+ getItems ( ) {
267+ return fetchMixedItems ( query ) . then ( ( items ) =>
268+ items . filter ( ( item ) => item . type === "project" )
269+ ) ;
270+ } ,
271+ getItemUrl ( { item } ) {
272+ return project_detail_uri_template . replace ( '__ID__' , item . id ) ;
273+ } ,
274+ templates : {
275+ header ( { html } ) {
276+ return html `< span class ="aa-SourceHeaderTitle "> ${ trans ( STATISTICS_PROJECTS ) } </ span >
277+ < div class ="aa-SourceHeaderLine " /> ` ;
278+ } ,
279+ item ( { item, components, html } ) {
280+ const details_url = project_detail_uri_template . replace ( '__ID__' , item . id ) ;
281+
282+ return html `
283+ < a class ="aa-ItemLink " href ="${ details_url } ">
284+ < div class ="aa-ItemContent ">
285+ < div class ="aa-ItemIcon aa-ItemIcon--picture aa-ItemIcon--alignTop ">
286+ < img src ="${ item . image !== "" ? item . image : placeholder_image } " alt ="${ item . name } " width ="30 " height ="30 "/>
287+ </ div >
288+ < div class ="aa-ItemContentBody ">
289+ < div class ="aa-ItemContentTitle ">
290+ < b >
291+ ${ components . Highlight ( { hit : item , attribute : 'name' } ) }
292+ </ b >
293+ </ div >
294+ < div class ="aa-ItemContentDescription ">
295+ ${ components . Highlight ( { hit : item , attribute : 'description' } ) }
296+ </ div >
297+ </ div >
298+ </ div >
299+ </ a >
300+ ` ;
301+ } ,
302+ } ,
303+ }
304+ ) ;
305+ }
306+
307+ return sources ;
184308 } ,
185309 } ) ;
186310
@@ -192,6 +316,5 @@ export default class extends Controller {
192316 this . _autocomplete . setIsOpen ( false ) ;
193317 } ) ;
194318 }
195-
196319 }
197320}
0 commit comments