1var observeDOM = (function () { 2 var MutationObserver = window.MutationObserver || window.WebKitMutationObserver; 3 4 return function (obj, callback) { 5 if (!obj || obj.nodeType !== 1) return; 6 7 if (MutationObserver) { 8 // define a new observer 9 var mutationObserver = new MutationObserver(callback) 10 11 // have the observer observe foo for changes in children 12 mutationObserver.observe(obj, { childList: true, subtree: true }) 13 return mutationObserver 14 } 15 16 // browser support fallback 17 else if (window.addEventListener) { 18 obj.addEventListener('DOMNodeInserted', callback, false) 19 obj.addEventListener('DOMNodeRemoved', callback, false) 20 } 21 } 22})() 23 24class JquerySwiftypeAutocomplete { 25 26 constructor(engineId) { 27 var engineId = engineId; 28 29 var queryParser = function (a) { 30 var i, p, b = {}; 31 if (a === "") { 32 return {}; 33 } 34 for (i = 0; i < a.length; i += 1) { 35 p = a[i].split('='); 36 if (p.length === 2) { 37 b[p[0]] = decodeURIComponent(p[1].replace(/\+/g, " ")); 38 } 39 } 40 return b; 41 } 42 $.queryParams = function () { 43 return queryParser(window.location.search.substr(1).split('&')); 44 }; 45 $.hashParams = function () { 46 return queryParser(window.location.hash.substr(1).split('&')); 47 }; 48 49 var ident = 0; 50 51 window.Swiftype = window.Swiftype || {}; 52 Swiftype.root_url = Swiftype.root_url || 'https://api.swiftype.com'; 53 Swiftype.pingUrl = function (endpoint, callback) { 54 var to = setTimeout(callback, 350); 55 var img = new Image(); 56 img.onload = img.onerror = function () { 57 clearTimeout(to); 58 callback(); 59 }; 60 img.src = endpoint; 61 return false; 62 }; 63 Swiftype.pingAutoSelection = function (engineKey, docId, value, callback) { 64 var params = { 65 t: new Date().getTime(), 66 engine_key: engineKey, 67 doc_id: docId, 68 prefix: value 69 }; 70 var url = Swiftype.root_url + '/api/v1/public/analytics/pas?' + $.param(params); 71 Swiftype.pingUrl(url, callback); 72 }; 73 Swiftype.findSelectedSection = function () { 74 var sectionText = $.hashParams().sts; 75 if (!sectionText) { return; } 76 77 function normalizeText(str) { 78 var out = str.replace(/\s+/g, ''); 79 out = out.toLowerCase(); 80 return out; 81 } 82 83 sectionText = normalizeText(sectionText); 84 85 $('h1, h2, h3, h4, h5, h6').each(function (idx) { 86 $this = $(this); 87 if (normalizeText($this.text()).indexOf(sectionText) >= 0) { 88 this.scrollIntoView(true); 89 return false; 90 } 91 }); 92 }; 93 94 Swiftype.htmlEscape = Swiftype.htmlEscape || function htmlEscape(str) { 95 return String(str).replace(/&/g, '&').replace(/"/g, '"').replace(/'/g, ''').replace(/</g, '<').replace(/>/g, '>'); 96 }; 97 98 $.fn.swiftype = function (options) { 99 Swiftype.findSelectedSection(); 100 var options = $.extend({}, $.fn.swiftype.defaults, options); 101 102 return this.each(function () { 103 var $this = $(this); 104 var config = $.meta ? $.extend({}, options, $this.data()) : options; 105 $this.attr('autocomplete', 'off'); 106 $this.data('swiftype-config-autocomplete', config); 107 $this.submitted = false; 108 $this.cache = new LRUCache(10); 109 $this.emptyQueries = []; 110 111 $this.isEmpty = function (query) { 112 return $.inArray(normalize(query), this.emptyQueries) >= 0 113 }; 114 115 $this.addEmpty = function (query) { 116 $this.emptyQueries.unshift(normalize(query)); 117 }; 118 119 var styles = config.dropdownStylesFunction($this);
120 var $swiftypeWidget = $('<div class="' + config.widgetContainerClass + '" />'); 121 var $listContainer = $('<div />').addClass(config.suggestionListClass).appendTo($swiftypeWidget).css(styles).hide(); 122 $swiftypeWidget.appendTo(config.autocompleteContainingElement); 123 var $list = $('<' + config.suggestionListType + ' />').appendTo($listContainer); 124 125 $this.data('swiftype-list', $list); 126 127 $this.abortCurrent = function () { 128 if ($this.currentRequest) { 129 $this.currentRequest.abort(); 130 } 131 }; 132 133 $this.showList = function () { 134 //---------------------------------------------------------------- 135 // CUSTOM PTC CODE: list is not shown when translating 136 //---------------------------------------------------------------- 137 var isTranslated = $("body").hasClass("-translated"); 138 if (isTranslated) { 139 return; 140 } 141 142 if (handleFunctionParam(config.disableAutocomplete) === false && $(window).width() > 576) { 143 $listContainer.show(); 144 } 145 }; 146 147 $this.hideList = function (sync) { 148 if (sync) { 149 $listContainer.hide(); 150 } else { 151 setTimeout(function () { $listContainer.hide(); }, 10); 152 } 153 }; 154 155 $this.showNoResults = function () { 156 $list.empty(); 157 if (config.noResultsMessage === undefined) { 158 $this.hideList(); 159 } else { 160 $list.append($('<li />', { 'class': config.noResultsClass }).text(config.noResultsMessage)); 161 $this.showList(); 162 } 163 }; 164 165 $this.focused = function () { 166 return $this.is(':focus'); 167 }; 168 169 $this.submitting = function () { 170 $this.submitted = true; 171 }; 172 173 $this.listResults = function () { 174 return $(config.resultListSelector, $list).filter(':not(.' + config.noResultsClass + ')'); 175 }; 176 177 $this.activeResult = function () { 178 return $this.listResults().filter('.' + config.activeItem
178Class).first(); 179 }; 180 181 $this.prevResult = function () { 182 var list = $this.listResults(), 183 currentIdx = list.index($this.activeResult()), 184 nextIdx = currentIdx - 1, 185 next = list.eq(nextIdx); 186 $this.listResults().removeClass(config.activeItemClass); 187 if (nextIdx >= 0) { 188 next.addClass(config.activeItemClass); 189 } 190 }; 191 192 $this.nextResult = function () { 193 var list = $this.listResults(), 194 currentIdx = list.index($this.activeResult()), 195 nextIdx = currentIdx + 1, 196 next = list.eq(nextIdx); 197 $this.listResults().removeClass(config.activeItemClass); 198 if (nextIdx >= 0) { 199 next.addClass(config.activeItemClass); 200 } 201 }; 202 203 $this.selectedCallback = function (data) { 204 return function () { 205 var value = $this.val(), 206 callback = function () { 207 config.onComplete(data, value); 208 }; 209 Swiftype.pingAutoSelection(config.engineKey, data['id'], value, callback); 210 }; 211 }; 212 213 $this.registerResult = function ($element, data) { 214 $element.data('swiftype-item', data); 215 $element.click($this.selectedCallback(data)).mouseover(function () { 216 $this.listResults().removeClass(config.activeItem
216Class); 217 $element.addClass(config.activeItemClass); 218 }); 219 }; 220 221 $this.getContext = function () { 222 return { 223 config: config, 224 list: $list, 225 registerResult: $this.registerResult 226 }; 227 }; 228 229 230 var typingDelayPointer; 231 var suppressKey = false; 232 $this.lastValue = ''; 233 $this.keyup(function (event) { 234 if (suppressKey) { 235 suppressKey = false; 236 return; 237 } 238 239 // ignore arrow keys, shift 240 if (((event.which > 36) && (event.which < 41)) || (event.which == 16)) return; 241 242 if (config.typingDelay > 0) { 243 clearTimeout(typingDelayPointer); 244 typingDelayPointer = setTimeout(function () { 245 processInput($this); 246 }, config.typingDelay); 247 } else { 248 processInput($this); 249 } 250 }); 251 252 $this.styleDropdown = function () { 253 $listContainer.css(config.dropdownStylesFunction($this)); 254 }; 255 256 $(window).resize(function (event) { 257 $this.styleDropdown(); 258 }); 259 260 $this.keydown(function (event) { 261 $this.styleDropdown(); 262 // enter = 13; up = 38; down = 40; esc = 27 263 var $active = $this.activeResult(); 264 switch (event.which) { 265 case 13: 266 if (($active.length !== 0) && ($list.is(':visible'))) { 267 event.preventDefault(); 268 $this.selectedCallback($active.data('swiftype-item'))(); 269 } else if ($this.currentRequest) { 270 $this.submitting(); 271 } 272 $this.hideList(); 273 suppressKey = true; 274 break; 275 case 38: 276 event.preventDefault(); 277 if ($active.length === 0) { 278 $this.listResults().last().addClass(config.activeItem
278Class); 279 } else { 280 $this.prevResult(); 281 } 282 break; 283 case 40: 284 event.preventDefault(); 285 if ($active.length === 0) { 286 $this.listResults().first().addClass(config.activeItemClass); 287 } else if ($active != $this.listResults().last()) { 288 $this.nextResult(); 289 } 290 break; 291 case 27: 292 $this.hideList(); 293 suppressKey = true; 294 break; 295 default: 296 $this.submitted = false; 297 break; 298 } 299 }); 300 301 // opera wants keypress rather than keydown to prevent the form submit 302 $this.keypress(function (event) { 303 if ((event.which == 13) && ($this.activeResult().length > 0)) { 304 event.preventDefault(); 305 } 306 }); 307 308 // stupid hack to get around loss of focus on mousedown 309 var mouseDown = false; 310 var blurWait = false; 311 $(document).bind('mousedown.swiftype' + ++ident, function () { 312 mouseDown = true; 313 }); 314 $(document).bind('mouseup.swiftype' + ident, function () { 315 mouseDown = false; 316 if (blurWait) { 317 blurWait = false; 318 $this.hideList(); 319 } 320 }); 321 $this.blur(function () { 322 if (mouseDown) { 323 blurWait = true; 324 } else { 325 $this.hideList(); 326 } 327 }); 328 $this.focus(function () { 329 setTimeout(function () { $this.select() }, 10); 330 if ($this.listResults().length > 0) { 331 $this.showList(); 332 } 333 }); 334 }); 335 }; 336 337 var normalize = function (str) { 338 return $.trim(str).toLowerCase(); 339 }; 340 341 var callRemote = function ($this, term) { 342 $this.abortCurrent(); 343 344 var params = {}, 345 config = $this.data('swiftype-config-autocomplete'); 346 347 if (term === "ezpass") { 348 term = "e-zpass"; 349 } 350 351 params['q'] = term; 352 params['engine_key'] = config.engineKey; 353 params['search_fields'] = handleFunctionParam(config.searchFields); 354 params['fetch_fields'] = handleFunctionParam(config.fetchFields); 355 params['filters'] = handleFunctionParam(config.filters); 356 params['document_types'] = handleFunctionParam(config.documentTypes); 357 params['functional_boosts'] = handleFunctionParam(config.functionalBoosts); 358 params['sort_field'] = handleFunctionParam(config.sortField); 359 params['sort_direction'] = handleFunctionParam(config.sortDirection); 360 params['per_page'] = config.resultLimit; 361 params['highlight_fields'] = config.highlightFields; 362 363 var endpoint = Swiftype.root_url + '/api/v1/public/engines/suggest.json'; 364 $this.currentRequest = $.ajax({ 365 type: 'GET', 366 dataType: 'jsonp', 367 url: endpoint, 368 data: params 369 }).done(function (data) { 370 var norm = normalize(term); 371 if (data.record_count > 0) { 372 $this.cache.put(norm, data.records); 373 } else { 374 $this.addEmpty(norm); 375 $this.showNoResults(); 376 return; 377 } 378 processData($this, data.records, term); 379 }); 380 }; 381 382 var getResults = function ($this, term) { 383 var norm = normalize(term); 384 if ($this.isEmpty(norm)) { 385 $this.showNoResults(); 386 return; 387 } 388 var cached = $this.cache.get(norm); 389 if (cached) { 390 processData($this, cached, term); 391 } else { 392 callRemote($this, term); 393 } 394 }; 395 396 // private helpers 397 var processInput = function ($this) { 398 var term = $this.val(); 399 if (term === $this.lastValue) { 400 return; 401 } 402 $this.lastValue = term; 403 if ($.trim(term) === '') { 404 $this.data('swiftype-list').empty() 405 $this.hideList(); 406 return; 407 } 408 if (typeof $this.data('swiftype-config-autocomplete').engineKey !== 'undefined') { 409 getResults($this, term); 410 } 411 }; 412 413 var processData = function ($this, data, term) { 414 var $list = $this.data('swiftype-list'), 415 config = $this.data('swiftype-config-autocomplete'); 416 417 $list.empty(); 418 $this.hideList(true); 419 420 config.resultRenderFunction($this.getContext(), data, term); 421 422 var totalItems = $this.listResults().length; 423 if ((totalItems > 0 && $this.focused()) || (config.noResultsMessage !== undefined)) { 424 if ($this.submitted) { 425 $this.submitted = false; 426 } else { 427 $this.showList(); 428 } 429 } 430 }; 431 432 var defaultResultRenderFunction = function (ctx, results) { 433 var $list = ctx.list, 434 config = ctx.config; 435 436 $.each(results, function (document_type, items) { 437 $.each(items, function (idx, item) { 438 //----------------------------------------------- 439 // CUSTOM PTC CODE: "notranslate" style added 440 //----------------------------------------------- 441 ctx.registerResult($('<li class="notranslate">' + config.renderFunction(document_type, item, idx) + '</li>').appendTo($list), item); 442 }); 443 }); 444 }; 445 446 var defaultRenderFunction = function (document_type, item, idx) { 447 if (item.specifictype === "document") { 448 return '<p class="title">' + Swiftype.htmlEscape(item['title']) + '</p><i class="fas fa-external-link-alt"></i>' 449 } 450 451 return '<p class="title">' + Swiftype.htmlEscape(item['title']) + '</p>'; 452 }; 453 454 var defaultOnComplete = function (item, prefix) { 455 var search = prefix; 456 457 switch (item.specifictype) { 458 case 'document': 459 window.open(item.siteurl, "_blank").focus(); 460 return; 461 case 'page': 462 window.location = location.protocol + "//" + location.host + item.siteurl; 463 return; 464 case 'faq': 465 window.location = location.protocol + "//" + location.host + `/help-center/faqs?q=${item.title}` 466 return; 467 default: 468 break; 469 } 470 471 var cleanedPrefix = prefix.replace("&", "%26"); 472 var search = encodeURI(cleanedPrefix); 473 // If the user clicked an item, and the item has a title, use that 474 // as the search term for the search page. 475 if (item.title != null) { 476 var cleanedTitle = item.title.replace("&", "%26"); 477 search = encodeURI(cleanedTitle); 478 } 479 480 if (window.location.href.includes("search")) { 481 window.location = window.location.href.split('search')[0] + `search?q=${search}`; 482 return; 483 } 484 goToSearchPage(search); 485 }; 486 487 var goToSearchPage = function (query) { 488 window.location = location.protocol + "//" + location.host + `/search?q=${query}`; 489 } 490 491 var defaultDropdownStylesFunction = function ($this) { 492 var config = $this.data('swiftype-config-autocomplete'); 493 var $attachEl = config.attachTo ? $(config.attachTo) : $this; 494 var offset = $attachEl.offset(); 495 var styles = { 496 'position': 'absolute', 497 'z-index': 9999, 498 'top': offset.top + $attachEl.outerHeight() + 1, 499 'left': offset.left 500 }; 501 if (config.setWidth) { 502 styles['width'] = $attachEl.outerWidth() - 2; 503 } 504 return styles; 505 }; 506 507 var handleFunctionParam = function (field) { 508 if (field !== undefined) { 509 var evald = field; 510 if (typeof evald === 'function') { 511 evald = evald.call(); 512 } 513 return evald; 514 } 515 return undefined; 516 }; 517 518 // simple client-side LRU Cache, based on https://github.com/rsms/js-lru 519 520 function LRUCache(limit) { 521 this.size = 0; 522 this.limit = limit; 523 this._keymap = {}; 524 } 525 526 LRUCache.prototype.put = function (key, value) { 527 var entry = { 528 key: key, 529 value: value 530 }; 531 this._keymap[key] = entry; 532 if (this.tail) { 533 this.tail.newer = entry; 534 entry.older = this.tail; 535 } else { 536 this.head = entry; 537 } 538 this.tail = entry; 539 if (this.size === this.limit) { 540 return this.shift(); 541 } else { 542 this.size++; 543 } 544 }; 545 546 LRUCache.prototype.shift = function () { 547 var entry = this.head; 548 if (entry) { 549 if (this.head.newer) { 550 this.head = this.head.newer; 551 this.head.older = undefined; 552 } else { 553 this.head = undefined; 554 } 555 entry.newer = entry.older = undefined; 556 delete this._keymap[entry.key]; 557 } 558 return entry; 559 }; 560 561 LRUCache.prototype.get = function (key, returnEntry) { 562 var entry = this._keymap[key]; 563 if (entry === undefined) return; 564 if (entry === this.tail) { 565 return entry.value; 566 } 567 if (entry.newer) { 568 if (entry === this.head) this.head = entry.newer; 569 entry.newer.older = entry.older; 570 } 571 if (entry.older) entry.older.newer = entry.newer; 572 entry.newer = undefined; 573 entry.older = this.tail; 574 if (this.tail) this.tail.newer = entry; 575 this.tail = entry; 576 return returnEntry ? entry : entry.value; 577 }; 578 579 LRUCache.prototype.remove = function (key) { 580 var entry = this._keymap[key]; 581 if (!entry) return; 582 delete this._keymap[entry.key]; 583 if (entry.newer && entry.older) { 584 entry.older.newer = entry.newer; 585 entry.newer.older = entry.older; 586 } else if (entry.newer) { 587 entry.newer.older = undefined; 588 this.head = entry.newer; 589 } else if (entry.older) { 590 entry.older.newer = undefined; 591 this.tail = entry.older; 592 } else { 593 this.head = this.tail = undefined; 594 } 595 596 this.size--; 597 return entry.value; 598 }; 599 600 LRUCache.prototype.clear = function () { 601 this.head = this.tail = undefined; 602 this.size = 0; 603 this._keymap = {}; 604 }; 605 606 if (typeof Object.keys === 'function') { 607 LRUCache.prototype.keys = function () { 608 return Object.keys(this._keymap); 609 }; 610 } else { 611 LRUCache.prototype.keys = function () { 612 var keys = []; 613 for (var k in this._keymap) keys.push(k); 614 return keys; 615 }; 616 } 617 618 const autocompleteContainingElement = "swiftype-auto-complete"; 619 const widgetContainerClass = "swiftype-widget"; 620 const suggestionListClass = "autocomplete"; 621 622 $.fn.swiftype.defaults = {
623 activeItemClass: 'active', 624 attachTo: undefined, 625 documentTypes: [ 626 "news-article", "page", "rfx", 627 "service-plaza", "faq", "document", 628 "contract-purchase-order" 629 ], 630 filters: { 631 "news-article": { 632 "categories": ["!constructionfilter"] 633 } 634 }, 635 engineKey: undefined, 636 searchFields: undefined, 637 functionalBoosts: undefined, 638 sortField: undefined, 639 sortDirection: undefined, 640 fetchFields: undefined, 641 highlightFields: undefined, 642 noResultsClass: 'noResults', 643 noResultsMessage: undefined, 644 onComplete: defaultOnComplete, 645 resultRenderFunction: defaultResultRenderFunction, 646 renderFunction: defaultRenderFunction, 647 dropdownStylesFunction: defaultDropdownStylesFunction, 648 resultLimit: undefined, 649 suggestionListType: 'ul', 650 suggestionListClass: suggestionListClass, 651 resultListSelector: 'li', 652 setWidth: true, 653 typingDelay: 80, 654 disableAutocomplete: false, 655 autocompleteContainingElement: `.${autocompleteContainingElement}`, 656 widgetContainerClass: widgetContainerClass 657 }; 658 659 $('#st-search-input').swiftype({ 660 engineKey: engineId, 661 searchFields: { 'all': ['title'] }, 662 sortField: { "all": "priority" }, 663 sortDirection: { "all": "asc" }, 664 resultLimit: 8 665 }); 666 667 $("#st-search-input").keyup(function (event) { 668 if (event.key === 'Enter' || event.keyCode === 13) { 669 var searchValue = $("#st-search-input").val(); 670 var cleanedSearchValue = searchValue.replace("&", "%26"); 671 672 if (window.location.href.includes("search")) { 673 window.location = window.location.href.split('search')[0] + `search?q=${encodeURI(cleanedSearchValue)}`; 674 return; 675 } 676 677 window.location = location.protocol + "//" + location.host + `/search?q=${encodeURI(cleanedSearchValue)}`; 678 } 679 }) 680 681 const $swiftypeResultsList = $(`.${autocompleteContainingElement} .${widgetContainerClass} .${suggestionListClass} ul`); 682 const seeAllResultsClass = "see-all-results"; 683 const $seeAllResults = $(`<li class="notranslate ${seeAllResultsClass}"><p class="title">See all results<i class="fas fa-arrow-right"></i></li>`); 684 // Observe the swiftype results list so the "see all results" link can be added to it when it's changed 685 observeDOM($swiftypeResultsList.get(0), function (m) { 686 if (m.length == 1 && 687 m[0].addedNodes.length == 1 && 688 m[0].addedNodes[0].classList.contains(seeAllResultsClass)) { 689 // the element being added is the see all results link... bail 690 return; 691 } 692 693 if ($("#st-search-input").val().length > 0) { 694 $seeAllResults.click(onClickSeeAllResults); 695 $seeAllResults.mouseover(onMouseOverSeeAllResults); 696 $swiftypeResultsList.append($seeAllResults); 697 } 698 }); 699 700 const onClickSeeAllResults = function () { 701 goToSearchPage($("#st-search-input").val()); 702 }; 703 const onMouseOverSeeAllResults = function () { 704 $swiftypeResultsList.find("li").removeClass("active"); 705 $seeAllResults.addClass("active"); 706 }; 707 } 708}
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.