@ithaka/bonsai
Version:
ITHAKA core styling
130 lines (114 loc) • 3.77 kB
JavaScript
import Fuse from "fuse.js";
const defaultOptions = {
shouldSort: false,
keys: ["title"],
maxPatternLength: 100,
distance: 1000,
location: 0,
threshold: 0.1,
tokenize: true,
matchAllTokens: true,
id: "itemId"
};
/**
* Class to implement searching of a list of content
*
* @param $searchItems {jQuery} - List of content that is being searched
* @param searchOptions {Object} - Fuse options to override whats in defaultOptions
* @param $noResults {jQuery} [$noResults = $("#no-results")] - Element that contains the no results message
*
*
* @example
* const MySearch = new BonsaiFuzzySearch($searchItems, searchOptions, $noResults);
* MySearch.search("Smith");
*
*/
class BonsaiFuzzySearch {
constructor($searchItems, searchOptions={}, $noResults=$("#no-results")) {
if (!$searchItems.length) {
throw new Error("Can't build BonsaiFuzzySearch, missing items to search");
}
this.$searchItems = $searchItems;
this.$noResults = $noResults;
this.searchOptions = Object.assign({}, defaultOptions, searchOptions);
Promise.resolve(
this._generateFuseSearchList()
).then(resultList => {
this.fuseSearchList = resultList;
this.fuzzySearcher = new Fuse(this.fuseSearchList, this.searchOptions);
});
}
/**
* Builds object for Fuse to query utilizing data-fuzzy-key="value" attributes
*
* @return {Array}
* @private
*/
_generateFuseSearchList() {
let itemData = {},
resultList = [],
currentFuseKey = "";
for (let searchItemPos = 0; searchItemPos < this.$searchItems.length; searchItemPos++) {
itemData = {};
for (let fuseKeyPos = 0; fuseKeyPos < this.searchOptions.keys.length; fuseKeyPos++) {
currentFuseKey = this.searchOptions.keys[fuseKeyPos];
itemData[currentFuseKey] = this.$searchItems.eq(searchItemPos).data(`fuzzy-${currentFuseKey}`);
}
itemData["itemId"] = searchItemPos;
resultList.push(itemData);
}
return resultList;
}
/**
* Filters the matching results in the UI. Displays the this.$noResults element if zero matches
*
* @param results {Array} - Fuse results index Array
* @private
*/
_filterDisplayedResults(results) {
let resultIndex;
this.$searchItems.addClass("fuzzy-filter-out");
this.$noResults.addClass("hide");
if(results.length) {
for (let position=0; position <= results.length; position++) {
resultIndex = results[position];
this.$searchItems.eq(resultIndex).removeClass("fuzzy-filter-out");
}
}
else {
this.$noResults.removeClass("hide");
}
}
/**
* Clears previous results
*
* @private
*/
_clearSearchFilter() {
this.$searchItems.removeClass("fuzzy-filter-out");
this.$noResults.addClass("hide");
}
/**
* Search filtering based on query string, if empty string is passed
* for the query, will clear previous search matches
*
* @param query {String}
* @return {boolean} - returns true when search is complete
*/
search(query) {
if (query === "") {
this._clearSearchFilter();
return true;
}
else {
return Promise.resolve(
this.fuzzySearcher.search(query)
).then(fuseResults => {
this._filterDisplayedResults(fuseResults);
}).then(() => {
return true;
});
}
}
}
export { BonsaiFuzzySearch };