Search code examples
dartdart-polymer

Dart Polymer: How to filter elements in repeat-template?


I'm using the repeat template of Dart Polymer to show the items of list. Now, I would like to implement a search filter, where only the items are displayed, which relates to the typed search term. Consider the following code:

my-element.html

<polymer-element name="my-element">
  <script type="application/dart" src="my-element.dart"></script>
  <template>
    <input type="text" value="{{ searchTerm }}">
    <ul>
      <template repeat="{{ item in items }}">
        <template if="{{ matchesSearchFilter(item) }}">
          <li>{{ item }}</li>
        </template>
      </template>
    </ul>
  </template>
</polymer-element> 

my-element.dart

@CustomTag('my-element')
class MyElement extends PolymerElement {
  @observable List<String> items = toObservable(["Monday", "Tuesday", "Wednesday"]);
  @observable String searchTerm = '';

  MyElement.created() : super.created();

  matchesSearchFilter(String item) {
    if (searchTerm.isEmpty) return true;
    return item.toLowerCase().contains(searchTerm.toLowerCase());
  }
} 

For example, when I type "Mo" in the textbox, I would expect that the list of items updates automatically, such that only "Monday" is shown. However on typing any search term, the list remains the same and the search term is ignored.

So, how to implement such a feature correctly?


Solution

  • An second alternative is to add a filter :

      <template repeat="{{ item in items | filter(searchTerm)}}">
          <li>{{ item }}</li>
      </template>
    

    and

    // One line
    filter(term) => (items) => term.isEmpty ? items : items.where((item) => item.toLowerCase().contains(searchTerm.toLowerCase())).toList();
    

    Items will be filtered before being used in the repeat loop. Personally I prefer to use a filter instead of Günter's solution because :
    - it's easy
    - centralized
    - quite understandable to add other filters with pipe operator
    - your list is not changed (only filtered)

    For example, imagine that you also want to sort your items. Then just add another filter :

    <template repeat="{{ item in items | filter(searchTerm) | sort(sortField, sortAsc)}}">
          <li>{{ item }}</li>
    </template>
    

    Your items will be filtered and then sorted (each time searchTerm, sortField or sortAsc change). Easy ;-)

    And the last tips: the previous code will filter your list at each keystroke which is not very user friendly!

    Instead you can use that kind of code:

    // Note that now I filter items on searchFilter and not more on searchTerm
    <template repeat="{{ item in items | filter(searchFilter) | sort(sortField, sortAsc)}}">
          <li>{{ item }}</li>
    </template>
    

    And then in your class

    @CustomTag('my-element')
    class MyElement extends PolymerElement {
    
      @observable List<String> items = toObservable(["Monday", "Tuesday", "Wednesday"]);
      @observable String searchTerm = '';
      @observable String searchFilter = '';
    
      MyElement.created() : super.created();
    
      filter(term) => (items) => term.isEmpty ? items : items.where((item) => item.toLowerCase().contains(searchTerm.toLowerCase())).toList();
    
      // When the user keystroke, searchTerm is changed
      // Launch a Timer (if the timer was not null then stop it: it means the user has not finished
      // When the delay is elapsed then set searchFilter with searchTerm
      // As your filter use 'filter(searchFilter)' then your items will be filtered
      Timer _searchTimer;
      searchTermChanged(oldValue) {
        // If there's another keystroke during the delay then reset it
        if (_searchTimer != null) _searchTimer.cancel();
        new Timer(new Duration(milliSeconds: 300), () => searchFilter = searchTerm);
      }
    }