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  }