github.com/anth0d/nomad@v0.0.0-20221214183521-ae3a0a2cad06/ui/app/mixins/searchable.js (about) 1 import Mixin from '@ember/object/mixin'; 2 import { get, computed } from '@ember/object'; 3 import { reads } from '@ember/object/computed'; 4 import Fuse from 'fuse.js'; 5 6 /** 7 Searchable mixin 8 9 Simple search filtering behavior for a list of objects. 10 11 Properties to override: 12 - searchTerm: the string to use as a query 13 - searchProps: the props on each object to search 14 -- exactMatchSearchProps: the props for exact search when props are different per search type 15 -- regexSearchProps: the props for regex search when props are different per search type 16 -- fuzzySearchProps: the props for fuzzy search when props are different per search type 17 - exactMatchEnabled: (true) disable to not use the exact match search type 18 - fuzzySearchEnabled: (false) enable to use the fuzzy search type 19 - regexEnabled: (true) disable to disable the regex search type 20 - listToSearch: the list of objects to search 21 22 Properties provided: 23 - listSearched: a subset of listToSearch of items that meet the search criteria 24 */ 25 // eslint-disable-next-line ember/no-new-mixins 26 export default Mixin.create({ 27 searchTerm: '', 28 listToSearch: computed(function () { 29 return []; 30 }), 31 32 searchProps: null, 33 exactMatchSearchProps: reads('searchProps'), 34 regexSearchProps: reads('searchProps'), 35 fuzzySearchProps: reads('searchProps'), 36 37 // Three search modes 38 exactMatchEnabled: true, 39 fuzzySearchEnabled: false, 40 includeFuzzySearchMatches: false, 41 regexEnabled: true, 42 43 // Search should reset pagination. Not every instance of 44 // search will be paired with pagination, but it's still 45 // preferable to generalize this rather than risking it being 46 // forgotten on a single page. 47 resetPagination() { 48 if (this.currentPage != null) { 49 this.set('currentPage', 1); 50 } 51 }, 52 53 fuse: computed( 54 'fuzzySearchProps.[]', 55 'includeFuzzySearchMatches', 56 'listToSearch.[]', 57 function () { 58 return new Fuse(this.listToSearch, { 59 shouldSort: true, 60 threshold: 0.4, 61 location: 0, 62 distance: 100, 63 tokenize: true, 64 matchAllTokens: true, 65 maxPatternLength: 32, 66 minMatchCharLength: 1, 67 includeMatches: this.includeFuzzySearchMatches, 68 keys: this.fuzzySearchProps || [], 69 getFn(item, key) { 70 return get(item, key); 71 }, 72 }); 73 } 74 ), 75 76 listSearched: computed( 77 'exactMatchEnabled', 78 'exactMatchSearchProps.[]', 79 'fuse', 80 'fuzzySearchEnabled', 81 'fuzzySearchProps.[]', 82 'includeFuzzySearchMatches', 83 'listToSearch.[]', 84 'regexEnabled', 85 'regexSearchProps.[]', 86 'searchTerm', 87 function () { 88 const searchTerm = this.searchTerm.trim(); 89 90 if (!searchTerm || !searchTerm.length) { 91 return this.listToSearch; 92 } 93 94 const results = []; 95 96 if (this.exactMatchEnabled) { 97 results.push( 98 ...exactMatchSearch( 99 searchTerm, 100 this.listToSearch, 101 this.exactMatchSearchProps 102 ) 103 ); 104 } 105 106 if (this.fuzzySearchEnabled) { 107 let fuseSearchResults = this.fuse.search(searchTerm); 108 109 if (this.includeFuzzySearchMatches) { 110 fuseSearchResults = fuseSearchResults.map((result) => { 111 const item = result.item; 112 item.set('fuzzySearchMatches', result.matches); 113 return item; 114 }); 115 } 116 117 results.push(...fuseSearchResults); 118 } 119 120 if (this.regexEnabled) { 121 results.push( 122 ...regexSearch(searchTerm, this.listToSearch, this.regexSearchProps) 123 ); 124 } 125 126 return results.uniq(); 127 } 128 ), 129 }); 130 131 function exactMatchSearch(term, list, keys) { 132 if (term.length) { 133 return list.filter((item) => keys.some((key) => get(item, key) === term)); 134 } 135 } 136 137 function regexSearch(term, list, keys) { 138 if (term.length) { 139 try { 140 const regex = new RegExp(term, 'i'); 141 // Test the value of each key for each object against the regex 142 // All that match are returned. 143 return list.filter((item) => 144 keys.some((key) => regex.test(get(item, key))) 145 ); 146 } catch (e) { 147 // Swallow the error; most likely due to an eager search of an incomplete regex 148 } 149 return []; 150 } 151 }