PageSourceSearch

https://www.ptglab.com/scripts/search.js?v=202604271

js ptglab.com collected 2026-09-24 19:22:41 UTC 14,711 bytes, 368 lines download raw bytes

1(() => {
2    const searchClass = '.search';
3    const searchWrapper = document.querySelector(searchClass);
4
5    // const containerClass = '.search__container';
6    // const searchBarClass = '.search__bar';
7    const inputClass = '.search__input';
8    const inputFieldClass = '.search__input__field';
9    const queryFieldClass = '.search__input__query';
10    const inputClearClass = '.search__clear';
11    const paramsClass = '.search__params';
12    const dropdownClass = '.search__dropdown';
13    const searchTermDropdownClass = '.search-category__dropdown';
14
15    const queryClass = '[data-search-field="query"]';
16    const targetClass = '[data-search-field="target"]';
17    const conjugateClass = '[data-search-field="conjugate"]';
18    const hostClass = '[data-search-field="host"]';
19    const speciesClass = '[data-search-field="species-reactivity"]';
20    const secondaryabSpeciesClass = '[data-search-field="secondaryab-species"]';
21    const secondaryabConjugateClass = '[data-search-field="secondaryab-conjugate"]';
22    const flexableCompatibleClass = '[data-search-field="compatible-species"]';
23    const flexableConjugateClass = '[data-search-field="flexable-conjugate"]';
24
25    const mobileFilterOverlay = '.mobile-filter-overlay';
26    const mobileDropdown = '.search-category__mobile-dropdown';
27    const mobileCancelClass = '.search__cancel';
28
29    const mediaQuery = '(max-width: 767px)';
30    const mediaQueryList = window.matchMedia(mediaQuery);
31
32    const defaultPlaceholder = 'Search by target, catalog number or keyword';
33
34    const pageType = document.body.dataset.pagetype || null;
35
36    let typingTimeout = null;
37
38    // DEVS to udpate
39    const apiUrl = "/api/categoty";
40
41    const handleFilterParentClick = (e) => {
42
43        if (!e.currentTarget.classList.contains('search__input')) {
44            const isOpen = e.target.classList.contains('is-open');
45
46            closeAllFilters(conjugateClass);
47            closeAllFilters(hostClass);
48            closeAllFilters(speciesClass);
49            closeAllFilters(secondaryabSpeciesClass);
50            closeAllFilters(secondaryabConjugateClass);
51            closeAllFilters(flexableCompatibleClass);
52            closeAllFilters(flexableConjugateClass);
53
54            if (!isOpen) {
55                openFilter(e.currentTarget);
56
57                requestAnimationFrame(() => {
58                    addClickToClose(e.currentTarget);
59                })
60            }
61        }
62    }
63
64    const openFilter = el => el.classList.add('is-open');
65    const closeFilter = el => el.classList.remove('is-open');
66    const closeAllFilters = () => searchWrapper.querySelectorAll(dropdownClass).forEach(el => closeFilter(el));
67
68    const resetFilter = (el) => el.classList.remove(...['active', 'is-open', 'has-clear', 'has-count']);
69    const resetAllFilters = () => searchWrapper.querySelectorAll(paramsClass).forEach(filter => resetFilter(filter));
70
71    const showFilter = el => searchWrapper.querySelector(el).classList.add('active');
72    const hideFilter = className => searchWrapper.querySelectorAll(className).forEach(el => {
73        el.classList.remove('active'); // dont change
74        resetFilterCheckboxes(el);
75        udpateFilterCount(el)
76    });
77
78    const resetFilterCheckboxes = el => {
79        el.querySelectorAll('input[type="checkbox"]').forEach((item) => item.checked = false);
80    }
81
82    const clearInput = el => el.value = '';
83
84    const setPlaceholder = (text) => {
85        searchWrapper.querySelectorAll(queryFieldClass).forEach(input => input.setAttribute('placeholder', text));
86    }
87
88    const populateResultsDropdown = (results) => {
89        const iconPath = '/img/icons/Icon_Search.svg'; // DEVS to udpate
90        const template = `
91            <div class="search__results__result">
92            <a href="[URL]">
93                <div class="search__results__icon">
94                    <img src="${iconPath}" width="14" />
95                </div>
96                <div class="search__results__text">
97                    [RESULT] <span class="ptg-green">in [CATEGORY]</span>
98                </div></a>
99            </div>`;
100
101        const resultsContainer = searchWrapper.querySelectorAll('.search__results');
102        let templateHtml = "", templateCopy;
103
104        results.data.map(resultItem => {
105            templateCopy = template;
106            templateCopy = templateCopy.replace('[RESULT]', resultItem.keyword)
107            templateCopy = templateCopy.replace('[CATEGORY]', resultItem.title)
108            templateCopy = templateCopy.replace('[URL]', resultItem.url)
109            templateHtml += templateCopy;
110        });
111
112        resultsContainer.forEach(input => input.innerHTML = templateHtml);
113        document.querySelectorAll(inputClass).forEach(parent => openFilter(parent));
114    }
115
116    const changeFilterCols = (paramNames) => {
117        hideFilter(targetClass);
118        hideFilter(conjugateClass);
119        hideFilter(hostClass);
120        hideFilter(speciesClass);
121        hideFilter(secondaryabSpeciesClass);
122        hideFilter(secondaryabConjugateClass);
123        hideFilter(flexableConjugateClass);
124        hideFilter(flexableCompatibleClass);
125        hideFilter(queryClass);
126
127        const paramString = paramNames.split(', ').map(name => `[data-search-field="${name}"]`).join(', ');
128        searchWrapper.querySelectorAll(paramString).forEach(param => param.classList.add('active'));
129    }
130
131    //  Add rounded border to search bar container
132    mediaQueryList.addEventListener('change', (e) => {
133        setTimeout(() => {
134            $(paramsClass).removeClass('rounded-search').filter(':visible').first().addClass('rounded-search');
135        }, 120);
136    });
137
138    const handleSearchTermMobileClick = (e) => {
139        if (mediaQueryList.matches) {
140            document.querySelector(mobileFilterOverlay).classList.add('active');
141            document.querySelector(mobileFilterOverlay).querySelector(`${queryClass} input`).focus();
142        }
143    }
144
145    const handleSearchTermKeyboardInput = (e) => {
146        const searchTerm = document.getElementById('searchTerm').innerText;
147        if (searchTerm != "All") {
148            return;
149        }
150        clearTimeout(typingTimeout);
151        typingTimeout = setTimeout(() => {
152
153            fetchAutocompleteResults(e.target);
154        }, 150);
155    };
156
157    const fetchAutocompleteResults = (el) => {
158        document.body.classList.add('busy');
159
160        const inputParent = el.closest(paramsClass);
161
162        // if (matchMedia.matches) {
163        //     const resultsContainer = document.querySelector('.search__results');
164        //     resultsContainer.innerHTML = 'Loading&hellip;';
165        // }
166
167        if (el.value.length > 0) {
168            inputParent && inputParent.classList.add('has-clear')
169
170            fetch(`${apiUrl}?keyword=${el.value}`)
171                .then(res => res.json())
172                .then(res => populateResultsDropdown(res))
173                .finally(() => document.body.classList.remove('busy'))
174        }
175        else {
176            document.body.classList.remove('busy');
177            inputParent.classList.remove('has-clear');
178            closeFilter(inputParent);
179        }
180    }
181
182    const handleMobileCancelButton = () => {
183        document.querySelector(mobileFilterOverlay).classList.remove('active');
184        searchWrapper.querySelector('form').reset();
185        resetAllFilters();
186
187        searchWrapper.querySelectorAll(queryClass).forEach((queryEl) => queryEl.classList.add('active'));
188    }
189
190    const addClickToClose = (el) => {
191
192        $(document).on('keyup.searchdd', function (e) {
193            if (e.which == 27) {
194                handleDropdownClose(el);
195            }
196        });
197
198        $(document).on('click.searchdd', function (e) {
199            if (!el.contains(e.target)) {
200                handleDropdownClose(el);
201            }
202        });
203    }
204
205    const handleDropdownClose = (el) => {
206        closeFilter(el);
207        udpateFilterCount(el);
208
209        $(document).off('.searchdd');
210    }
211
212    const handleClearSearchTerm = (e) => {
213        const seearchTermParent = e.target.closest(paramsClass);
214        seearchTermParent.classList.remove('is-open');
215        seearchTermParent.classList.remove('has-clear');
216
217        document.querySelectorAll(inputFieldClass).forEach(input => clearInput(input));
218    }
219
220    const handleSearchTermDropdownClick = (e) => {
221        const searchTermEl = e.currentTarget;
222        const categoryDropdownParent = searchTermEl.closest('.search__params');
223
224        // Remove all active class, then add active to chosen item
225        searchTermEl
226            .closest('.search__dropdown')
227            .querySelectorAll('span').forEach(el => el.classList.remove('active'));
228
229        searchTermEl.classList.add('active');
230
231        //Update visiblity on filter cols
232        changeFilterCols(searchTermEl.dataset.params)
233
234        document.getElementById('searchTerm').innerText = searchTermEl.title;
235        document.querySelector('input[name="category"]').value = searchTermEl.dataset.category;
236
237        requestAnimationFrame(() => {
238            closeFilter(categoryDropdownParent);
239
240            setPlaceholder(searchTermEl.dataset.placeholder || defaultPlaceholder)
241        })
242    }
243
244    const udpateFilterCount = (dropdown) => {
245        const countEl = dropdown.querySelector('.search-term-layout__count');
246        const checkedInputs = dropdown.querySelectorAll(`input[type="checkbox"]:checked`);
247
248        if (checkedInputs.length > 0) {
249            countEl.innerText = checkedInputs.length;
250            dropdown.classList.add('has-count');
251        }
252        else {
253            dropdown.classList.remove('has-count');
254        }
255    }
256
257    const handleFilterMenuConfirmButton = (e) => {
258        const dropdown = e.target.closest(paramsClass);
259
260        requestAnimationFrame(() => {
261            closeFilter(dropdown);
262            udpateFilterCount(dropdown)
263        });
264    }
265
266    const handleFilterMenuClearButton = (e) => {
267        const dropdown = e.target.closest(paramsClass);
268        dropdown.classList.remove('has-count');
269
270        const countEl = dropdown.querySelector('.search-term-layout__count');
271        countEl.innerText = 0;
272
273        resetFilterCheckboxes(dropdown)
274    }
275
276    const handleMobileCategoryChange = (e) => {
277        setPlaceholder(e.target[e.target.selectedIndex].dataset.placeholder || defaultPlaceholder);
278        searchWrapper.querySelector(`input[name='category']`).value = e.target.value; //updates hidden input with val
279
280        //changeFilterCols(e.target[e.target.selectedIndex].dataset.params)
281
282        //const submitBtn = searchWrapper.querySelector('.mobile-filter-overlay__submit');
283        //if (e.target[e.target.selectedIndex].dataset.params === 'query') {
284        //    submitBtn.classList.remove('active')
285        //}
286        //else {
287        //    submitBtn.classList.add('active')
288        //}
289    }
290
291    const checkPageType = () => {
292        if (pageType) {
293            if (mediaQueryList.matches) {
294                $(`${mobileDropdown} [data-landing="${pageType}"]`).trigger('click');
295            }
296            else {
297                $(`.search [data-landing="${pageType}"]`).trigger('click');
298            }
299        }
300    }
301
302    $(searchClass).on('click', '.search__dropdown__actions__clear', handleFilterMenuClearButton);
303    $(searchClass).on('click', '.search__dropdown__actions__confirm', handleFilterMenuConfirmButton);
304
305    $(searchClass).on('click', paramsClass, handleFilterParentClick)
306    $(mobileCancelClass).on('click', handleMobileCancelButton);
307
308    $(`${mobileDropdown} select`).on('change', handleMobileCategoryChange)
309
310    $(searchTermDropdownClass).on('click', 'span', handleSearchTermDropdownClick);
311    $(inputClearClass).on('click', handleClearSearchTerm);
312
313    $(queryFieldClass).on('keydown', handleSearchTermKeyboardInput);
314    //$(queryFieldClass).on('click', handleSearchTermMobileClick);//СÆÁĻʱËÑË÷
315
316    window.addEventListener('DOMContentLoaded', checkPageType);
317
318    var _lastRealInteraction = 0;
319    $(document).on('mousedown keydown touchstart', function(e) {
320        if (e.originalEvent && e.originalEvent.isTrusted) {
321            _lastRealInteraction = Date.now();
322        }
323    });
324$('form[action="/results"]').on('submit', function(e) {
325        e.preventDefault();
326        var isTrusted = e.originalEvent && e.originalEvent.isTrusted;
327        var hasRecentInteraction = (Date.now() - _lastRealInteraction) < 300;
328        if (isTrusted && hasRecentInteraction) {
329            try { localStorage.setItem('isHumanClick', 'true'); } catch(ex) {}
330        } else {
331            try { localStorage.removeItem('isHumanClick'); } catch(ex) {}
332        }
333        this.submit();
334    });
335$(document).on('click', 'a.example-text', function(e) {
336            e.preventDefault();
337            var $this = $(this);
338            var targetHref = $this.attr('href');
339            
340            var isTrusted = e.originalEvent && e.originalEvent.isTrusted;
341            var hasRecentInteraction = (Date.now() - _lastRealInteraction) < 300;
342            
343            if (isTrusted && hasRecentInteraction) {
344                try { localStorage.setItem('isHumanClick', 'true'); } catch(ex) {}
345            } else {
346                try { localStorage.removeItem('isHumanClick'); } catch(ex) {}
347            }
348            
349            window.location.href = targetHref;
350        });
351    const urlCategory = getRequest("category");
352    if (urlCategory !== undefined && urlCategory != null) {
353
354        const searchTermEl = document.querySelector(searchTermDropdownClass).querySelector('span[data-category="' + urlCategory + '"]');
355        const searchDropdownParent = searchTermEl.closest('.search__category');
356
357        handleDropdownClose(searchDropdownParent);
358
359        [...document.querySelector(searchTermDropdownClass).querySelectorAll('span')].map(el => el.classList.remove('active'));
360        document.getElementById('searchTerm').innerText = searchTermEl.title;
361        document.querySelector('input[name="category"]').value = searchTermEl.dataset.category;
362        searchTermEl.classList.add('active');
363
364        //updatePlaceholder(searchTermEl.dataset.placeholder || defaultPlaceholder)
365        //Update visiblity on filter cols
366        changeFilterCols(searchTermEl.dataset.params)
367    }
368})();

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.