github.com/Ilhicas/nomad@v1.0.4-0.20210304152020-e86851182bc3/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('fuzzySearchProps.[]', 'includeFuzzySearchMatches', 'listToSearch.[]', function() { 54 return new Fuse(this.listToSearch, { 55 shouldSort: true, 56 threshold: 0.4, 57 location: 0, 58 distance: 100, 59 tokenize: true, 60 matchAllTokens: true, 61 maxPatternLength: 32, 62 minMatchCharLength: 1, 63 includeMatches: this.includeFuzzySearchMatches, 64 keys: this.fuzzySearchProps || [], 65 getFn(item, key) { 66 return get(item, key); 67 }, 68 }); 69 }), 70 71 listSearched: computed( 72 'exactMatchEnabled', 73 'exactMatchSearchProps.[]', 74 'fuse', 75 'fuzzySearchEnabled', 76 'fuzzySearchProps.[]', 77 'includeFuzzySearchMatches', 78 'listToSearch.[]', 79 'regexEnabled', 80 'regexSearchProps.[]', 81 'searchTerm', 82 function() { 83 const searchTerm = this.searchTerm.trim(); 84 85 if (!searchTerm || !searchTerm.length) { 86 return this.listToSearch; 87 } 88 89 const results = []; 90 91 if (this.exactMatchEnabled) { 92 results.push( 93 ...exactMatchSearch(searchTerm, this.listToSearch, this.exactMatchSearchProps) 94 ); 95 } 96 97 if (this.fuzzySearchEnabled) { 98 let fuseSearchResults = this.fuse.search(searchTerm); 99 100 if (this.includeFuzzySearchMatches) { 101 fuseSearchResults = fuseSearchResults.map(result => { 102 const item = result.item; 103 item.set('fuzzySearchMatches', result.matches); 104 return item; 105 }); 106 } 107 108 results.push(...fuseSearchResults); 109 } 110 111 if (this.regexEnabled) { 112 results.push(...regexSearch(searchTerm, this.listToSearch, this.regexSearchProps)); 113 } 114 115 return results.uniq(); 116 } 117 ), 118 }); 119 120 function exactMatchSearch(term, list, keys) { 121 if (term.length) { 122 return list.filter(item => keys.some(key => get(item, key) === term)); 123 } 124 } 125 126 function regexSearch(term, list, keys) { 127 if (term.length) { 128 try { 129 const regex = new RegExp(term, 'i'); 130 // Test the value of each key for each object against the regex 131 // All that match are returned. 132 return list.filter(item => keys.some(key => regex.test(get(item, key)))); 133 } catch (e) { 134 // Swallow the error; most likely due to an eager search of an incomplete regex 135 } 136 return []; 137 } 138 }