PageSourceSearch

https://www.paturnpike.com/MVC/Scripts/Autocomplete/jquery.swiftype.autocomplete.js?package=PATurnpike

js paturnpike.com collected 2026-09-24 09:28:38 UTC 28,062 bytes, 708 lines download raw bytes

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, '&amp;').replace(/"/g, '&quot;').replace(/'/g, '&#39;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
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.