UNPKG

@ithaka/bonsai

Version:
130 lines (114 loc) 3.77 kB
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 };