PageSourceSearch

https://artdugout.ru/local/templates/artdugout/js/search.js

js artdugout.ru collected 2026-10-07 18:53:34 UTC 21,749 bytes, 701 lines download raw bytes

1document.addEventListener('DOMContentLoaded', function () {
2    const forms = document.querySelectorAll('header .jsSearchForm, .menu__search .jsSearchForm');
3    const header = document.querySelector('header.header');
4    const headerSearch = document.querySelector('.headerSearch');
5
6    const menuHeader = document.querySelector('.menu__header');
7    const menuSearch = document.querySelector('.menuSearch');
8
9    const closeButtons = document.querySelectorAll('.jsSearchClose');
10
11    let activeForm = null;
12    let activePanel = null;
13    let debounceTimer = null;
14    let currentXhr = null;
15    let currentItemsXhr = null;
16    let pointerDownInsideSearch = false;
17
18    function setHeaderHeightVar() {
19        if (!header) return;
20
21        document.documentElement.style.setProperty(
22            '--header-height',
23            header.offsetHeight + 'px'
24        );
25    }
26
27    function setMenuHeaderHeightVar() {
28        if (!menuHeader) return;
29
30        document.documentElement.style.setProperty(
31            '--menu-header-height',
32            menuHeader.offsetHeight + 'px'
33        );
34    }
35
36    function lockBodyScroll() {
37        document.body.classList.add('search-opened');
38    }
39
40    function unlockBodyScroll() {
41        document.body.classList.remove('search-opened');
42    }
43
44    function isSearchForm(form) {
45        return form && form.classList && form.classList.contains('jsSearchForm');
46    }
47
48    function isAnySearchOpened() {
49        return Array.from(forms).some(function (form) {
50            const row = form.querySelector('.jsSearchRow');
51            return row && row.classList.contains('is-open');
52        });
53    }
54
55    function getSearchContext(form) {
56        if (form.closest('.menu__search')) {
57            return {
58                panel: menuSearch,
59                loader: document.querySelector('.jsSearchLoaderMobile'),
60                itemsWrapper: menuSearch ? menuSearch.querySelector('.jsSearchItems') : null,
61                sectionsWrapper: menuSearch ? menuSearch.querySelector('.jsSearchSections') : null,
62                emptyBlock: menuSearch ? menuSearch.querySelector('.menuSearch__empty') : null,
63                popularBlock: menuSearch ? menuSearch.querySelector('.jsSearchPopular') : null,
64                resultsBlock: menuSearch ? menuSearch.querySelector('.jsSearchResults') : null,
65                moreLink: menuSearch ? menuSearch.querySelector('.menuSearch__more') : null,
66                totalCounter: menuSearch ? menuSearch.querySelector('.jsSearchTotal') : null,
67            };
68        }
69
70        return {
71            panel: headerSearch,
72            loader: document.querySelector('.jsSearchLoader'),
73            itemsWrapper: headerSearch ? headerSearch.querySelector('.jsSearchItems') : null,
74            sectionsWrapper: headerSearch ? headerSearch.querySelector('.jsSearchSections') : null,
75            emptyBlock: headerSearch ? headerSearch.querySelector('.headerSearch__empty') : null,
76            popularBlock: headerSearch ? headerSearch.querySelector('.jsSearchPopular') : null,
77            resultsBlock: headerSearch ? headerSearch.querySelector('.jsSearchResults') : null,
78            moreLink: headerSearch ? headerSearch.querySelector('.headerSearch__more') : null,
79            totalCounter: headerSearch ? headerSearch.querySelector('.jsSearchTotal') : null,
80        };
81    }
82
83    function showLoader(ctx) {
84        if (!ctx) return;
85
86        if (ctx.loader) ctx.loader.classList.add('is-visible');
87        if (ctx.popularBlock) ctx.popularBlock.style.display = 'none';
88        if (ctx.resultsBlock) ctx.resultsBlock.style.display = 'none';
89        if (ctx.emptyBlock) ctx.emptyBlock.style.display = 'none';
90    }
91
92    function showPopular(ctx) {
93        if (!ctx) return;
94
95        if (ctx.loader) ctx.loader.classList.remove('is-visible');
96        if (ctx.popularBlock) ctx.popularBlock.style.display = '';
97        if (ctx.resultsBlock) ctx.resultsBlock.style.display = 'none';
98        if (ctx.emptyBlock) ctx.emptyBlock.style.display = 'none';
99    }
100
101    function showEmpty(ctx) {
102        if (!ctx) return;
103
104        if (ctx.loader) ctx.loader.classList.remove('is-visible');
105        if (ctx.popularBlock) ctx.popularBlock.style.display = '';
106        if (ctx.resultsBlock) ctx.resultsBlock.style.display = 'none';
107        if (ctx.emptyBlock) ctx.emptyBlock.style.display = '';
108    }
109
110    function showResults(ctx) {
111        if (!ctx) return;
112
113        if (ctx.loader) ctx.loader.classList.remove('is-visible');
114        if (ctx.popularBlock) ctx.popularBlock.style.display = 'none';
115        if (ctx.resultsBlock) ctx.resultsBlock.style.display = '';
116        if (ctx.emptyBlock) ctx.emptyBlock.style.display = 'none';
117    }
118
119    function abortAjax() {
120        if (currentXhr) {
121            currentXhr.abort();
122            currentXhr = null;
123        }
124
125        if (currentItemsXhr) {
126            currentItemsXhr.abort();
127            currentItemsXhr = null;
128        }
129    }
130
131    function initSwiperForWrapper(wrapper) {
132        if (!wrapper || typeof Swiper === 'undefined') return;
133
134        var container = wrapper.closest('.jsProductSearchSlider');
135        if (!container) return;
136
137        var swiperEl = container.querySelector('.jsSliderInit');
138        if (!swiperEl) return;
139
140        if (swiperEl.swiper) {
141            swiperEl.swiper.destroy(true, true);
142        }
143
144        new Swiper(swiperEl, {
145            slidesPerView: 'auto',
146            spaceBetween: 8,
147            loop: false,
148            scrollbar: {
149                el: container.querySelector('.jsSliderScroll')
150            },
151            breakpoints: {
152                768: {
153                    spaceBetween: 16,
154                    slidesPerView: 3
155                },
156                991: {
157                    spaceBetween: 16,
158                    slidesPerView: 4
159                },
160                1200: {
161                    slidesPerView: 5,
162                    spaceBetween: 24
163                },
164            },
165        });
166    }
167
168    function resetSearchForm(form, options) {
169        if (!isSearchForm(form)) return;
170
171        const settings = Object.assign({
172            clearInput: true,
173            blurInput: false
174        }, options || {});
175
176        const row = form.querySelector('.jsSearchRow');
177        const input = form.querySelector('.jsSearchBlockInput');
178
179        if (input) {
180            if (settings.clearInput) {
181                input.value = '';
182            }
183
184            if (settings.blurInput && document.activeElement === input) {
185                input.blur();
186            }
187        }
188
189        if (row) {
190            row.classList.remove('is-open');
191            row.classList.remove('is-active');
192        }
193    }
194
195    function closeAllSearch(options) {
196        const settings = Object.assign({
197            clearInput: true,
198            blurInput: false
199        }, options || {});
200
201        if (!isAnySearchOpened() && !activeForm && !activePanel) {
202            return;
203        }
204
205        const desktopLoader = document.querySelector('.jsSearchLoader');
206        const mobileLoader = document.querySelector('.jsSearchLoaderMobile');
207
208        forms.forEach(function (form) {
209            resetSearchForm(form, settings);
210        });
211
212        if (headerSearch) {
213            headerSearch.classList.remove('is-open');
214        }
215
216        if (menuSearch) {
217            menuSearch.classList.remove('is-open');
218        }
219
220        if (desktopLoader) {
221            desktopLoader.classList.remove('is-visible');
222        }
223
224        if (mobileLoader) {
225            mobileLoader.classList.remove('is-visible');
226        }
227
228        unlockBodyScroll();
229
230        activeForm = null;
231        activePanel = null;
232        pointerDownInsideSearch = false;
233
234        abortAjax();
235        clearTimeout(debounceTimer);
236    }
237
238    function openSearch(form) {
239        const row = form.querySelector('.jsSearchRow');
240        const input = form.querySelector('.jsSearchBlockInput');
241
242        if (!row || !input) return;
243
244        const context = getSearchContext(form);
245        const isMenuSearch = !!form.closest('.menu__search');
246
247        closeAllSearch(true);
248
249        if (!isMenuSearch) {
250            document.dispatchEvent(new CustomEvent('app:search-open'));
251        }
252
253        activeForm = form;
254        activePanel = context.panel;
255
256        row.classList.add('is-open');
257
258        if (context.panel) {
259            context.panel.classList.add('is-open');
260        }
261
262        lockBodyScroll();
263        setHeaderHeightVar();
264        setMenuHeaderHeightVar();
265
266        showPopular(context);
267        input.focus();
268    }
269
270    function doSearch(form, query) {
271        if (!isSearchForm(form)) return;
272
273        var ctx = getSearchContext(form);
274
275        if (!query || query.length < 2) {
276            showPopular(ctx);
277            return;
278        }
279
280        showLoader(ctx);
281        abortAjax();
282
283        currentXhr = $.ajax({
284            url: '/ajax/search.php',
285            method: 'POST',
286            data: {
287                ajax_call: 'y',
288                q: query
289            },
290            dataType: 'json',
291            success: function (data) {
292                if (!activeForm || activeForm !== form) {
293                    return;
294                }
295
296                if (!data || !data.items || data.items.length === 0) {
297                    showEmpty(ctx);
298                    return;
299                }
300
301                if (ctx.totalCounter) {
302                    ctx.totalCounter.textContent = data.total || 0;
303                }
304
305                if (ctx.moreLink && data.search_url) {
306                    ctx.moreLink.href = data.search_url;
307                }
308
309                if (ctx.sectionsWrapper) {
310                    var tagsHtml = '';
311
312                    if (data.sections && data.sections.length > 0) {
313                        data.sections.forEach(function (s) {
314                            tagsHtml += '<a href="' + s.url + '">' + s.name + ' <span>' + s.count + '</span></a>';
315                        });
316                    }
317
318                    ctx.sectionsWrapper.innerHTML = tagsHtml;
319                }
320
321                if (data.ids && ctx.itemsWrapper) {
322                    currentItemsXhr = $.ajax({
323                        url: '/ajax/search_items.php',
324                        method: 'POST',
325                        data: {
326                            ids: data.ids
327                        },
328                        success: function (html) {
329                            if (!activeForm || activeForm !== form) {
330                                return;
331                            }
332
333                            $(ctx.itemsWrapper).html(html);
334                            initSwiperForWrapper(ctx.itemsWrapper);
335
336                            if (typeof BX !== 'undefined' && BX.Sale && BX.Sale.BasketComponent) {
337                                BX.Sale.BasketComponent.init();
338                            }
339
340                            showResults(ctx);
341                        },
342                        error: function (xhr, status) {
343                            if (status === 'abort') return;
344                            showResults(ctx);
345                        }
346                    });
347                } else {
348                    showResults(ctx);
349                }
350            },
351            error: function (xhr, status) {
352                if (status === 'abort') return;
353                showPopular(ctx);
354            }
355        });
356    }
357
358    function initAnimatedPlaceholder(inputs, phrases) {
359        inputs = Array.from(inputs);
360
361        if (!inputs.length || !phrases.length) return;
362
363        let phraseIndex = 0;
364        let charIndex = 0;
365        let isDeleting = false;
366
367        function hasUserValue() {
368            return inputs.some(function (input) {
369                return input.value.trim() !== '';
370            });
371        }
372
373        function setPlaceholder(text) {
374            inputs.forEach(function (input) {
375                if (document.activeElement !== input && input.value.trim() === '') {
376                    input.setAttribute('placeholder', text);
377                }
378            });
379        }
380
381        function tick() {
382            if (isAnySearchOpened() || hasUserValue()) {
383                setTimeout(tick, 400);
384                return;
385            }
386
387            const currentPhrase = phrases[phraseIndex];
388            let currentText = '';
389
390            if (isDeleting) {
391                charIndex--;
392                currentText = currentPhrase.substring(0, charIndex);
393            } else {
394                charIndex++;
395                currentText = currentPhrase.substring(0, charIndex);
396            }
397
398            setPlaceholder(currentText);
399
400            let delay = isDeleting ? 45 : 90;
401
402            if (!isDeleting && charIndex === currentPhrase.length) {
403                delay = 1400;
404                isDeleting = true;
405            } else if (isDeleting && charIndex === 0) {
406                isDeleting = false;
407                phraseIndex = (phraseIndex + 1) % phrases.length;
408                delay = 350;
409            }
410
411            setTimeout(tick, delay);
412        }
413
414        inputs.forEach(function (input) {
415            input.addEventListener('focus', function () {
416                input.setAttribute('placeholder', 'Поиск по товарам');
417            });
418
419            input.addEventListener('blur', function () {
420                if (input.value.trim() === '' && !isAnySearchOpened()) {
421                    input.setAttribute('placeholder', '');
422                }
423            });
424
425            input.addEventListener('input', function () {
426                if (input.value.trim() !== '') {
427                    input.setAttribute('placeholder', 'Поиск по товарам');
428                }
429            });
430        });
431
432        tick();
433    }
434
435    setHeaderHeightVar();
436    setMenuHeaderHeightVar();
437
438    window.addEventListener('resize', function () {
439        setHeaderHeightVar();
440        setMenuHeaderHeightVar();
441    });
442
443    forms.forEach(function (form) {
444        const row = form.querySelector('.jsSearchRow');
445        const input = form.querySelector('.jsSearchBlockInput');
446        const submitBtn = form.querySelector('.search-block__button');
447        const clearBtn = form.querySelector('.jsSearchFormClear');
448
449        if (!row || !input) return;
450
451        input.addEventListener('input', function () {
452            clearTimeout(debounceTimer);
453
454            const query = input.value.trim();
455            const context = getSearchContext(form);
456
457            if (query.length >= 2) {
458                showLoader(context);
459            } else {
460                showPopular(context);
461            }
462
463            debounceTimer = setTimeout(function () {
464                doSearch(form, query);
465            }, 600);
466        });
467
468        if (clearBtn) {
469            clearBtn.addEventListener('click', function (e) {
470                e.preventDefault();
471                e.stopPropagation();
472
473                input.value = '';
474                input.focus();
475
476                abortAjax();
477                clearTimeout(debounceTimer);
478
479                showPopular(getSearchContext(form));
480            });
481        }
482
483        row.addEventListener('click', function (e) {
484            if (!row.classList.contains('is-open')) {
485                e.preventDefault();
486                e.stopPropagation();
487                openSearch(form);
488            }
489        });
490
491        input.addEventListener('click', function (e) {
492            e.stopPropagation();
493        });
494
495        if (submitBtn) {
496            submitBtn.addEventListener('click', function (e) {
497                e.stopPropagation();
498
499                if (!row.classList.contains('is-open')) {
500                    e.preventDefault();
501                    openSearch(form);
502                    return;
503                }
504
505                if (input.value.trim() === '') {
506                    e.preventDefault();
507                    input.focus();
508                }
509            });
510        }
511    });
512
513    document.addEventListener('mousedown', function (e) {
514        if (!isAnySearchOpened()) {
515            pointerDownInsideSearch = false;
516            return;
517        }
518
519        const insideActiveForm = activeForm ? activeForm.contains(e.target) : false;
520        const insideActivePanel = activePanel ? activePanel.contains(e.target) : false;
521
522        pointerDownInsideSearch = insideActiveForm || insideActivePanel;
523    });
524
525    document.addEventListener('touchstart', function (e) {
526        if (!isAnySearchOpened()) {
527            pointerDownInsideSearch = false;
528            return;
529        }
530
531        const touch = e.touches && e.touches.length ? e.touches[0] : e;
532        const target = touch.target || e.target;
533
534        const insideActiveForm = activeForm ? activeForm.contains(target) : false;
535        const insideActivePanel = activePanel ? activePanel.contains(target) : false;
536
537        pointerDownInsideSearch = insideActiveForm || insideActivePanel;
538    }, { passive: true });
539
540    document.addEventListener('click', function (e) {
541        if (!isAnySearchOpened()) {
542            pointerDownInsideSearch = false;
543            return;
544        }
545
546        const clickedInsideActiveForm = activeForm ? activeForm.contains(e.target) : false;
547        const clickedInsideActivePanel = activePanel ? activePanel.contains(e.target) : false;
548
549        if (pointerDownInsideSearch) {
550            pointerDownInsideSearch = false;
551            return;
552        }
553
554        if (!clickedInsideActiveForm && !clickedInsideActivePanel) {
555            closeAllSearch({
556                clearInput: true,
557                blurInput: false
558            });
559        }
560    });
561
562    document.addEventListener('mouseup', function () {
563        setTimeout(function () {
564            pointerDownInsideSearch = false;
565        }, 0);
566    });
567
568    document.addEventListener('touchend', function () {
569        setTimeout(function () {
570            pointerDownInsideSearch = false;
571        }, 0);
572    }, { passive: true });
573
574    document.addEventListener('keydown', function (e) {
575        if (e.key === 'Escape') {
576            closeAllSearch({
577                clearInput: true,
578                blurInput: true
579            });
580        }
581    });
582
583    document.addEventListener('app:menu-open', function () {
584        closeAllSearch({
585            clearInput: true,
586            blurInput: false
587        });
588    });
589
590    closeButtons.forEach(function (btn) {
591        btn.addEventListener('click', function (e) {
592            e.preventDefault();
593            e.stopPropagation();
594
595            closeAllSearch({
596                clearInput: true,
597                blurInput: true
598            });
599        });
600    });
601
602    initAnimatedPlaceholder(
603        document.querySelectorAll('header .jsSearchBlockInput, .menu__search .jsSearchBlockInput'),
604        [
605            'Сырные корзины',
606            'Пармезан',
607            'Сыр с голубой плесенью',
608            'Подарочная корзина',
609            'Буррата',
610            'Трюфельный сыр'
611        ]
612    );
613
614    if (typeof Swiper !== 'undefined') {
615        $('.jsProductSearchSlider').each(function () {
616            var that = $(this);
617            var sliderEl = that.find('.jsSliderInit')[0];
618
619            if (!sliderEl) return;
620
621            new Swiper(sliderEl, {
622                slidesPerView: 'auto',
623                spaceBetween: 8,
624                loop: false,
625                scrollbar: {
626                    el: that.find('.jsSliderScroll')[0],
627                },
628                navigation: {
629                    nextEl: that.find('.jsSliderBtnNext')[0],
630                    prevEl: that.find('.jsSliderBtnPrev')[0],
631                },
632                breakpoints: {
633                    768: {
634                        spaceBetween: 16,
635                        slidesPerView: 3,
636                    },
637                    991: {
638                        spaceBetween: 16,
639                        slidesPerView: 4,
640                    },
641                    1200: {
642                        slidesPerView: 5,
643                        spaceBetween: 24
644                    },
645                },
646                on: {
647                    init: function () {
648                        if ($(window).width() > 1199 && this.slides.length <= 4) {
649                            that.find('.slider__btn').addClass('d-none');
650                        }
651                    },
652                },
653            });
654        });
655    }
656});
657
658// footer
659$(function () {
660    const $social = $('.footer__social');
661
662    function closeSocialItems($excludeItem) {
663        let $items = $social.find('.footer__social-item');
664
665        if ($excludeItem && $excludeItem.length) {
666            $items = $items.not($excludeItem);
667        }
668
669        $items.removeClass('is-open');
670        $items.children('.footer__social-itemContent:visible').stop(true, true).slide
670Up(200);
671    }
672
673    $social.on('click', '.footer__social-link', function (e) {
674        const $item = $(this).closest('.footer__social-item');
675        const $content = $item.children('.footer__social-itemContent');
676
677        if (!$content.length) return;
678
679        e.preventDefault();
680        e.stopPropagation();
681
682        if ($item.hasClass('is-open')) {
683            $item.removeClass('is-open');
684            $content.stop(true, true).slideUp(400);
685        } else {
686            closeSocialItems($item);
687            $item.addClass('is-open');
688            $content.stop(true, true).slideDown(400);
689        }
690    });
691
692    $social.on('click', '.footer__social-itemContent', function (e) {
693        e.stopPropagation();
694    });
695
696    $(document).on('click', function (e) {
697        if (!$(e.target).closest('.footer__social').length) {
698            closeSocialItems();
699        }
700    });
701});

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.