const findstr = window.findstr || {};
import TomSelect from 'tom-select';
import 'tom-select/src/scss/tom-select.default.scss';
import { _x, sprintf } from '@wordpress/i18n';
document.addEventListener('findstrLoaded', function (e) {
const { buildSearchQuery, parseFilters, parseSort, getFacetDistribution } =
window.findstr.helpers;
function isMultiselect(field) {
return field.options && field.options.multiselect === true;
}
function hideEmptyItems(field) {
return (
isMultiselect(field) &&
field.options?.multiselectLogic !== 'OR' &&
field.options?.hideEmpty
);
}
function showCount(field) {
return field.options?.showCount;
}
function optionCount(field, option, group, facets) {
if (facets) {
return facets[field.source_name][option] || 0;
}
return showCount(field)
? findstr.groups[group]?.facets[field.source_name]?.[option] || 0
: findstr.facets[field.source_name][option] || 0;
}
function manageItems(dropdown) {
const items = dropdown.control.children;
const countElement = dropdown.control.querySelector('.items-count');
if (2 < dropdown.items.length) {
for (let i = 0; i < items.length; i++) {
if (items[i].classList.contains('item')) {
items[i].classList.add('item--hidden');
}
}
if (countElement) {
countElement.textContent = sprintf(
/* Translators: %s is the number of selected items */
_x('%s selected', 'items selected count', 'findstr'),
dropdown.items.length
);
} else {
const selectionCount = document.createElement('span');
selectionCount.classList.add('items-count');
selectionCount.textContent = sprintf(
/* Translators: %s is the number of selected items */
_x('%s selected', 'items selected count', 'findstr'),
dropdown.items.length
);
dropdown.control.appendChild(selectionCount);
}
} else {
for (let i = 0; i < items.length; i++) {
if (items[i].classList.contains('item--hidden')) {
items[i].classList.remove('item--hidden');
}
}
if (countElement) {
dropdown.control.removeChild(countElement);
}
}
}
findstr.hooks.addAction('findstrInit', 'findstr-search', () => {
const dropdownFields = document.querySelectorAll(
'.findstr-field.findstr-field-dropdown'
);
dropdownFields.forEach((fieldItem) => {
const field = JSON.parse(fieldItem.dataset.field);
const input = fieldItem.querySelector(
'.findstrFieldContainer input[data-findstr]'
);
const values = input.value.split('||');
const group = fieldItem.dataset.group;
const choices = findstr.facets[field.source_name] || {};
const hideEmpty = hideEmptyItems(field);
const shouldDisable = isMultiselect(field);
const options = [];
Object.keys(choices).forEach((choice) => {
const count = optionCount(field, choice, group);
const disabled =
count === 0 && !values.includes(choice) && shouldDisable;
if (!hideEmpty || !disabled) {
options.push({
count,
disabled,
value: choice,
/**
* Filter the field value label
*
* @hook findstrFieldValueLabel
*
* @param {string} label - The label
* @param {string} key - The key
* @param {Object} field - The field object
* @param {Array} selected - The selected values
*
* @return {string} The label
*/
text: findstr.hooks.applyFilters(
'findstrFieldValueLabel',
choice,
field
),
});
}
});
const maxItems = isMultiselect(field) ? null : 1;
let plugins = {
clear_button: {
title: _x('Reset', 'dropdown clear button', 'findstr'),
},
};
if (isMultiselect(field)) {
plugins = ['checkbox_options'];
}
const inputElement = fieldItem.querySelector(
'.findstrFieldContainer input'
);
const dropdown = new TomSelect(
inputElement,
/**
* Filter the dropdown select options.
* Options are passed to TomSelect, @see https://tom-select.js.org/docs/
*
* @hook findstrDropdownSelectOptions
*
* @param {Object} options - The options
* @param {Object} field - The field object
* @param {string} group - The group
* @param {Object} fieldItem - The field item
*
* @return {Object} The options
*/
findstr.hooks.applyFilters(
'findstrDropdownSelectOptions',
{
maxItems,
options,
plugins,
persist: false,
controlInput: '<input type="text" readonly="readonly">',
placeholder: inputElement.placeholder || '',
hidePlaceholder: false,
sortField: [
{ field: 'value' },
{ field: '$order' },
{ field: '$score' },
],
delimiter: '||',
onInitialize() {
manageItems(this);
},
render: {
option(data, escape) {
if (showCount(field)) {
const count = data.count || 0;
const ariaLabel = sprintf(
/* Translators: %s is the field value, %i is the results count */
_x(
'Number of results for %s : %i',
'dropdown option',
'findstr'
),
escape(data.text),
count
);
return `<div>${escape(data.text)} <span class="filter-count" data-value="${escape(data.value)}" data-field="${field.id}" aria-label="${ariaLabel}">${escape(count)}</span></div>`;
}
return `<div>${escape(data.text)}</div>`;
},
item(data, escape) {
return (
'<span class="item">' +
escape(data.text) +
'</span>'
);
},
},
},
field,
fieldItem.dataset.group,
fieldItem
)
);
dropdown.on('item_add', function () {
manageItems(dropdown);
});
dropdown.on('item_remove', function () {
manageItems(dropdown);
});
findstr.hooks.addAction(
'beforeSearch',
'findstr-dropdown',
(currentGroup, parameters, currentField, currentElement) => {
const newQuery = buildSearchQuery(group, fieldItem);
const query = {
...newQuery,
facets: ['*'],
limit: 1,
};
const queryClone = JSON.parse(JSON.stringify(query)); //clone query;
if (
field.options?.multiselect &&
field.options?.multiselectLogic === 'OR'
) {
delete query.filter.clauses[field.source_name];
//update count, remove filters
queryClone.filter = parseFilters(query.filter);
queryClone.sort = parseSort(query.sort);
} else {
//update count, with filters
queryClone.filter = parseFilters(newQuery.filter);
queryClone.sort = parseSort(newQuery.sort);
}
getFacetDistribution(queryClone).then((facets) => {
Object.keys(findstr.facets[field.source_name]).forEach(
(choice) => {
//manage excluded options
if (
field.options?.excludeOptions?.includes(
choice
)
) {
dropdown.removeOption(choice);
return;
}
const count = optionCount(
field,
choice,
group,
facets
);
const disabled =
count === 0 &&
!dropdown.items.includes(choice) &&
shouldDisable;
const data = {
count,
value: choice,
disabled,
text: findstr.hooks.applyFilters(
'findstrFieldValueLabel',
choice,
field
),
};
dropdown.updateOption(choice, data);
if (hideEmpty && disabled) {
dropdown.removeOption(choice);
} else if (!dropdown.getOption(choice)) {
dropdown.addOption(data);
}
}
);
if (field.options?.multiselect && currentElement) {
//this condition is added to prevent the dropdown from refreshing when the search is reset
if (
currentElement.dataset.findstrReseted !== 'true'
) {
dropdown.refreshOptions(
field.id === currentField.id
);
}
}
manageItems(dropdown);
});
}
);
findstr.hooks.addAction(
'afterSearchResults',
'findstr-dropdown',
(results, group, field, currentElement) => {
if (currentElement) {
currentElement.dataset.findstrReseted = 'false';
}
}
);
});
});
/**
* Build search query based on select value
*/
findstr.hooks.addFilter(
'findstrBuildSearchQuery',
'findstr-search',
(query, items, group) => {
for (const [filter_name, inputs] of Object.entries(items)) {
inputs.forEach((input) => {
if (input.dataset.fieldType === 'dropdown' && input.value) {
const field = JSON.parse(
document.querySelector(
`[data-id="${input.dataset.findstrId}"][data-group="${group}"]`
).dataset.field
);
const values = input.value.split('||');
//default case, single select
query.filter.clauses[filter_name] = {
value: values[0],
};
if (field.options.multiselect === true) {
query.filter.clauses[filter_name].value = values;
if (field.options.multiselectLogic === 'OR') {
query.filter.clauses[filter_name].compare =
'IN';
} else {
query.filter.clauses[filter_name].compare = '=';
}
}
}
});
}
return query;
}
);
/**
* Reset Filters
*/
findstr.hooks.addAction('resetFilters', 'dropdownField', (group, query) => {
findstr.groups[group].items.forEach((item) => {
if ('dropdown' === item.field.type) {
const targetValue =
query.filter.clauses[item.field.source_name]?.value;
item.tomselect.clear(true);
if ('undefined' !== typeof targetValue) {
item.tomselect.addItems(targetValue, true);
}
}
});
});
/**
* Excluded filters
*/
findstr.hooks.addFilter(
'findstrDropdownSelectOptions',
'checkboxField',
function (options, field) {
if (field.options?.excludeOptions) {
//remove the excluded options from the choices
options.options = options.options.filter(
(option) =>
!field.options.excludeOptions.includes(option.value)
);
}
return options;
}
);
});