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  }