1(function ($) { 2 3const isLocal = window.location.hostname.startsWith("local."); 4const algoliaCfg = { 5 appId: isLocal ? '7YBEXRNILY' : 'ONZ5UYNDHI', 6 apiKey: isLocal ? '6b24cc8afd36cb371486fdfccad96169' : 'cd9958d5a487a75fc6eda5566a75e1b8', 7}; 8// console.log('AG', algoliaCfg); 9 10function debouncePromise(fn, time) { 11 let timerId = undefined; 12 return function debounced(...args) { 13 if (timerId) { 14 clearTimeout(timerId); 15 } 16 17 return new Promise((resolve) => { 18 timerId = setTimeout(() => resolve(fn(...args)), time); 19 }); 20 }; 21} 22 23const debounced = debouncePromise((items) => Promise.resolve(items), 100); 24 25function onResultClick(itemUrl) { 26 window.location.href = itemUrl; 27} 28 29window.setupAutocomplete = function(templatePath, isAlgoliaEnabled) { 30 $(document).ready(function () { 31 32 if (isAlgoliaEnabled) { 33 const searchClient = algoliasearch(algoliaCfg.appId, algoliaCfg.apiKey); 34 const algolia = window['@algolia/autocomplete-js']; 35 const minEventStartDate = Math.floor(Date.now() / 1000); 36 // const minEventStartDate = new Date('2024-09-01').getTime() / 1000; 37 38 window.algoliaAutocomplete = algolia.autocomplete({ 39 container: '#algolia-search', 40 detachedMediaQuery: 'none', // Disable detached mode 41 placeholder: 'Search for an Event, Team/Artist, or Venue...', 42 classNames: { 43 submitButton: 'far fa-magnifying-glass', 44 }, 45 getSources({ query }) { 46 let optionalWords = ""; 47 48 // We have a way to customize ranking using index Rules configured in Algolia dashboard (see 49 // https://dashboard.algolia.com/apps/ONZ5UYNDHI/rules/teams_v1). That said, the code-based approach 50 // shown below gives us much more control; leaving it here in case we need to use it in the future. 51 52 /* 53 // If user searches for "dallas" (and not "dallas cowboys") we want to artificially boost the 54 // ranking of the Dallas Cowboys. 55 const dallasRegex = /(?:^|\s+)dallas(?:$|\s+)/i; 56 if (dallasRegex.test(query) && !/dallas cowboys/i.test(query)) { 57 // Pretend user typed in "dallas cowboys" instead of just "dallas" 58 query = query.replace(dallasRegex, "dallas cowboys "); 59 // Make "cowboys" optional so that we still get results if user only types "dallas" 60 optionalWords += " cowboys"; 61 } 62 */ 63 64 return query.trim().length < 2 ? [] : debounced([{ 65 sourceId: 'teams', 66 getItemUrl({ item }) { 67 return `/all-suites/${item.permalink}?ref=search`; 68 }, 69 getItems() { 70 return algolia.getAlgoliaResults({ 71 searchClient, 72 queries: [ 73 { 74 indexName: 'teams_v1', 75 query, 76 params: { 77 optionalWords: optionalWords, 78 hitsPerPage: 5, 79 }, 80 }, 81 ], 82 }); 83 }, 84 templates: { 85 header() { 86 return 'Teams'; 87 }, 88 item({ item, components, html }) { 89 return html` 90 <div 91 class="search-result-row" 92 onClick=${(event) => { 93 onResultClick(`/all-suites/${item.permalink}?ref=search`); 94 }} 95 >
96 <span class="stadium_icon_sm stadium_icon_${item.league_id}"></span> 97 <a href="/all-suites/${item.permalink}?ref=search" class="search-result"> 98 ${item.name} 99 </a> 100 </div> 101 `; 102 }, 103 }, 104 }, { 105 sourceId: "stadiums", 106 getItemUrl({ item }) { 107 return `/all-suites/${item.permalink}?ref=search`; 108 }, 109 getItems() { 110 return algolia.getAlgoliaResults({ 111 searchClient, 112 queries: [ 113 { 114 indexName: 'stadiums_v1', 115 query, 116 params: { 117 hitsPerPage: 5 118 }, 119 }, 120 ], 121 }); 122 }, 123 templates: { 124 // See https://www.algolia.com/doc/ui-libraries/autocomplete/core-concepts/templates/#returning-html 125 header() { 126 return 'Venue'; 127 }, 128 item({ item, components, html }) { 129 return html` 130 <div 131 class="search-result-row" 132 onClick=${(event) => { 133 onResultClick(`/all-suites/${item.permalink}?ref=search`); 134 }} 135 > 136 <span class="stadium_icon_sm stadium_icon_0"></span> 137 <a href="/all-suites/${item.permalink}?ref=search" class="search-result"> 138 ${item.name} (${item.location}) 139 </a> 140 </div> 141 `; 142 }, 143 }, 144 }, { 145 sourceId: "events", 146 getItemUrl({ item }) { 147 return `${item.marketplace_v1_url}?ref=search`; 148 }, 149 getItems() { 150 return algolia.getAlgoliaResults({ 151 searchClient, 152 queries: [ 153 { 154 indexName: 'events_v1', 155 query, 156 params: { 157 hitsPerPage: 5, 158 // Ensure that filter out events that have already happened 159 filters: `date_unix_sec > ${minEventStartDate}`, 160 }, 161 }, 162 ], 163 }); 164 }, 165 templates: { 166 // See https://www.algolia.com/doc/ui-libraries/autocomplete/core-concepts/templates/#returning-html 167 header() { 168 return 'Events'; 169 }, 170 item({ item, components, html }) { 171 // console.log({ item, components }); 172 return html` 173 <div 174 class="search-result-row" 175 onClick=${(event) => { 176 onResultClick(`${item.marketplace_v1_url}?ref=search`); 177 }} 178 >
179 <span class="stadium_icon_sm event_icon"></span> 180 <a href="${item.marketplace_v1_url}?ref=search" class="search-result"> 181 ${item.event_name} / ${item.venue} / ${item.date_formatted} 182 </a> 183 </div> 184 `; 185 }, 186 }, 187 }, { 188 // The autocomplete library doesn't give us an easy way to add a footer that is shown 189 // independent of specific sources. If you add a footer for a specific source, then that footer 190 // is only visible if search results exist (or if you also display a "no results" message). The 191 // work-around we'll use is to create a source that always returns no results, and then add a 192 // footer to that source. 193 sourceId: "footer-only-no-results", 194 getItems() { 195 return Promise.resolve([]); 196 }, 197 templates: { 198 noResults({ html }) { 199 return html`<div style="display: none;"></div>`; 200 }, 201 footer({ html, state }) { 202 return html` 203 <div class="non-result see-all ui-menu-item"> 204 <a 205 class="ui-menu-item-wrapper" 206 href="/search-results/?term=${encodeURIComponent(state.query)}" 207 > 208 See All Results 209 </a> 210 </div> 211 `; 212 }, 213 }, 214 }]); 215 }, 216 // debug: true, // â ï¸ Only for local dev: keep search results pane open after blur 217 onSubmit({ state }) { 218 window.location.href = `/search-results/?term=${encodeURIComponent(state.query)}`; 219 }, 220 }); 221 222 // focus on search input on element load 223 $("#algolia-search input").focus(); 224 } else { 225 var searchApiPath = templatePath + "/search/search.php"; 226 var minLength = 2; 227 var searchInput = $("#all_suites_search"); 228 229 // focus on search input on element load 230 searchInput.focus(); 231 232 // on input hide the empty message if the chars in the field are less than minLength 233 searchInput.on("input", function (event) { 234 if ($(this).val().length < minLength) { 235 $("#search-empty-message").hide(); 236 $(".loading_small").css("display", "none"); 237 } else { 238 $(".loading_small").css("display", "block"); 239 } 240 }); 241 242 // autocomplete api 243 var autocompleteInstance = searchInput.autocomplete({ 244 source: searchApiPath, 245 appendTo: ".seg-search__input-wrapper", 246 focus: function (event, ui) { 247 $(event.currentTarget).find("li").css("background", "none") 248 $(event.currentTarget).find("a.ui-state-focus").parent().css("background", "#f1f7ff") 249 }, 250 select: function (event, ui) { 251 var toElement = event.originalEvent.toElement; 252 var elClicked = toElement ? toElement.localName : false; 253 254 if (ui.item.url.length && (event.key === "Enter" || elClicked === "li")) { 255 window.location = ui.item.url; 256 } 257 }, 258 create: function () { 259 $(this).data("ui-autocomplete")._renderItem = function (ul, item) { 260 var li = $("<li></li>"); 261 262 // if the item has a url, it is a result and gets an a tag inside 263 // if not it does not and the item will not be selectable via keyboard 264 if (item.url.length) { 265 var icon = "<span class='stadium_icon_sm stadium_icon_" + item.league + "'></span>"; 266 var a = $("<a></a>"); 267 268 a.html(icon + item.label); 269 a.attr("href", item.url); 270 li.html(a); 271 } else { 272 li.html(item.label); 273 } 274 275 li.addClass(item.class); 276 277 return li.appendTo(ul); 278 }; 279 }, 280 response: function (event, ui) { 281 // after search completed, before menu is shown, show empty results message if no results 282 $(".loading_small").css("display", "none"); 283 if (ui.content.length === 0) { 284 $("#search-empty-message").show(); 285 } else { 286 $("#search-empty-message").hide(); 287 } 288 }, 289 html: true, 290 minLength: minLength 291 }); 292 293 // override the close method which closes the menu when clicking a link 294 autocompleteInstance.data("uiAutocomplete").close = function (event) { 295 if (event && event.type !== "menuselect") { 296 $("ul.ui-autocomplete").hide(); 297 } 298 }; 299 300 // make sure search happens again upon refocusing on autocomplete 301 autocompleteInstance.focus(function () { 302 var val = $(this).val(); 303 if (val.length >= minLength) { 304 $(this).autocomplete("search", val); 305 } 306 }); 307 } 308 }); 309}; 310 311})(jQuery);
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.