github.com/manicqin/nomad@v0.9.5/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    // Search should reset pagination. Not every instance of
    40    // search will be paired with pagination, but it's still
    41    // preferable to generalize this rather than risking it being
    42    // forgotten on a single page.
    43    resetPagination() {
    44      if (this.currentPage != null) {
    45        this.set('currentPage', 1);
    46      }
    47    },
    48  
    49    fuse: computed('listToSearch.[]', 'fuzzySearchProps.[]', function() {
    50      return new Fuse(this.listToSearch, {
    51        shouldSort: true,
    52        threshold: 0.4,
    53        location: 0,
    54        distance: 100,
    55        tokenize: true,
    56        matchAllTokens: true,
    57        maxPatternLength: 32,
    58        minMatchCharLength: 1,
    59        keys: this.fuzzySearchProps || [],
    60        getFn(item, key) {
    61          return get(item, key);
    62        },
    63      });
    64    }),
    65  
    66    listSearched: computed(
    67      'searchTerm',
    68      'listToSearch.[]',
    69      'exactMatchEnabled',
    70      'fuzzySearchEnabled',
    71      'regexEnabled',
    72      'exactMatchSearchProps.[]',
    73      'fuzzySearchProps.[]',
    74      'regexSearchProps.[]',
    75      function() {
    76        const searchTerm = this.searchTerm.trim();
    77  
    78        if (!searchTerm || !searchTerm.length) {
    79          return this.listToSearch;
    80        }
    81  
    82        const results = [];
    83  
    84        if (this.exactMatchEnabled) {
    85          results.push(
    86            ...exactMatchSearch(
    87              searchTerm,
    88              this.listToSearch,
    89              this.exactMatchSearchProps
    90            )
    91          );
    92        }
    93  
    94        if (this.fuzzySearchEnabled) {
    95          results.push(...this.fuse.search(searchTerm));
    96        }
    97  
    98        if (this.regexEnabled) {
    99          results.push(
   100            ...regexSearch(searchTerm, this.listToSearch, this.regexSearchProps)
   101          );
   102        }
   103  
   104        return results.uniq();
   105      }
   106    ),
   107  });
   108  
   109  function exactMatchSearch(term, list, keys) {
   110    if (term.length) {
   111      return list.filter(item => keys.some(key => get(item, key) === term));
   112    }
   113  }
   114  
   115  function regexSearch(term, list, keys) {
   116    if (term.length) {
   117      try {
   118        const regex = new RegExp(term, 'i');
   119        // Test the value of each key for each object against the regex
   120        // All that match are returned.
   121        return list.filter(item => keys.some(key => regex.test(get(item, key))));
   122      } catch (e) {
   123        // Swallow the error; most likely due to an eager search of an incomplete regex
   124      }
   125      return [];
   126    }
   127  }