jQuery(function() { function getModuleDatasets() { return jQuery('.js-search-module-wrapper'); } function getFilterValues($module) { const values = {}; $module.find(".js-search-module-filter").each((_, filter) => { const filterType = filter.type || filter.dataset.type; const v = []; if(filterType === "select-one" || filterType === "select-multiple") { const selected = filter.selectedOptions; for (let option of selected) { v.push(option.value); } } else if(filterType === "multi-range") { const min = jQuery(filter).find(".from_input").val(); const max = jQuery(filter).find(".to_input").val(); v.push({"type": "range", min, max}); } else { // radio or checkboxes if(filter.checked) { v.push(filter.value); } } if(v.length > 0) { if(typeof values[filter.dataset.extraFieldId] === "undefined") { values[filter.dataset.extraFieldId] = []; } values[filter.dataset.extraFieldId] = values[filter.dataset.extraFieldId].concat(v); } }); return values; } function extraFieldsContainText(extraFields, relevantExtraFields, value) { let contains = false; for(const [field_id, active] of Object.entries(relevantExtraFields)){ if(active && typeof extraFields[parseInt(field_id)] !== "undefined") { contains ||= (extraFields[parseInt(field_id)].value || "").toLowerCase().includes(value); } } return contains; } function extraFieldsMatchFilters(extra_fields, filterValues) { for(let [field_id, values] of Object.entries(filterValues)) { if(typeof extra_fields[field_id] === "undefined") { return false; } const value_lines = (extra_fields[field_id].value || "").split("\n"); if(!values.every(v => { if(typeof v === "object") { switch(v.type) { case 'range': return (v.min === "" || v.min <= Number(value_lines[0])) && (v.max === "" || v.max >= Number(value_lines[0])); default: console.warn("[JS Search Module]: unknown filter value type: "+v.type+"!"); return true; } } return value_lines.includes(v); })) { return false; } } return true; } function selectDatasets($module) { const $datasets = $module.find(".js-search-module-dataset"); const searchValue = ($module.find(".js-search-module-search-bar").val() || "").toLowerCase(); const filterValues = getFilterValues($module); const relevantExtraFields = $module.data("extrafields"); $datasets.each((_, ds) => { let display = true; const $ds = jQuery(ds); const extra_fields = $ds.data("extra-fields"); const title = String($ds.data("title")).toLowerCase(); const picText = String($ds.data("pic-text")).toLowerCase(); if(searchValue !== "") { display &&= title.includes(searchValue) || picText.includes(searchValue) || extraFieldsContainText(extra_fields, relevantExtraFields, searchValue); } if(Object.entries(filterValues).length > 0) { display &&= extraFieldsMatchFilters(extra_fields, filterValues); } if(display) { $ds.show(); } else { $ds.hide(); } }); } const $modules = getModuleDatasets(); $modules.find(".js-search-module-search-bar").on("keyup", (e) => { const $module = jQuery(e.currentTarget).parents(".js-search-module-wrapper").first(); selectDatasets($module); }); $modules.find(".js-search-module-filter").on("change", (e) => { const $module = jQuery(e.currentTarget).parents(".js-search-module-wrapper").first(); selectDatasets($module); }); // allow unchecking radio buttons by clicking them again // this needs a lot of workarounds to work // since e.currentTarget.checked is always reported as true during click event $modules.find("input[type='radio'].js-search-module-filter").on("click", (e) => { // set clicked status to dataset, we will need this in order to check if the change event has fired or not // dataset will be turned into a string anyways, so might as well assign it as a string already e.currentTarget.dataset.clicked = "true"; // check after 1 ms to wait for the change event to fire if current radio button was not previously selected setTimeout((target) => { // target.dataset.clicked === "true" means that change event hasn't fired, so radi obutton was previously unchecked if(target.dataset.clicked === "true") { target.checked = false; target.dataset.clicked = false; // re-fire dataset selection selectDatasets(jQuery(target).parents(".js-search-module-wrapper").first()); } }, 1, e.currentTarget); }); // change event firing means that radio button was not previously selected, do not unselect! $modules.find("input[type='radio'].js-search-module-filter").on("change", (e) => e.currentTarget.dataset.clicked = false); // doing a similar trick with select fields but switched around // change is here called before click, so the logic gets a little easier $modules.find("select.js-search-module-filter option").on("click", e => { const $select = jQuery(e.currentTarget).parents(".js-search-module-filter").first(); if($select.data("changed") !== "true") { $select.prop("selectedIndex", -1); // re-fire dataset selection selectDatasets($select.parents(".js-search-module-wrapper").first()); } $select.data("changed", "false"); }); $modules.find("select.js-search-module-filter").on("change", e => { const $select = jQuery(e.currentTarget); $select.data("changed", "true"); }); // slider search function controlFromInput(fromSlider, fromInput, toInput, controlSlider) { const [from, to] = getParsed(fromInput, toInput); fillSlider(fromInput, toInput, controlSlider); if (from > to) { fromSlider.value = to; fromInput.value = to; } else { fromSlider.value = from; } } function controlToInput(toSlider, fromInput, toInput, controlSlider) { const [from, to] = getParsed(fromInput, toInput); fillSlider(fromInput, toInput, controlSlider); setToggleAccessible(toInput, toSlider); if (from <= to) { toSlider.value = to; toInput.value = to; } else { toInput.value = from; } } function controlFromSlider(fromSlider, toSlider, fromInput) { const [from, to] = getParsed(fromSlider, toSlider); fillSlider(fromSlider, toSlider, toSlider); if (from > to) { fromSlider.value = to; fromInput.value = to; } else { fromInput.value = from; } } function controlToSlider(fromSlider, toSlider, toInput) { const [from, to] = getParsed(fromSlider, toSlider); fillSlider(fromSlider, toSlider, toSlider); setToggleAccessible(toSlider, toSlider); if (from <= to) { toSlider.value = to; toInput.value = to; } else { toInput.value = from; toSlider.value = from; } } function getParsed(currentFrom, currentTo) { const from = parseInt(currentFrom.value, 10); const to = parseInt(currentTo.value, 10); return [from, to]; } function fillSlider(from, to, controlSlider) { const rangeDistance = to.max-to.min; const fromPosition = from.value - to.min; const toPosition = to.value - to.min; controlSlider.style.background = `linear-gradient( to right, var(--slider-inactive-color) 0%, var(--slider-inactive-color) ${(fromPosition)/(rangeDistance)*100}%, var(--slider-active-color) ${((fromPosition)/(rangeDistance))*100}%, var(--slider-active-color) ${(toPosition)/(rangeDistance)*100}%, var(--slider-inactive-color) ${(toPosition)/(rangeDistance)*100}%, var(--slider-inactive-color) 100%)`; } function setToggleAccessible(currentTarget, toSlider) { if (Number(currentTarget.value) <= 0 ) { toSlider.style.zIndex = 2; } else { toSlider.style.zIndex = 0; } } $modules.find('.js-search-module-filter-range_container').each((_, container) => { const fromSlider = container.querySelector('.from_slider'); const toSlider = container.querySelector('.to_slider'); const fromInput = container.querySelector('.from_input'); const toInput = container.querySelector('.to_input'); fillSlider(fromSlider, toSlider, toSlider); setToggleAccessible(toSlider, toSlider); fromSlider.oninput = () => controlFromSlider(fromSlider, toSlider, fromInput); toSlider.oninput = () => controlToSlider(fromSlider, toSlider, toInput); fromInput.oninput = () => controlFromInput(fromSlider, fromInput, toInput, toSlider); toInput.oninput = () => controlToInput(toSlider, fromInput, toInput, toSlider); [fromSlider, toSlider, fromInput, toInput].forEach(element => { element.onchange = (e) => { const $module = jQuery(e.currentTarget).parents(".js-search-module-wrapper").first(); selectDatasets($module); };}); }); });