PageSourceSearch

https://memogames.com.ua/wp-content/themes/memo-theme/ajax/product-filter.js?ver=1.3.5

js memogames.com.ua collected 2026-10-06 18:43:40 UTC 18,909 bytes, 506 lines download raw bytes

1const checkImageLoading = () => {
2    document.querySelectorAll('.card-product_img')?.forEach(card => {
3        card.dataset.loadingInited = 'true';
4
5        const img = card.querySelector('img');
6        if (!img) return;
7
8        card.classList.add('loading');
9        img.addEventListener('load', () => {
10            card.classList.remove('loading');
11        });
12
13        if (img.complete && img.naturalWidth !== 0) {
14            card.classList.remove('loading');
15        }
16    });
17}
18
19jQuery(document).ready(function ($) {
20
21    /* PROUCT FILTER BLOCK */
22    let tabs = $('.s-products_content .tab-filter');
23    tabs.on('click', function (e) {
24        e.preventDefault();
25        let current_tab = this;
26        tabs.each(function () {
27            if (this !== current_tab) {
28                $(this).removeClass('active');
29            }
30        });
31        $(current_tab).addClass('active');
32        $('.tab-content').addClass('preload');
33        let tag_id = $(this).attr('data-tag-id');
34        let count = $(this).attr('data-count');
35        $.ajax({
36            url: '/wp-json/artilab_filter_products/v1/product-filter',
37            method: 'GET',
38            data: {
39                tag_id: tag_id,
40                count: count,
41            },
42            success: function (response) {
43                if (response) {
44                    $('.tab-content .s-products_wrap').html(response);
45                    checkImageLoading();
46                    $('.tab-content ').removeClass('preload');
47                }
48
49            },
50            error: function (err) {
51                console.error('Error:', err);
52            }
53        });
54
55    });
56
57    /* SHOP PAGE*/
58    if ((pageInfo.isShopPage || pageInfo.isProductCat) && !pageInfo.isSearch) {
59        const debouncedSendAjax = debounce(sendAjax, 300);
60        let inputs = $('.catalog-filter input');
61        let select_list = $('.select_list__item');
62        let ajaxurl = ajax_object.ajaxurl;
63        
64        // Apply filters from URL on page load
65        applyFiltersFromUrl();
66        
67        inputs.on('change input', function () {
68            debouncedSendAjax();
69        });
70        select_list.on('click', function () {
71            $('.select_title__text').data('type', $(this).data('type'));
72            sendAjax();
73        });
74        $('.input-min, .input-max').on('change', function () {
75            sendAjax();
76        });
77        updatePaginationLinks();
78        
79        // Handle pagination clicks
80        $(document).on('click', '.pagination_wrap a', function (e) {
81            e.preventDefault();
82            let href = $(this).attr('href');
83            if (!href || href === '#') return false;
84            
85            // Try to extract page number from different URL formats (query param or pretty path)
86            let pageNumber = 1;
87            try {
88                let url = new URL(href, window.location.origin);
89                if (url.searchParams.has('paged')) {
90                    pageNumber = parseInt(url.searchParams.get('paged'), 10) || 1;
91                } else if (url.searchParams.has('page')) {
92                    pageNumber = parseInt(url.searchParams.get('page'), 10) || 1;
93                } else {
94                    let pathMatch = url.pathname.match(/\/page\/(\d+)\/?/);
95                    pageNumber = pathMatch ? parseInt(pathMatch[1], 10) : 1;
96                }
97            } catch (err) {
98                // Fallback: regex on href string
99                let pageMatch = href.match(/(?:[?&]paged=|\/page\/)(\d+)/);
100                pageNumber = pageMatch ? parseInt(pageMatch[1], 10) : 1;
101            }
102            
103            sendAjax(pageNumber, true);
104            return false;
105        });
106    }
107
108    function applyFiltersFromUrl() {
109        const urlParams = new URLSearchParams(window.location.search);
110        const sortFromUrl = urlParams.get('filter_sort') || urlParams.get('sort') || 'popularity';
111        const filterSort = sortFromUrl === 'default' ? 'popularity' : sortFromUrl;
112
113        $('.select_title__text').data('type', filterSort);
114
115        const sortTexts = {
116            'lp': 'за зростанням ціни',
117            'hp': 'за спаданням ціни',
118            'date': 'за новизною',
119            'rating': 'за рейтингом',
120            'popularity': 'за популярністю', 
121            'default': 'за замовчуванням'   
122        };
123
124        if (sortTexts[filterSort]) {
125            $('.select_title__text').text(sortTexts[filterSort]);
126        }
127        // Apply categories filter
128        const filterCategories = urlParams.get('filter_categories');
129        if (filterCategories) {
130            const categories = filterCategories.split('_');
131            categories.forEach(cat => {
132                $('.catalog-filter .filter-categories input[value="' + cat + '"]').prop('checked', true);
133            });
134        }
135        
136        // Apply tags filter
137        const filterTags = urlParams.get('filter_tags');
138        if (filterTags) {
139            const tags = filterTags.split('_');
140            tags.forEach(tag => {
141                $('.catalog-filter .filter-tags input[value="' + tag + '"]').prop('checked', true);
142            });
143        }
144        
145        // Apply price range
146        const rangeMin = urlParams.get('range_min');
147        const rangeMax = urlParams.get('range_max');
148        if (rangeMin) {
149            $('.input-min').val(rangeMin);
150            $('.range-input .range-min').val(rangeMin);
151        }
152        if (rangeMax) {
153            $('.input-max').val(rangeMax);
154            $('.range-input .range-max').val(rangeMax);
155        }
156        
157        // Update range slider visually if exists
158        if (rangeMin || rangeMax) {
159            updateRangeSlider();
160        }
161
162        // Update filter clear button state
163        switchFiltering();
164    }
165    
166    function updateRangeSlider() {
167        const min = parseFloat($('.range-min').attr('min')) || 0;
168        const max = parseFloat($('.range-max').attr('max')) || 100;
169        const minVal = parseFloat($('.range-min').val()) || min;
170        const maxVal = parseFloat($('.range-max').val()) || max;
171        
172        const minPercent = ((minVal - min) / (max - min)) * 100;
173        const maxPercent = ((max - maxVal) / (max - min)) * 100;
174        
175        $('.slider .progress').css({
176            'left': minPercent + '%',
177            'right': maxPercent + '%'
178        });
179    }
180
181    function getFilterParams() {
182        let params = {};
183        let selectedSort = $('.select_title__text').data('type') || 'popularity';
184        selectedSort = selectedSort === 'default' ? 'popularity' : selectedSort;
185        let checked_cat = [];
186        $('.catalog-filter .filter-categories input[type="checkbox"]:checked').each(function () {
187            checked_cat.push($(this).val());
188        });
189
190        let checked_tags = [];
191        $('.catalog-filter .filter-tags input[type="checkbox"]:checked').each(function () {
192            checked_tags.push($(this).val());
193        });
194
195        params.categories = checked_cat;
196        params.tags = checked_tags;
197        params.range_min = $('.input-min').val();
198        params.range_max = $('.input-max').val();
199        params.sort = selectedSort;
200        return params;
201    }
202
203    function buildQueryString(params) {
204        const queryParts = [];
205
206        const keyOrder = ['categories', 'tags', 'range_min', 'range_max', 'sort'];
207
208        keyOrder.forEach(key => {
209            const value = params[key];
210
211            if (!value || (Array.isArray(value) && value.length === 0)) return;
212
213            if (Array.isArray(value)) {
214                // Use underscore-separated values to avoid conflicts
215                queryParts.push(`filter_${key}=${encodeURIComponent(value.join('_'))}`);
216            } else {
217                if (key === 'sort') {
218                    queryParts.push(`filter_${key}=${encodeURIComponent(value)}`);
219                } else {
220                    queryParts.push(`${key}=${encodeURIComponent(value)}`);
221                }
222            }
223        });
224
225        return queryParts.join('&');
226    }
227
228    function updatePaginationLinks() {
229        const queryString = buildQueryString(getFilterParams());
230        let currentPageUrl = null;
231        const baseHref = window.wooShopUrl || '/shop';
232        
233        $('.pagination_wrap a').each(function () {
234            let $link = $(this);
235            let href = $link.attr('href');
236            
237            if (!href || href === '#') return;
238            
239            // Parse page number and detect original style
240            let pageNumber = 1;
241            let originalHasPagedParam = false;
242            try {
243                let url = new URL(href, window.location.origin);
244                if (url.searchParams.has('paged')) {
245                    pageNumber = parseInt(url.searchParams.get('paged'), 10) || 1;
246                    originalHasPagedParam = true;
247                } else if (url.searchParams.has('page')) {
248                    pageNumber = parseInt(url.searchParams.get('page'), 10) || 1;
249                    originalHasPagedParam = true;
250                } else {
251                    let pathMatch = url.pathname.match(/\/page\/(\d+)\/?/);
252                    pageNumber = pathMatch ? parseInt(pathMatch[1], 10) : 1;
253                }
254            } catch (err) {
255                let pageMatch = href.match(/(?:[?&]paged=|\/page\/)(\d+)/);
256                pageNumber = pageMatch ? parseInt(pageMatch[1], 10) : 1;
257            }
258            
259            // Build new href preserving pagination style
260            let newHref = '';
261            const shopBase = (baseHref || (window.wooShopUrl || '/shop')).replace(/\/$/, '');
262            if (originalHasPagedParam) {
263                // Keep paged as query parameter
264                let urlBase = href.split('?')[0];
265                newHref = urlBase + (urlBase.indexOf('?') === -1 ? '?' : '&') + 'paged=' + pageNumber;
266                if (queryString) newHref += '&' + queryString;
267            } else {
268                // Use pretty /page/X/ structure
269                if (pageNumber > 1) {
270                    newHref = shopBase + '/page/' + pageNumber + '/';
271                } else {
272                    newHref = shopBase + '/';
273                }
274                if (queryString) newHref += '?' + queryString;
275            }
276
277            $link.attr('href', newHref);
278        });
279
280        // Update current page URL for browser history
281        const $currentSpan = $('.pagination_wrap span.current');
282        if ($currentSpan.length) {
283            const currentPageNumber = parseInt($currentSpan.text(), 10);
284            
285            // Remove trailing slash from baseHref to avoid double slash
286            const cleanBaseHref = baseHref.replace(/\/$/, '');
287            
288            if (currentPageNumber > 1) {
289                currentPageUrl = cleanBaseHref + '/page/' + currentPageNumber + '/';
290            } else {
291                currentPageUrl = cleanBaseHref + '/';
292            }
293            
294            if (queryString) {
295                currentPageUrl += '?' + queryString;
296            }
297        } else {
298            currentPageUrl = baseHref;
299            if (queryString) {
300                currentPageUrl += '?' + queryString;
301            }
302        }
303
304        return currentPageUrl;
305    }
306
307    function scrollToCatalogTop() {
308        const $target = $('.catalog_head').first();
309        if (!$target.length) return;
310
311        const headerOffset = $('.header_top').outerHeight() || 0;
312        const top = Math.max($target.offset().top - headerOffset - 20, 0);
313
314        $('html, body').stop(true).animate({ scrollTop: top }, 400);
315    }
316
317    function sendAjax(page = 1, scrollAfterLoad = false) {
318        let ajaxurl = ajax_object.ajaxurl;
319        let params = getFilterParams();
320        let nonce = ajax_object.nonce;
321        $('.catalog_main').addClass('preload');
322        $.ajax({
323            url: ajaxurl,
324            type: 'POST',
325            data: {
326                action: 'filter_products_adv',
327                categories: params,
328                paged: page,
329                security: nonce
330            },
331            success: function (response) {
332                $('.pag-box').html(response.data.pagination);
333                $('.catalog_main').html(response.data.products);
334                $('.title-page_count').html(response.data.count);
335                $('.title-page h1').text('Магазин');
336                switchFiltering();
337                updatePaginationLinks(); // Update pagination links with filter params
338                changeUrl();
339                checkImageLoading();
340                $('.breadcrumbs_list li:nth-child(3)').remove();
341                $('.catalog_main').removeClass('preload');
342
343                if (scrollAfterLoad) {
344                    scrollToCatalogTop();
345                }
346            }
347        });
348    }
349
350    /* Open cart PopUp */
351    // $('.open_cart_popup').on('click', function (e) {
352    //     e.preventDefault();
353    //     $('div.cc-compass').trigger('click');
354    // });
355    $('#no_phone_confirmation').on('change', function () {
356        if ($('#no_phone_confirmation').is(':checked')) {
357            $('#order_comments').val('Не телефонувати мені для підтвердження замовлення');
358        } else {
359            $('#order_comments').val('');
360        }
361    });
362
363    jQuery(function ($) {
364        const target = document.body;
365
366        const observer = new MutationObserver(function (mutationsList) {
367            mutationsList.forEach(function (mutation) {
368                mutation.addedNodes.forEach(function (node) {
369                    if (node.nodeType === 1 && node.classList.contains('added_to_cart')) {
370                        node.remove();
371                    }
372                });
373            });
374        });
375
376        observer.observe(target, {
377            childList: true,
378            subtree: true
379        });
380    });
381
382    $('.filter-clear').on('click', function () {
383        $('.catalog-filter input[type="checkbox"]').prop('checked', false);
384        $('.select_title__text').text('за замовчуванням').data('type', 'popularity');
385        $('.input-min').val(0);
386        $('.input-max').val($('.range-max').attr('max'));
387        $('.slider .progress').attr('style', "left: 0; right: 0");
388        $('.range-input .range-min').val(0);
389        $('.range-input .range-max').val($('.range-max').attr('max'));
390        sendAjax();
391    });
392
393    function changeUrl() {
394        const newUrl = updatePaginationLinks();
395        window.history.replaceState({}, document.title, newUrl);
396    }
397
398    function switchFiltering() {
399        let inputs = $('.catalog-filter input');
400        let is_checkbox = false;
401        let is_selected = false;
402        let range = false;
403
404        inputs.each(function (index, element) {
405            if ($(element).is(':checkbox') && $(element).is(':checked')) {
406                is_checkbox = true;
407            }
408        });
409        const selectedSort = $('.select_title__text').data('type');
410        if (selectedSort && selectedSort !== 'default' && selectedSort !== 'popularity') {
411            is_selected = true;
412        }
413
414        if (
415            parseFloat($('.input-min').val()) > 0 ||
416            parseFloat($('.input-max').val()) < parseFloat($('.range-max').attr('max'))
417        ) {
418            range = true;
419        }
420        if (is_checkbox || is_selected || range) {
421            $('.filter-clear').addClass('active');
422        } else {
423            $('.filter-clear').removeClass('active');
424        }
425    }
426
427    function debounce(func, wait) {
428        let timeout;
429        return function () {
430            clearTimeout(timeout);
431            timeout = setTimeout(() => func.apply(this), wait);
432        };
433    }
434
435    $(document).on('click', '.custom-ajax-add-to-cart', function (e) {
436        e.preventDefault();
437        var $button = $(this);
438        $button.addClass('loading').prop('disabled', true);
439        var product_id = $button.data('product_id');
440        var variation_id = $button.data('variation_id');
441        var quantity = $button.data('quantity') || 1;
442
443        var variation = {};
444        $.each($button[0].attributes, function () {
445            if (this.name.startsWith('data-attribute_')) {
446                var attrName = this.name.replace('data-', '');
447                variation[attrName] = this.value;
448            }
449        });
450
451        $.ajax({
452            type: 'POST',
453            url: wc_add_to_cart_params.ajax_url,
454            data: {
455                action: 'variation_add_to_cart',
456                product_id: product_id,
457                variation_id: variation_id,
458                quantity: quantity,
459                variation: variation,
460                security: woocommerce_params.ajax_nonce || pageInfo.nonce
461            },
462            success: function (response) {
463                if (response.success) {
464                    if (response.data && response.data.fragments) {
465                        $.each(response.data.fragments, function (key, value) {
466                            $(key).replaceWith(value);
467                        });
468                    }
469                    $button.removeClass('loading').addClass('added').prop('disabled', false);
470                    $(document.body).trigger('added_to_cart', [
471                        response.data.fragments,
472                        response.data.cart_hash,
473                        $button
474                    ]);
475                } else {
476                    $button.removeClass('loading').prop('disabled', false);
477                    if (response.data && response.data.message) {
478
479                        if (typeof wc_add_to_cart_params !== 'undefined' &&
480                            wc_add_to_cart_params.i18n_view_cart &&
481                            $('.woocommerce-error, .woocommerce-message').length === 0) {
482
483                            $('body').append('<div class="woocommerce-error">' + response.data.message + '</div>');
484                            $(document.body).trigger('wc_fragments_refreshed');
485                        } else {
486                            alert(response.data.message);
487                        }
488                    }
489                }
490            },
491            error: function (jqXHR, textStatus, errorThrown) {
492                $button.removeClass('loading').prop('disabled', false);
493                console.error('AJAX error:', textStatus, errorThrown);
494                alert('Помилка під час додавання товару до кошика: ' + errorThrown);
495            }
496        });
497    });
498
499    $(document).on('click', '.add_to_cart_button', function () {
500        if (typeof pageData !== 'undefined' && pageData && pageData.is_thankyoupage) {
501            setTimeout(function () {
502                $(document.body).trigger('added_to_cart');
503            }, 1000);
504        }
505    });
506});

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.