1/** 2 * 3 * searchbar handler 4 * 5 * @constructor 6 */ 7 8class SearchboxViewMobile { 9 #template = null; 10 #searchDataObj = null; 11 #resultsAreOpen = null; 12 #requestToken = 0; 13 14 /** 15 * 16 * @param {object} searchDataObj 17 */ 18 constructor(searchDataObj) { 19 this.#searchDataObj = searchDataObj; 20 } 21 22 /** 23 * Init Events 24 * @returns {void} 25 */ 26 initEvents() { 27 this.#compile(); 28 this.#subscriptions(); 29 this.#initState(); 30 }; 31 /** 32 * subscriptions 33 * @returns {void} 34 */ 35 #subscriptions() { 36 const self = this; 37 events.subscribe('SearchboxView_menu_state_change', (data) => { 38 if (!data.fullScreen) self.#closeResultsDiv(); 39 }); 40 }; 41 /** 42 * Init State 43 * @returns {void} 44 */ 45 #initState() { 46 this.#clickOutside(); 47 let timer; 48 document.getElementsByClassName('search-mobile s')[0].addEventListener('input', async (e) => { 49 clearTimeout(timer); 50 timer = setTimeout(async () => { 51 this.#searchDataObj.searchTerm = e.target.value; 52 if (this.#searchDataObj.searchTerm.length > 1) { 53 document.body.classList.toggle('results-open', !this.#resultsAreOpen); 54 this.#resultsAreOpen = true; 55 const currentRequest = ++this.#requestToken; 56 this.#showLoading(); 57 const results = await AlgoliaView.search(this.#searchDataObj); 58 if (currentRequest !== this.#requestToken) return; // stale response, a newer search superseded it 59 this.#renderResults(results, this.#searchDataObj.searchTerm); 60 } else { 61 this.#requestToken++; 62 document.getElementsByClassName('algolia_results mobile')[0].style.display = 'none'; 63 } 64 }, 250); 65 }); 66 } 67 68 /** 69 * open full screen upon click 70 * @param {object} el 71 * @returns {void} 72 */ 73 static open(el) { 74 const container = document.getElementById('searchBoxContainer'); 75 if (container) { 76 container.style.zIndex = '99999'; 77 container.style.position = 'relative'; 78 container.classList.remove('hidden'); 79 container.classList.add('flex'); 80 } 81 82 document.body.classList.add('full-screen'); 83 el.focus(); 84 85 events.publish('SearchboxView_menu_state_change', { fullScreen: true }); 86 87 if (/iPad|iPhone|iPod/.test(navigator.userAgent)) { 88 const form = document.querySelector('.algolia-form'); 89 const input = document.querySelector('.search.s'); 90 91 Object.assign(form.style, { 92 position: 'fixed', 93 top: '0', 94 left: '0', 95 right: '0', 96 height: 'auto', 97 backgroundColor: '#fff', 98 zIndex: '9999' 99 }); 100 101 // Handle keyboard appearance using visualViewport 102 if (window.visualViewport) { 103 // Store handler in form property instead of window 104 form._viewportHandler = () => { 105 const offsetTop = window.visualViewport.offsetTop; 106 form.style.transform = `translateY(${offsetTop}px)`; 107 }; 108 109 // Add viewport event listeners 110 window.visualViewport.addEventListener('resize', form._viewportHandler); 111 window.visualViewport.addEventListener('scroll', form._viewportHandler); 112 } 113 114 // Initial focus 115 requestAnimationFrame(() => { 116 input.focus(); 117 }); 118 } 119 120 setTimeout(() => { 121 if (window.scrollY > 0) { 122 window.scrollTo({ top: 0 }); 123 } 124 }, 500); 125 } 126 127 static close(el) { 128 const container = document.getElementById('searchBoxContainer'); 129 container.classList.remove('flex'); 130 container.classList.add('hidden'); 131 132 document.body.classList.remove('full-screen'); 133 el.closest('form').querySelector('input[type=search]').value = ''; 134 135 if (/iPad|iPhone|iPod/.test(navigator.userAgent)) { 136 SearchboxViewMobile.cleanup(); 137 } 138 139 events.publish('SearchboxView_menu_state_change', { fullScreen: false }); 140 } 141 142 static cleanup() { 143 const form = document.querySelector('.algolia-form'); 144 if (!form) return; 145 146 document.documentElement.style.overflow = ''; 147 form.style.height = 'auto'; 148 form.style.position = ''; 149 150 // Clean up viewport event listeners
151 if (window.visualViewport && form._viewportHandler) { 152 window.visualViewport.removeEventListener('resize', form._viewportHandler); 153 window.visualViewport.removeEventListener('scroll', form._viewportHandler); 154 delete form._viewportHandler; 155 } 156 157 form.style.transform = ''; 158 } 159 160 /** 161 * hiding results-open css class - in use for default template 162 * @returns {void} 163 */ 164 #closeResultsDiv() { 165 document.body.classList.remove('results-open'); 166 const algoliaResults = document.querySelector('.algolia_results.mobile'); 167 if (algoliaResults) { 168 algoliaResults.style.display = 'none'; 169 } 170 } 171 172 /** 173 * Hide the algolia results box when clicking anywhere else 174 * @returns {void} 175 */ 176 #clickOutside() { 177 document.body.addEventListener("click", (e) => { 178 const algoliaResults = document.querySelector('.algolia_results.mobile'); 179 if (algoliaResults && !e.target.form && !algoliaResults.contains(e.target) && !e.target.input) { 180 algoliaResults.innerHTML = ''; 181 algoliaResults.style.display = 'none'; 182 } 183 }); 184 } 185 186 /** 187 * Analyze and render array 188 * @param {str} results 189 * @param {string} searchTerm 190 * @returns {arr} 191 */ 192 /** 193 * show a loading indicator while results are being fetched 194 * @returns {void} 195 */ 196 #showLoading() { 197 const algoliaResults = document.querySelector('.algolia_results.mobile'); 198 algoliaResults.innerHTML = '<div class="search-loading"><span class="search-loading__spinner"></span></div>'; 199 algoliaResults.style.display = 'block'; 200 } 201 202 #renderResults(results, searchTerm) { 203 const algoliaResults = document.querySelector('.algolia_results.mobile'); 204 algoliaResults.innerHTML = ''; 205 algoliaResults.style.display = 'block'; 206 207 let hasResults = false; 208 209 for (const [k, v] of Object.entries(results)) { 210 if (v.length > 0) { 211 hasResults = true; // Set to true if at least one result is found 212 213 const sectionName = this.#getSectionName(k); 214 this.#render('section', { 215 sectionName, 216 sectionNameCSSClass: k//SingleFunctionsView.slugify(k) 217 }); 218 219 v.forEach(result => { 220 if (['teams', 'stadiums', 'circuits', 'arenas', 'courts', 'venues', 'locations'].includes(k)) { 221 this.#render('teamOrStadium', { 222 title: result.title, 223 url: result.url 224 }); 225 } else if (['football', 'tennis', 'concert', 'motorsport', 'rugby', 'basketball', 'cricket', 'fighting', 'nfl'].includes(k)) { 226 this.#render('event', result); 227 } else if (k === 'seatingplans' || k === 'reviews') { 228 this.#renderOther(result, k); 229 } else { 230 this.#renderCategory(result, k); 231 } 232 }); 233 } 234 } 235 236 if (!hasResults) { 237 algoliaResults.innerHTML = ` 238 <div class="no-results-message"> 239 ${AlgoliaSearchJS.translations.no_results} 240 </div> 241 `; 242 } 243 244 this.#render('footer', { searchTerm }); 245 } 246 247 #getSectionName(key) { 248 let sectionName = key; 249 if (key.indexOf("_categories") !== -1) { 250 sectionName = key.split('_categories')[0]; 251 if (key.indexOf("concert") !== -1) { 252 sectionName = AlgoliaSearchJS.translations.artists; 253 } else { 254 if (sectionName === 'football') { 255 sectionName = AlgoliaSearchJS.translations.football + ' '; 256 } 257 sectionName += ' ' + AlgoliaSearchJS.translations.tournaments; 258 } 259 } else if (key.indexOf("concert") !== -1) { 260 sectionName = AlgoliaSearchJS.translations.concerts; 261 } else if (sectionName === 'seatingplans') { 262 sectionName = 'SEATING PLANS'; 263 } else { 264 if (typeof AlgoliaSearchJS.translations[sectionName.toLowerCase()] != 'undefined') { 265 sectionName = AlgoliaSearchJS.translations[sectionName.toLowerCase()]; 266 } 267 } 268 return sectionName; 269 } 270 271 #renderCategory(result, key) { 272 const viewAllEventsTranslation = AlgoliaSearchJS.translations.view_all_events; 273 this.#render('category', { title: result.title, url: result.url, viewAllEventsTranslation }); 274 } 275 276 #renderOther(result, key) { 277 this.#render('other', { title: result.title, url: result.url }); 278 } 279 280 /** 281 * compile handlebars 282 * @returns {void} 283 */ 284 #compile() { 285 const source = document.getElementById('algolia').innerHTML;//load full inventory handlebar 286 this.#template = Handlebars.compile(source); 287 }; 288 289 /** 290 * render results 291 * @returns {void} 292 */ 293 #render(template, data) { 294 document.getElementsByClassName('algolia_results mobile')[0].innerHTML += this.#template({ 295 event: template === 'event', 296 section: template === 'section', 297 teamOrStadium: template === 'teamOrStadium', 298 other: template === 'other', 299 category: template === 'category', 300 footer: template === 'footer', 301 data: data, 302 translations: AlgoliaSearchJS.translations 303 }); 304 }; 305};
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.