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