Dart Polymer: Как фильтровать элементы в повторяющемся шаблоне?

Я использую шаблон повторения Dart Polymer, чтобы показать элементы списка. Теперь я хотел бы реализовать фильтр поиска, в котором отображаются только элементы, относящиеся к введенному запросу. Рассмотрим следующий код:

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());
  }
} 

Например, когда я набираю «Пн» в текстовое поле, я ожидаю, что список элементов обновится автоматически, так что будет отображаться только «Понедельник». Однако при вводе любого поискового запроса список остается прежним, а поисковый запрос игнорируется.

Итак, как правильно реализовать такую ​​функцию?


person melmac    schedule 27.01.2014    source источник
comment
Что не работает? Вы проверили, устанавливается ли searchTerm и вызывается ли matchesSearchFilter?   -  person Günter Zöchbauer    schedule 27.01.2014
comment
Да, searchTerm установлен правильно. matchesSearchFilters вызывается при первой визуализации списка (но не при изменении searchTerm).   -  person melmac    schedule 27.01.2014
comment
Вы можете попробовать stackoverflow.com/questions/20004117 здесь. Я опубликовал еще один пример фильтра, который может помочь, если вы хотите попробовать этот маршрут stackoverflow.com/questions/20323815   -  person Günter Zöchbauer    schedule 27.01.2014


Ответы (2)


Вторая альтернатива - добавить фильтр:

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

а также

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

Элементы будут отфильтрованы перед использованием в цикле повтора. Лично я предпочитаю использовать фильтр вместо решения Günter, потому что:
- это просто
- централизованное
- вполне понятно, чтобы добавить другие фильтры с помощью оператора конвейера
- ваш список не изменяется (только фильтруется)

Например, представьте, что вы также хотите отсортировать свои предметы. Затем просто добавьте еще один фильтр:

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

Ваши элементы будут отфильтрованы, а затем отсортированы (каждый раз при изменении searchTerm, sortField или sortAsc). Легкий ;-)

И последний совет: предыдущий код будет фильтровать ваш список при каждом нажатии клавиши, что не очень удобно для пользователя!

Вместо этого вы можете использовать такой код:

// 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>

А потом в твоем классе

@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);
  }
} 
person Eric Taix    schedule 12.02.2014
comment
Отличное описание! Мне тоже нравятся фильтры, но в данном случае я о них не подумал. Я пока использовал их только для конвертации. - person Günter Zöchbauer; 13.02.2014
comment
Я думаю, мы можем использовать фильтр / трансформатор везде. Я написал двухчасовую кодовую лабораторию для нашей летной школы Дартса и использовал эту технику для фильтрации и сортировки фильмов. Репозиторий GitHub: github.com/eric-taix/movie-board, а также онлайн-демонстрация приложения: eric-taix.github.io/movie-board - person Eric Taix; 13.02.2014
comment
Да, хороший материал, Эрик! Но на самом деле строка фильтра должна быть filter(term) => (List<String> entries) => term.isEmpty [...], верно? В противном случае список не отображается. - person melmac; 14.02.2014

Альтернативным решением было бы составить список allItems, содержащий все элементы и когда searchTerm изменил обновление items, например:

  @observable List<String> items = ['Monday', 'Tuesday', 'Wdnesday'];

  void searchTermChanged(oldVal) {
    items = items.where((String item) {
      if (searchTerm.isEmpty) return true;
      return item.toLowerCase().contains(searchTerm.toLowerCase());      
    }).toList(growable: false);
  }

В этом случае вам не понадобится toObservable, поскольку он является избыточным для наблюдения за добавлением или удалением элементов. Чтобы увидеть, был ли заменен список items, достаточно атрибута @observable.

person Günter Zöchbauer    schedule 27.01.2014