front_fields_checkbox.js

import qs from 'qs';

const findstr = window.findstr || {};
const {
	buildSearchQuery,
	getFacetDistribution,
	itemsToFields,
	parseFilters,
	parseSort,
	Handlebars,
} = window.findstr.helpers;

function isMultiselectField(field) {
	return !(field.options && field.options.multiselect !== true);
}

function renderCheckboxField(fieldItem, field, choices) {
	const checkboxTemplate = Handlebars.compile(
		fieldItem.querySelector('.findstrCheckboxFieldTemplate').innerHTML
	);
	const container = fieldItem.querySelector('.findstrFieldContainer');

	container.innerHTML = checkboxTemplate({
		/**
		 * Filter the choices for the checkbox field
		 *
		 * @hook findstrCheckboxFieldChoices
		 *
		 * @param {Object} choices - The choices object
		 * @param {Object} field   - The field object
		 *
		 * @return {Object} The choices object
		 */
		choices: findstr.hooks.applyFilters(
			'findstrCheckboxFieldChoices',
			choices,
			field
		),
		field,
	});
}

function optionCount(field, option, group, facets) {
	if (facets) {
		return facets[field.source_name][option] || 0;
	}

	if (disableCountUpdate(field)) {
		return findstr.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 showCount(field) {
	return field.options?.showCount;
}

function disableCountUpdate(field) {
	return field.options?.disableCountUpdate;
}

function updateElementCount(element, count, field) {
	const parent = element.parentElement;

	if (0 === count && !element.checked) {
		element.disabled = true;
		parent.classList.add('disabled');
	} else {
		element.disabled = false;
		parent.classList.remove('disabled');
	}

	if (!disableCountUpdate(field)) {
		const countElement = parent.querySelector('.filter-count');
		if (countElement) {
			countElement.innerHTML = count;
		}
	}
}

document.addEventListener(
	'findstrLoaded',
	function (e) {
		/**
		 * Build checkbox field with values on first search (warmup)
		 */
		findstr.hooks.addAction(
			'searchResultsFirstLoad',
			'findstr-search',
			() => {
				const urlSearchParams = qs.parse(window.location.search, {
					ignoreQueryPrefix: true,
					arrayFormat: 'brackets',
				});

				const checkboxFields = document.querySelectorAll(
					'.findstr-field.findstr-field-checkbox'
				);

				checkboxFields.forEach((fieldItem) => {
					const field = JSON.parse(fieldItem.dataset.field);
					const facets = findstr.facets[field.source_name] || {};

					const choices = {};
					const selected =
						urlSearchParams?.findstr?.filter?.[field.source_name]
							?.value || [];

					Object.keys(facets).forEach((key) => {
						choices[key] = {
							value: key,
							/**
							 * 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
							 */
							label: findstr.hooks.applyFilters(
								'findstrFieldValueLabel',
								key,
								field,
								selected
							),
							count: 0,
							checked: selected.includes(key),
						};
					});

					renderCheckboxField(fieldItem, field, choices);

					const items = fieldItem.querySelectorAll('[data-findstr]');
					itemsToFields(items, findstr);
				});
			}
		);

		findstr.hooks.addAction(
			'findstrInit',
			'findstr-search-checkbox',
			() => {
				//manage checkbox default values
				Object.keys(findstr.groups).forEach((group) => {
					const checkboxFields = document.querySelectorAll(
						`.findstr-field.findstr-field-checkbox[data-group="${group}"]`
					);

					checkboxFields.forEach((checkboxField) => {
						const field = JSON.parse(checkboxField.dataset.field);

						if (showCount(field)) {
							const items = checkboxField.querySelectorAll(
								'[data-findstr][type="checkbox"]'
							);

							items.forEach((item) => {
								if (field.type === 'checkbox') {
									if (findstr.groups[group].facets) {
										const count = optionCount(
											field,
											item.value,
											group
										);
										updateElementCount(item, count, field);
									}
								}
							});

							/**
							 * manage facets count updates
							 */
							findstr.hooks.addAction(
								'beforeSearch',
								'findstr-search',
								(
									currentGroup,
									parameters,
									currentField,
									currentElement,
									index
								) => {
									const newQuery = buildSearchQuery(
										group,
										checkboxField
									);

									const query = {
										...newQuery,
										q: newQuery.q,
										facets: ['*'],
										limit: 1,
									};

									const queryClone = JSON.parse(
										JSON.stringify(query)
									); //clone query;

									//remove the filter for the current field
									delete query.filter.clauses[
										field.source_name
									];

									if (
										field.options?.multiselect &&
										field.options?.multiselectLogic === 'OR'
									) {
										//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) => {
												const count = optionCount(
													field,
													choice,
													group,
													facets
												);

												const item =
													document.querySelector(
														`[data-findstr-group="${group}"][data-findstr-id="${field.id}"][value="${choice}"]`
													);

												updateElementCount(
													item,
													count,
													field
												);
											});
										}
									);
								}
							);
						}
					});
				});
			}
		);

		/**
		 *  Build search query based on select value
		 */
		findstr.hooks.addFilter(
			'findstrBuildSearchQuery',
			'findstr-search',
			(query, items, group, target) => {
				//loop through all the fields
				for (const [filter_name, inputs] of Object.entries(items)) {
					//for each field, get the field object
					const field = JSON.parse(
						document.querySelector(
							`[data-id="${inputs[0].dataset.findstrId}"]`
						).dataset.field
					);

					//if the field is a checkbox
					if (field.type === 'checkbox') {
						const values = [];
						inputs.forEach((input) => {
							//if the checkbox is checked and has a value
							if (input.checked && '' !== input.value) {
								values.push(input.value);

								if (!isMultiselectField(field)) {
									query.filter.clauses[input.name] = {
										value: input.value,
									};
								}
							}
						});

						//default case, single select
						if (values[0]) {
							query.filter.clauses[filter_name] = {
								value: values[0],
							};
						}

						//handle multiselect fields
						if (isMultiselectField(field)) {
							if (values.length > 0) {
								query.filter.clauses[filter_name].value =
									values;

								//if the field has AND/OR logic
								if (field.options.multiselectLogic === 'OR') {
									query.filter.clauses[filter_name].compare =
										'IN';
								} else {
									query.filter.clauses[filter_name].compare =
										'=';
								}
							}
						}
					}
				}

				return query;
			}
		);

		/**
		 * Handle "radio mode" for checkbox fields
		 * Uncheck other checkboxes in the same group
		 */
		findstr.hooks.addAction(
			'findstrOnFilterEvents',
			'findstr-search',
			(target, item, group, field) => {
				if (
					field.type === 'checkbox' &&
					(!field.options || field.options.multiselect !== true)
				) {
					if (item.checked) {
						const inputs = document.querySelectorAll(
							`[data-findstr-group="${group}"][data-findstr-id="${field.id}"]:checked`
						);

						inputs.forEach((input) => {
							if (input !== target) {
								input.checked = false;
							}
						});
					} else {
						//if the checkbox is unchecked, re-check the first one ("all")
						document.getElementById(
							`findstr-all-${field.id}-${group}`
						).checked = true;
					}
				}
			}
		);

		/**
		 * Reset Filters
		 */
		findstr.hooks.addAction(
			'resetFilters',
			'checkboxField',
			(group, query) => {
				findstr.groups[group].items.forEach((item) => {
					if ('checkbox' === item.field.type) {
						const targetValue =
							query.filter.clauses[item.field.source_name]
								?.value || '';

						if (targetValue === item.value) {
							item.checked = true;
						} else {
							item.checked = false;
						}
					}
				});
			}
		);

		/**
		 * Excluded filters
		 */
		findstr.hooks.addFilter(
			'findstrCheckboxFieldChoices',
			'checkboxField',
			function (choices, field) {
				if (field.options?.excludeOptions) {
					field.options.excludeOptions.forEach((excluded) => {
						delete choices[excluded];
					});
				}
				return choices;
			}
		);
	},
	false
);