PageSourceSearch

https://www.promotionproducts.com.au/templates/promotionproducts/assets/js/pages/colour-search/main.js

js promotionproducts.com.au collected 2026-10-03 23:56:55 UTC 12,530 bytes, 381 lines download raw bytes

1(function ($) {
2
3    var $picker         = $('#cs-native-picker');
4    var $hexInput       = $('#cs-hex-input');
5    var $btn            = $('#cs-search-btn');
6    var $spinner        = $('#cs-spinner');
7    var $grid           = $('#cs-product-grid');
8    var $countLine      = $('#cs-count-line');
9    var $matchInfo      = $('#cs-matched-info');
10    var $matchSwatch    = $('#cs-matched-swatch');
11    var $matchLabel     = $('#cs-matched-label');
12    var $searchBar      = $('.cs-search-bar');
13    var $categorySelect = $('#cs-category-select');
14    var $subcatList     = $('#cs-subcat-list');
15    var $clearFilters   = $('#cs-clear-filters');
16
17    var DEFAULT_HEX = '#FF0000';
18
19    var categoryTree = [];
20    try {
21        categoryTree = JSON.parse($('#cs-category-tree-data').text() || '[]');
22    } catch (e) {
23        categoryTree = [];
24    }
25
26    var state = { hex: '', categoryId: '', subcategoryId: '', page: 0 };
27    var shownCount  = 0;
28    var searchTimer = null;
29
30    // --- category tree lookups --------------------------------------------------
31
32    function findTopLevel(id) {
33        id = String(id);
34        for (var i = 0; i < categoryTree.length; i++) {
35            if (String(categoryTree[i].id) === id) return categoryTree[i];
36        }
37        return null;
38    }
39
40    function findParentOfSub(id) {
41        id = String(id);
42        for (var i = 0; i < categoryTree.length; i++) {
43            var children = categoryTree[i].children || [];
44            for (var j = 0; j < children.length; j++) {
45                if (String(children[j].id) === id) return categoryTree[i];
46            }
47        }
48        return null;
49    }
50
51    function renderSubcatButtons(parentId, selectedId) {
52        $subcatList.empty();
53        var top = parentId ? findTopLevel(parentId) : null;
54        if (!top || !top.children || !top.children.length) return;
55
56        top.children.forEach(function (c) {
57            var $sbtn = $('<button type="button" class="cs-subcat-btn"></button>')
58                .attr('data-id', c.id)
59                .text(c.name);
60            if (selectedId && String(selectedId) === String(c.id)) $sbtn.addClass('active');
61            $subcatList.append($sbtn);
62        });
63    }
64
65    // --- URL state ---------------------------------------------------------------
66
67    function syncUrl(push) {
68        var params = new URLSearchParams();
69        if (state.hex) params.set('hex', state.hex);
70
71        var catParam = state.subcategoryId || state.categoryId;
72        if (catParam) params.set('category_id', catParam);
73
74        var qs  = params.toString();
75        var url = location.pathname + (qs ? '?' + qs : '');
76
77        if (push) {
78            history.pushState(null, '', url);
79        } else {
80            history.replaceState(null, '', url);
81        }
82    }
83
84    // --- fetch a page of results ----------------------------------------------
85
86    function fetchPage(hex, categoryId, page) {
87        var isFirst = (page === 1);
88
89        if (isFirst) {
90            shownCount = 0;
91            $grid.empty();
92            $('#cs-loadmore-wrap').remove();
93            $countLine.empty();
94            $matchInfo.removeClass('visible');
95            $spinner.show();
96            $btn.prop('disabled', true);
97        } else {
98            $('#cs-loadmore-wrap').remove();
99        }
100
101        $.getJSON('/ajax.php', {
102            pp_function_name: 'colour_search_standalone',
103            hex:         hex,
104            category_id: categoryId || '',
105            page:        page
106        })
107        .done(function (res) {
108            $spinner.hide();
109            $btn.prop('disabled', false);
110
111            if (!res || res.code !== 1) {
112                if (isFirst) {
113                    $grid.html('<p class="text-muted text-center py-4">' + (res && res.message ? res.message : 'An error occurred. Please try again.') + '</p>');
114                }
115                return;
116            }
117
118            var d = res.data;
119            state.page = page;
120
121            if (isFirst) {
122                if (d.matched && d.matched.length) {
123                    $matchSwatch.css('background-color', '#' + d.bestHex);
124                    $matchLabel.text('Closest match: ' + d.matched.slice(0, 3).join(', '));
125                    $matchInfo.addClass('visible');
126                }
127                $countLine.html(d.productCount > 0
128                    ? '<span class="cs-count-num">' + d.productCount + '</span> product' + (d.productCount === 1 ? '' : 's') + ' found'
129                    : '');
130                $grid.html(d.html || '');
131            } else {
132                $grid.append(d.html || '');
133            }
134
135            shownCount += (d.renderedCount || 0);
136
137            if (d.hasMore) {
138                var $wrap    = $('<div id="cs-loadmore-wrap"></div>');
139                var $counter = $('<p id="cs-showing-count"></p>').text('Showing ' + shownCount + ' of ' + d.productCount + ' products');
140                var $loadBtn = $('<a class="btn btn-primary" id="cs-loadmore-btn">LOAD MORE</a>');
141                $wrap.append($counter).append($loadBtn);
142                $grid.after($wrap);
143                $loadBtn.on('click', function () {
144                    fetchPage(state.hex, (state.subcategoryId || state.categoryId || ''), state.page + 1);
145                });
146            }
147        })
148        .fail(function () {
149            $spinner.hide();
150            $btn.prop('disabled', false);
151            if (isFirst) {
152                $grid.html('<p class="text-muted text-center py-4">Could not connect. Please try again.</p>');
153            }
154        });
155    }
156
157    // --- apply current state: sync URL, fetch only once a colour is set --------
158
159    function applyFilters(push) {
160        syncUrl(push !== false);
161
162        if (/^[0-9A-F]{6}$/.test(state.hex)) {
163            state.page = 0;
164            fetchPage(state.hex, (state.subcategoryId || state.categoryId || ''), 1);
165        } else {
166            shownCount = 0;
167            $grid.empty();
168            $('#cs-loadmore-wrap').remove();
169            $countLine.empty();
170            $matchInfo.removeClass('visible');
171        }
172    }
173
174    // --- keep picker and text input in sync -----------------------------------
175
176    $picker.on('input', function () {
177        $hexInput.val($(this).val().toUpperCase());
178        $searchBar.removeClass('invalid');
179    });
180
181    $hexInput.on('input', function () {
182        var raw = $(this).val().trim();
183        var hex = raw.replace(/^#/, '');
184        if (/^[0-9A-Fa-f]{6}$/.test(hex)) {
185            $picker.val('#' + hex);
186            $searchBar.removeClass('invalid');
187        } else {
188            $searchBar.addClass('invalid');
189        }
190    });
191
192    // --- search (always starts at page 1) ------------------------------------
193
194    function doSearch() {
195        var raw = $hexInput.val().trim();
196        var hex = raw.replace(/^#/, '').toUpperCase();
197
198        if (!/^[0-9A-F]{6}$/.test(hex)) {
199            $searchBar.addClass('invalid');
200            $hexInput.focus();
201            return;
202        }
203
204        if (hex === state.hex) return;
205        state.hex = hex;
206
207        applyFilters(true);
208    }
209
210    $btn.on('click', function (e) {
211        e.preventDefault();
212        doSearch();
213    });
214
215    $hexInput.on('keydown', function (e) {
216        if (e.key === 'Enter') doSearch();
217    });
218
219    // auto-search while dragging the colour wheel or on close (debounced 600 ms)
220    $picker.on('input change', function () {
221        clearTimeout(searchTimer);
222        searchTimer = setTimeout(doSearch, 600);
223    });
224
225    // colour family preset dots
226    $(document).on('click', '.cs-preset-btn', function () {
227        var hex = $(this).data('hex');
228        $picker.val(hex);
229        $hexInput.val(hex.toUpperCase());
230        $searchBar.removeClass('invalid');
231        $('.cs-preset-btn').removeClass('active');
232        $(this).addClass('active');
233        state.hex = hex.replace(/^#/, '').toUpperCase();
234        applyFilters(true);
235    });
236
237    // --- product type filter: category / sub-category --------------------------
238
239    $categorySelect.on('change', function () {
240        state.categoryId    = $(this).val() || '';
241        state.subcategoryId = '';
242        renderSubcatButtons(state.categoryId, '');
243        applyFilters(true);
244    });
245
246    $(document).on('click', '.cs-subcat-btn', function () {
247        var id = $(this).attr('data-id');
248        var alreadyActive = $(this).hasClass('active');
249
250        $subcatList.find('.cs-subcat-btn').removeClass('active');
251
252        if (alreadyActive) {
253            state.subcategoryId = '';
254        } else {
255            $(this).addClass('active');
256            state.subcategoryId = id;
257        }
258
259        applyFilters(true);
260    });
261
262    $clearFilters.on('click', function (e) {
263        e.preventDefault();
264
265        state = { hex: '', categoryId: '', subcategoryId: '', page: 0 };
266
267        $picker.val(DEFAULT_HEX);
268        $hexInput.val(DEFAULT_HEX);
269        $searchBar.removeClass('invalid');
270        $('.cs-preset-btn').removeClass('active');
271        $categorySelect.val('');
272        $subcatList.empty();
273
274        shownCount = 0;
275        $grid.empty();
276        $('#cs-loadmore-wrap').remove();
277        $countLine.empty();
278        $matchInfo.removeClass('visible');
279
280        syncUrl(true);
281    });
282
283    // colour dot click → swap product image to that colour variant
284    $(document).on('click', '.btn-prod-color', function () {
285        var $btn       = $(this);
286        var parent     = $btn.closest('.item-product');
287        var product_id = $btn.attr('product');
288        var color_id   = $btn.data('id');
289
290        parent.find('.btn-prod-color.active').removeClass('active');
291        parent.find('.btn-prod-color.selected').removeClass('selected');
292        $btn.addClass('active');
293
294        $.get('/ajax.php', {
295            pp_function_name: 'subcat_products',
296            only_image:       true,
297            product_id:       product_id,
298            color:            color_id
299        }, function (result) {
300            if ($.trim(result) !== '') {
301                $('#imgid' + product_id).attr('src', result);
302            }
303        });
304    });
305
306    // --- init from URL (deep link / reload / bookmark) --------------------------
307
308    function applyStateToUi() {
309        if (state.categoryId) {
310            $categorySelect.val(state.categoryId);
311            renderSubcatButtons(state.categoryId, state.subcategoryId);
312        } else {
313            $categorySelect.val('');
314            $subcatList.empty();
315        }
316
317        if (state.hex) {
318            $picker.val('#' + state.hex);
319            $hexInput.val('#' + state.hex);
320            $('.cs-preset-btn').removeClass('active').each(function () {
321                var presetHex = String($(this).data('hex')).replace(/^#/, '').toUpperCase();
322                if (presetHex === state.hex) $(this).addClass('active');
323            });
324        } else {
325            $picker.val(DEFAULT_HEX);
326            $hexInput.val(DEFAULT_HEX);
327            $('.cs-preset-btn').removeClass('active');
328        }
329    }
330
331    function stateFromUrl() {
332        var params   = new URLSearchParams(location.search);
333        var hexParam = (params.get('hex') || '').replace(/^#/, '').toUpperCase();
334        var catParam = params.get('category_id') || '';
335
336        var next = { hex: '', categoryId: '', subcategoryId: '', page: 0 };
337
338        if (catParam) {
339            var top = findTopLevel(catParam);
340            if (top) {
341                next.categoryId = String(catParam);
342            } else {
343                var parent = findParentOfSub(catParam);
344                if (parent) {
345                    next.categoryId    = String(parent.id);
346                    next.subcategoryId = String(catParam);
347                } else {
348                    next.categoryId = String(catParam);
349                }
350            }
351        }
352
353        if (/^[0-9A-F]{6}$/.test(hexParam)) {
354            next.hex = hexParam;
355        }
356
357        return next;
358    }
359
360    window.addEventListener('popstate', function () {
361        state = stateFromUrl();
362        applyStateToUi();
363
364        if (state.hex) {
365            fetchPage(state.hex, (state.subcategoryId || state.categoryId || ''), 1);
366        } else {
367            shownCount = 0;
368            $grid.empty();
369            $('#cs-loadmore-wrap').remove();
370            $countLine.empty();
371            $matchInfo.removeClass('visible');
372        }
373    });
374
375    state = stateFromUrl();
376    applyStateToUi();
377    if (state.hex) {
378        fetchPage(state.hex, (state.subcategoryId || state.categoryId || ''), 1);
379    }
380
381}(jQuery));

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.