1import {SearchVars} from "./list-search-global-vars.js"; 2 3(function ($, Drupal) { 4 5 /** 6 * Parse formatted value using dot thousands separator. 7 * @param {string|number|null|undefined} value 8 * @returns {number|null} 9 */ 10 function parseThousandsDot(value) { 11 const digits = String(value ?? '').replace(/\D/g, ''); 12 return digits ? Number(digits) : null; 13 } 14 15 /** 16 * Collect noUiSlider slider configs 17 */ 18 function collectSliderConfigs() { 19 const filters = {}; 20 const sliderInputs = document.querySelectorAll('.slide-filter'); 21 22 sliderInputs.forEach(input => { 23 const name = input.getAttribute('name'); 24 if (!name) return; 25 26 const min = input.dataset.sliderMin; 27 const max = input.dataset.sliderMax; 28 const step = input.dataset.sliderStep; 29 const defaultValue = input.dataset.sliderDefaultValue; 30 31 if (!min || !max || !step || !defaultValue) return; 32 33 filters[name] = { 34 min: Number(min), 35 max: Number(max), 36 step: Number(step), 37 default: defaultValue.split(',').map(v => Number(v.trim())) 38 }; 39 }); 40 41 return filters; 42 } 43 44 /** 45 * init noUiSlider slider filter 46 * @param key 47 * @param conf 48 */ 49 function initSlider(key, conf) { 50 if (key === SearchVars.selected_slider_name 51 || key === `advanced-${SearchVars.selected_slider_name}`) return; 52 53 const sliderElement = document.getElementById(`slider-${key}`); 54 if (!sliderElement) return; 55 56 if (sliderElement.noUiSlider) { 57 sliderElement.noUiSlider.destroy(); 58 } 59 60 const inputMin = document.getElementById(`${key}-min`); 61 const inputMax = document.getElementById(`${key}-max`); 62 63 if (!inputMin || !inputMax) return; 64 65 const startValues = 66 conf.values?.Cash || conf.default || [conf.min, conf.max]; 67 68 noUiSlider.create(sliderElement, { 69 start: startValues, 70 connect: true, 71 step: conf.step, 72 range: { min: conf.min, max: conf.max }, 73 format: { 74 to: v => Math.round(v), 75 from: v => Number(v), 76 }, 77 }); 78 79 bindSliderEvents(sliderElement, key, conf, inputMin, inputMax); 80 initToggleButtons(sliderElement); 81 } 82 83 /** 84 * @param sliderElement 85 * @param key 86 * @param conf 87 * @param inputMin 88 * @param inputMax 89 */ 90 function bindSliderEvents(sliderElement, key, conf, inputMin, inputMax) { 91 const shouldFormatThousands = sliderElement.closest('[id$="-slide"], .filter-content, .filter-section') 92 ?.querySelector('.filter-data[data-thousand-format="1"]') !== null; 93 sliderElement.noUiSlider.on('update', (values, handle) => { 94 const inputs = [inputMin, inputMax]; 95 if (handle === 0 || handle === 1) { 96 const value = values[handle]; 97 inputs[handle].value = shouldFormatThousands ? $.fn.format_thousands_dot(value) : value; 98 } 99 }); 100 101 const handleBlur = (values, handle) => { 102 const type = handle === 0 ? 'min' : 'max'; 103 const filterInput = document.getElementById(`${type}_${key}`); 104 105 if (!filterInput) return; 106 107 filterInput.value = values[handle]; 108 filterInput.dispatchEvent(new Event('blur')); 109 110 if (SearchVars?.is_home_page) { 111 syncDuplicatedFilters(sliderElement); 112 } 113 }; 114 115 // Setting values from slider to inputs 116 sliderElement.noUiSlider.on('change', handleBlur); 117 118 // Setting values from inputs to slider 119 const bindInput = (input, handle) => { 120 input.addEventListener('input', () => { 121 if (!shouldFormatThousands) return; 122 const parsed = parseThousandsDot(input.value); 123 input.value = parsed === null ? '' : $.fn.format_thousands_dot(parsed); 124 }); 125 126 input.addEventListener('change', () => { 127 const parsed = shouldFormatThousands ? parseThousandsDot(input.value) : Number(input.value); 128 if (parsed === null || Number.isNaN(parsed)) return; 129 sliderElement.noUiSlider.set( 130 handle === 0 ? [parsed, null] : [null, parsed] 131 ); 132 }); 133 134 input.addEventListener('blur', () => { 135 if (shouldFormatThousands) { 136 const parsed = parseThousandsDot(input.value); 137 if (parsed !== null) input.value = $.fn.format_thousands_dot(parsed); 138 } 139 handleBlur(sliderElement.noUiSlider.get(), handle); 140 }); 141 142 // input blur on enter key press. 143 input.addEventListener('keydown', (e) => { 144 if (e.key === 'Enter') { 145 input.blur(); 146 } 147 }); 148 }; 149 150 bindInput(inputMin, 0); 151 bindInput(inputMax, 1); 152 } 153 154 /** 155 * @param sliderElement 156 */ 157 function initToggleButtons(sliderElement) { 158 159 const toggleButtons = sliderElement 160 .closest('.filter-section') 161 ?.querySelectorAll('.toggle-buttons .toggle'); 162 163 if (!toggleButtons?.length) return; 164 165 toggleButtons.forEach(btn => { 166 167 if (btn.dataset.listenerAttached) return; 168 btn.dataset.listenerAttached = 'true'; 169 170 const handleToggle = () => { 171 toggleButtons.forEach(b => b.classList.remove('active')); 172 btn.classList.add('active'); 173 174 const targetId = btn.dataset.target; 175 const target = document.getElementById(targetId); 176 if (!target) return; 177 178 target.classList.remove('hide'); 179 180 const parent = target.parentNode;
181 Array.from(parent.children).forEach(el => { 182 if (el !== target && !el.classList.contains('toggle-buttons')) { 183 el.classList.add('hide'); 184 } 185 }); 186 }; 187 188 btn.addEventListener('click', handleToggle); 189 btn._toggleHandler = handleToggle; 190 }); 191 } 192 193 /** 194 * @param sliderElement 195 */ 196 function syncDuplicatedFilters(sliderElement) { 197 198 const defaultFilter = sliderElement.dataset.defaultFilter; 199 if (!defaultFilter) return; 200 201 const values = sliderElement.noUiSlider.get().map(Number); 202 203 document 204 .querySelectorAll(`.double-slider[data-default-filter="${defaultFilter}"]`) 205 .forEach(el => { 206 if (el !== sliderElement) { 207 el.noUiSlider.set(values); 208 } 209 }); 210 } 211 212 /** 213 * @param context 214 */ 215 function runDoubleSlider(context = document) { 216 const filters = collectSliderConfigs(); 217 218 if (!Object.keys(filters).length) return; 219 220 Object.entries(filters).forEach(([key, conf]) => { 221 initSlider(key, conf); 222 }); 223 } 224 225 Drupal.doubleSliderInit = runDoubleSlider; 226 227 Drupal.behaviors.double_slider = { 228 attach(context) { 229 runDoubleSlider(context); 230 } 231 }; 232 233 // --- Toggle filter sections --- 234 const filter_headers = document.querySelectorAll('.filter-header'); 235 filter_headers.forEach(header => { 236 header.addEventListener('click', () => { 237 const filter_content = header.nextElementSibling; 238 header.classList.toggle('active'); 239 filter_content.classList.toggle('open'); 240 }); 241 }); 242 243 const subFilterWrappers = document.querySelectorAll('.sub-filters-wrapper'); 244 subFilterWrappers.forEach(wrapper => { 245 const header = wrapper.querySelector('.sub-filters-header'); 246 const content = wrapper.querySelector('.sub-filters-content'); 247 const wrapperId = wrapper.id; 248 249 if (!header || !content) return; 250 if (header.classList.contains('active')) { 251 content.classList.add('open'); 252 wrapper.style.borderBottom = 'none'; 253 } 254 255 header.addEventListener('click', () => { 256 const targetWrapper = document.getElementById(wrapperId); 257 const targetHeader = targetWrapper.querySelector('.sub-filters-header'); 258 const targetContent = targetWrapper.querySelector('.sub-filters-content'); 259 targetHeader.classList.toggle('active'); 260 targetContent.classList.toggle('open'); 261 if (targetHeader.classList.contains('active')) { 262 targetWrapper.style.borderBottom = 'none'; 263 } else { 264 targetWrapper.style.borderBottom = '1px solid #e0e0e0'; 265 } 266 }); 267 }); 268 269 // --- Toggle filters visibility --- 270 Drupal.behaviors.toggle_filters = { 271 attach: function (context) { 272 const toggle_btn = $('#toggle-filters', context); 273 const wrapper = $('#teaser-page-wrapper', context); 274 const filters_container = document.getElementById("filters-container"); 275 const modal_body = document.getElementById("modal-filters-body"); 276 const modal = document.getElementById("new-advanced-search"); 277 const close_btn = modal?.querySelector(".close"); 278 const see_result_btn = modal?.querySelector("#mobile-see-result-btn"); 279 280 /** 281 * Moves the filters container into the modal body. 282 * Used when screen width is less than 1199px. 283 */ 284 function moveFiltersToModal() { 285 if (!modal_body || !filters_container) return; 286 if (!modal_body.contains(filters_container)) { 287 modal_body.appendChild(filters_container); 288 } 289 } 290 291 /** 292 * Moves the filters container back to its original position in the page. 293 * Used when screen width is 1199px or more. 294 */ 295 function moveFiltersBack() { 296 if (!wrapper || !wrapper[0] || !filters_container) return; 297 const teaserPart = wrapper.find(".teaser-part").get(0); 298 if (!teaserPart) return; 299 wrapper[0].insertBefore(filters_container, teaserPart); 300 } 301 302 /** 303 * Opens the modal, moves filters into it, adds backdrop, 304 * and disables page scrolling. 305 */ 306 function openModal() { 307 moveFiltersToModal();
308 Drupal.ModalUtils.openModal(modal); 309 } 310 Drupal.t("psa.backoffice.list_search.set_filters"); 311 Drupal.t("psa.backoffice.list_search.hide_filter"); 312 // Toggle filters visibility or open modal depending on screen size 313 toggle_btn.off('click.toggleFilters').on('click.toggleFilters', function () { 314 if (window.innerWidth > 1199) { 315 wrapper.toggleClass('filters-hidden'); 316 const label = wrapper.hasClass('filters-hidden') ? 317 Drupal.t("psa.backoffice.list_search.set_filters") : Drupal.t("psa.backoffice.list_search.hide_filter"); 318 toggle_btn.find('.choices-list').text(label); 319 } else { 320 openModal(); 321 } 322 }); 323 324 // Close modal on close button click 325 function closeSearchModal(event) { 326 event.preventDefault(); 327 Drupal.ModalUtils.closeModal(modal); 328 } 329 330 close_btn?.addEventListener("click", function (e) { 331 closeSearchModal(e); 332 }); 333 334 see_result_btn?.addEventListener("click", function (e) { 335 closeSearchModal(e); 336 }); 337 338 /** 339 * Handles screen resize: moves filters to modal or back to page 340 * depending on current screen width. 341 */ 342 function handleResize() { 343 if (window.innerWidth <= 1199) { 344 moveFiltersToModal(); 345 Drupal.t("psa.backoffice.list_search.set_filter_mobile"); 346 toggle_btn.find('.choices-list').text(Drupal.t("psa.backoffice.list_search.set_filter_mobile")); 347 } else { 348 moveFiltersBack(); 349 const label = wrapper.hasClass('filters-hidden') ? 350 Drupal.t("psa.backoffice.list_search.set_filters") : Drupal.t("psa.backoffice.list_search.hide_filter") ; 351 toggle_btn.find('.choices-list').text(label); 352 if (modal?.classList.contains('show')) { 353 Drupal.ModalUtils.closeModal(modal); 354 } 355 } 356 } 357 358 // Listen to window resize and initialize layout 359 window.removeEventListener("resize", handleResize); 360 window.addEventListener("resize", handleResize); 361 handleResize(); 362 } 363 }; 364 365 // --- Color filter behavior --- 366 Drupal.behaviors.color_filter = { 367 attach: function (context) { 368 $('.filter-section', context).each(function () { 369 const $section = $(this); 370 const $color_options = $section.find('.color-option'); 371 const $title = $section.find('.filter-title'); 372 373 if (!$color_options.length || !$title.length) return; 374 const initial_selected = $color_options.filter('.selected').length; 375 const base_text = $title.text().replace(/\s*\(\d+\)$/, ''); 376 if (initial_selected > 0) { 377 $title.text(`${base_text} (${initial_selected})`); 378 } 379 $color_options.off('click.colorFilter').on('click.colorFilter', function () { 380 const $option = $(this); 381 $option.toggleClass('selected'); 382 const selected_count = $section.find('.color-option.selected').length; 383 const base_text = $title.text().replace(/\s*\(\d+\)$/, ''); 384 if (selected_count > 0) { 385 $title.text(`${base_text} (${selected_count})`); 386 } else { 387 $title.text(base_text); 388 } 389 }); 390 }); 391 } 392 }; 393 394 // --- Brand filter counter --- 395 Drupal.behaviors.brand_filter = { 396 attach: function (context) { 397 $('.filter-section', context).each(function () { 398 const $section = $(this); 399 const $check_boxes = $section.find('input[type="checkbox"]'); 400 const $title = $section.find('.filter-title'); 401 402 if (!$check_boxes.length || !$title.length) return; 403 $check_boxes.off('change.brandFilter').on('change.brandFilter', function () { 404 const checked_count = $check_boxes.filter(':checked').length; 405 const base_text = $title.text().replace(/\s*\(\d+\)$/, ''); 406 $title.text(checked_count > 0 ? `${base_text} (${checked_count})` : base_text); 407 }); 408 $check_boxes.each(function () { 409 $(this).trigger('change.brandFilter'); 410 }); 411 }); 412 } 413 }; 414 415 // --- Energy filter counter --- 416 Drupal.behaviors.energy_filter = { 417 attach: function (context) {
418 $('.filter-section.energy-filter', context).each(function () { 419 const $section = $(this); 420 if ($section.data('energyFilterAttached')) { 421 return; 422 } 423 $section.data('energyFilterAttached', true); 424 const $check_boxes = $section.find('input[type="checkbox"]'); 425 const $title = $section.find('.filter-title'); 426 if (!$check_boxes.length || !$title.length) return; 427 428 $check_boxes.on('change.energyFilter', function () { 429 const checked_count = $check_boxes.filter(':checked').length; 430 const base_text = $title.text().replace(/\s*\(\d+\)$/, ''); 431 $title.text(checked_count > 0 ? `${base_text} (${checked_count})` : base_text); 432 }); 433 $check_boxes.trigger('change.energyFilter'); 434 }); 435 } 436 }; 437 438 $('#more-filter-home').click(function (e) { 439 $.fn.show_search_modal(e, 'new-advanced-search'); 440 }); 441 442})(jQuery, Drupal);
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.