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.