github.com/zoomfoo/nomad@v0.8.5-0.20180907175415-f28fd3a1a056/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 export default Mixin.create({ 26 searchTerm: '', 27 listToSearch: computed(() => []), 28 29 searchProps: null, 30 exactMatchSearchProps: reads('searchProps'), 31 regexSearchProps: reads('searchProps'), 32 fuzzySearchProps: reads('searchProps'), 33 34 // Three search modes 35 exactMatchEnabled: true, 36 fuzzySearchEnabled: false, 37 regexEnabled: true, 38 39 fuse: computed('listToSearch.[]', 'fuzzySearchProps.[]', function() { 40 return new Fuse(this.get('listToSearch'), { 41 shouldSort: true, 42 threshold: 0.4, 43 location: 0, 44 distance: 100, 45 tokenize: true, 46 matchAllTokens: true, 47 maxPatternLength: 32, 48 minMatchCharLength: 1, 49 keys: this.get('fuzzySearchProps') || [], 50 getFn(item, key) { 51 return get(item, key); 52 }, 53 }); 54 }), 55 56 listSearched: computed( 57 'searchTerm', 58 'listToSearch.[]', 59 'exactMatchEnabled', 60 'fuzzySearchEnabled', 61 'regexEnabled', 62 'exactMatchSearchProps.[]', 63 'fuzzySearchProps.[]', 64 'regexSearchProps.[]', 65 function() { 66 const searchTerm = this.get('searchTerm').trim(); 67 68 if (!searchTerm || !searchTerm.length) { 69 return this.get('listToSearch'); 70 } 71 72 const results = []; 73 74 if (this.get('exactMatchEnabled')) { 75 results.push( 76 ...exactMatchSearch( 77 searchTerm, 78 this.get('listToSearch'), 79 this.get('exactMatchSearchProps') 80 ) 81 ); 82 } 83 84 if (this.get('fuzzySearchEnabled')) { 85 results.push(...this.get('fuse').search(searchTerm)); 86 } 87 88 if (this.get('regexEnabled')) { 89 results.push( 90 ...regexSearch(searchTerm, this.get('listToSearch'), this.get('regexSearchProps')) 91 ); 92 } 93 94 return results.uniq(); 95 } 96 ), 97 }); 98 99 function exactMatchSearch(term, list, keys) { 100 if (term.length) { 101 return list.filter(item => keys.some(key => get(item, key) === term)); 102 } 103 } 104 105 function regexSearch(term, list, keys) { 106 if (term.length) { 107 try { 108 const regex = new RegExp(term, 'i'); 109 // Test the value of each key for each object against the regex 110 // All that match are returned. 111 return list.filter(item => keys.some(key => regex.test(get(item, key)))); 112 } catch (e) { 113 // Swallow the error; most likely due to an eager search of an incomplete regex 114 } 115 return []; 116 } 117 }