front_findstr-search_src_helpers.js
import { MeiliSearch } from 'meilisearch';
import { Handlebars } from './handlebars';
import cyrb53 from 'cyrb53';
const escapeString = function (str) {
if (typeof str !== 'string') {
return str;
}
return str.replace(/'/g, "\\'");
};
/**
* Parse filter parameter
*
* @return {string}
* @param filtersQuery
*/
const parseFilters = (filtersQuery) => {
const filters = JSON.parse(JSON.stringify(filtersQuery));
filters.relation = filters.relation || 'AND';
if (!filters.clauses || typeof filters.clauses !== 'object') {
return '';
}
let queryString = '';
const temp = [];
for (const [key, clause] of Object.entries(filters.clauses)) {
if (typeof clause !== 'object') {
console.error('clause must be an object');
}
if (clause.relation && clause.clauses) {
const subQuery = parseFilters(clause);
temp.push(`( ${subQuery} )`);
} else {
//set key if not set
if (!clause.key) {
clause.key = key;
}
//set default compare to "IN" if value is an array
if (Array.isArray(clause.value) && !clause.compare) {
clause.compare = 'IN';
}
clause.compare = clause.compare || '=';
switch (clause.compare) {
case 'IN':
case 'NOT IN':
//at this point, clause.value should be an array
if (!Array.isArray(clause.value)) {
clause.value = [clause.value];
}
clause.value = clause.value.map(
(v) => `'${escapeString(v)}'`
);
clause.value = clause.value.join(',');
temp.push(
`${clause.key} ${clause.compare} [${clause.value}]`
);
break;
case 'EXISTS':
case 'NOT EXISTS':
case 'EMPTY':
case 'NOT EMPTY':
case 'IS NULL':
case 'IS NOT NULL':
temp.push(`${clause.key} ${clause.compare}`);
break;
case 'BETWEEN':
if (Array.isArray(clause.value)) {
temp.push(
`${clause.key} >= '${escapeString(clause.value[0])}'` +
` AND ${clause.key} < '${escapeString(clause.value[1])}'`
);
}
break;
default:
if (Array.isArray(clause.value)) {
clause.value.forEach((v) => {
temp.push(
`${clause.key} ${clause.compare} '${escapeString(v)}'`
);
});
} else {
temp.push(
`${clause.key} ${clause.compare} '${escapeString(clause.value)}'`
);
}
break;
}
}
}
queryString = temp.join(` ${filters.relation} `);
return `${queryString}`;
};
/**
* Parse sort parameter
*
* @param sort
* @return {string[]}
*/
const parseSort = (sort) => {
const sortQuery = [];
if (!sort || typeof sort !== 'object') {
return sortQuery;
}
for (const [key, value] of Object.entries(sort)) {
sortQuery.push(`${key}:${value}`);
}
return sortQuery;
};
const buildSearchQuery = (group, target) => {
let query;
const groupQuery = document.querySelector(
`[data-findstr-results="${group}"]`
);
if (groupQuery) {
query = JSON.parse(groupQuery.dataset.findstrQuery);
} else {
query = JSON.parse(findstr.config.defaultQuery);
}
if ('undefined' === typeof query.filter.clauses) {
query.filter.clauses = [];
}
const items = document.querySelectorAll(
`[data-findstr-group="${group}"][data-findstr-id]`
);
const groupedItems = {};
items.forEach((item) => {
if (!item.name) {
console.info(`item must have a name (group : ${group})`, item);
} else {
groupedItems[item.name] = groupedItems[item.name] || [];
groupedItems[item.name].push(item);
}
});
/**
* Filter the search query after the query is built.
*
* @hook findstrBuildSearchQuery
*
* @param {Object} query - The query object
* @param {Object} groupedItems - The grouped items
* @param {string} group - The group name
* @param {HTMLObjectElement} target - The target element
*
* @return {Object} The query object
*/
query = findstr.hooks.applyFilters(
'findstrBuildSearchQuery',
query,
groupedItems,
group,
target
);
return query;
};
const getField = function (id, group) {
const element = findstr.groups[group].items.find(
(item) => parseInt(item.field.id) === parseInt(id)
);
if (element) {
return element.field;
}
return null;
};
const itemsToFields = function (items, findstr) {
const fields = {};
items.forEach((item) => {
item.field = JSON.parse(item.closest('[data-field]').dataset.field);
const group = item.dataset.findstrGroup;
findstr.groups[group] = findstr.groups[group] || {};
findstr.groups[group].items = findstr.groups[group].items || [];
if (findstr.groups[group].items.includes(item) === false) {
findstr.groups[group].items.push(item);
}
//determine if group needs to have facets count
if (item.field.options?.showCount) {
findstr.groups[group].showCount = true;
//count behavior, if multiselectLogic is "OR" count should not be updated on each search
if (
item.field.options?.multiselect &&
item.field.options?.multiselectLogic === 'OR'
) {
findstr.groups[group].countUpdate = false;
}
}
});
return fields;
};
const getFacetDistribution = function async(query) {
const findstr = window.findstr || {};
window.findstr.queries = window.findstr.queries || {};
const queryKey = cyrb53(JSON.stringify(query));
if (window.findstr.queries[queryKey]) {
return Promise.resolve(window.findstr.queries[queryKey]);
}
const client = new MeiliSearch({
host: findstr.config.host,
apiKey: findstr.config.publicKey,
});
const index = client.index(findstr.config.index);
return index.search('', query).then((results) => {
window.findstr.queries[queryKey] = results.facetDistribution;
return results.facetDistribution;
});
};
const userHasSelectedFilters = function( group, parameters ) {
const groupQueryElement = document.querySelector(
`[data-findstr-results="${group}"]`
);
const groupDefaultQuery = JSON.parse(
groupQueryElement.dataset.findstrQuery
);
const queryMatch =
JSON.stringify(groupDefaultQuery.filter) ===
JSON.stringify(parameters.filter);
return !queryMatch;
}
export {
buildSearchQuery,
parseFilters,
parseSort,
getField,
itemsToFields,
getFacetDistribution,
Handlebars,
userHasSelectedFilters,
};