В этой заключительной части нашего нокаутного мини-сериала мы добавим еще пару функций в простое приложение контактов, которое мы создали в течение последних двух уроков. Мы уже рассмотрели основные принципы библиотеки — привязку данных, шаблоны, наблюдаемые и зависимые наблюдаемые — так что эта часть объединит то, что мы изучили до сих пор.
Одной из функций, которые мы добавим в этой части, является возможность фильтровать отображаемый список контактов по первой букве их имени — довольно распространенная функция, которую может быть сложно выполнить вручную. Кроме того, читатель второй части этой серии спросил, насколько сложно было бы добавить функцию поиска с помощью Knockout, поэтому мы также добавим поле поиска в пользовательский интерфейс, которое позволит подмножеству контактов, которые соответствуют определенному поисковому запросу, отображаться. Давайте начнем.
Раунд 1 — Начало работы
Мы начнем с добавления новой разметки в представление. В файле index.html
из предыдущего урока добавьте следующую новую разметку в начало <body>
:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
|
<div id=»alphaFilter»>
<span>Filter name by:
<ul data-bind=»template: ‘letterTemplate'»></ul>
<a id=»clear» href=»#» title=»Clear Filter» data-bind=»click: clearLetter, css: { disabled: filterLetter() === » }»>Clear filter</a>
<fieldset id=»searchForm»>
<span>Search for:
<button data-bind=»click: setTerm, disable: filterTerm» type=»button»>Go</button>
<input id=»term»>
<a data-bind=»visible: filterTerm, click: clearTerm» title=»Clear search» href=»#»>x</a>
</fieldset>
</div>
<script id=»letterTemplate» type=»text/x-jquery-tmpl»>
{{each(i, val) letters}}
<li>
<a href=»#» title=»Filter name by ${ val }» data-bind=»click: function() {
filterLetter(val) },
css: { disabled: val === filterLetter() }»>
${ val }
</a>
</li>
{{/each}}
</script>
|
Мы начнем с простого внешнего контейнера для хранения наших новых элементов пользовательского интерфейса, которым мы даем id
для стилизации. Внутри находится <span>
содержащий пояснительную метку для букв, используемых для фильтрации контактов по имени, за которым следует пустой элемент <ul>
который мы привязываем к шаблону letters
с помощью атрибута data-bind
.
После списка есть ссылка; эта ссылка используется для очистки фильтра и имеет две привязки: первая — привязка click
, которая связана с методом в нашей viewModel
который мы добавим через мгновение. Второй привязкой является привязка css
, которая используется для добавления имени класса, disabled
к элементу, когда буква фильтрации не была выбрана.
Компонент поиска нашего пользовательского интерфейса использует <fieldset>
с id
(также для стиля), который содержит пояснительную текстовую метку, элемент <button>
который будет запускать поиск, <input>
, в который будет введен поисковый термин, и ссылка, которая может быть использована для очистки поиска.
<button>
использует click
и disable
привязки; привязка click
используется для запуска поиска, а disable
привязки отключит кнопку, когда значение filterTerm
равно пустой строке (что равно false
). Ссылка очистки также имеет две привязки: visible
и click
. visible
привязка используется для отображения ссылки только после выполнения поиска, а привязка click
используется для очистки поиска.
Затем мы добавляем шаблон letters
jQuery, который используется для создания писем, используемых для фильтрации по первой букве имени каждого контакта. Как и в случае с числовой подкачкой из предыдущего урока, мы используем здесь синтаксис jQuery tmpl
вместо tmpl
функциональности Knockout. Это означает, что весь шаблон будет перерисован при изменении одного из элементов, но в этом примере это не сильно влияет на производительность.
Мы используем шаблонный тег {{each}}
и будем использовать второй параметр val
, который передается шаблону для каждого элемента в массиве, который использует шаблон, который будет соответствовать первой букве имени каждого контакта (мы скоро мы увидим, как этот массив генерируется, когда мы обновим нашу viewModel
).
Для каждого элемента в array
мы создаем элемент <li>
и <a>
. Элемент <a>
использует параметр val
переданный в функцию шаблона, для установки атрибута title
ссылки и ее текстового содержимого. Мы также добавляем привязки click
и css
. Привязка click
устанавливает для filterLetter viewModel
(которое будет filterLetter viewModel
для наблюдения) значение ссылки, по которой был выполнен щелчок. Привязка css
просто добавляет disabled
класс так же, как мы делали с очищением <a>
, но на этот раз класс применяется, если значение текущего элемента равно filterLetter
.
Хотя вы не сможете запустить страницу в этот момент, компоненты фильтрации и поиска будут выглядеть так, как только необходимый код будет добавлен в viewModel
:
Раунд 2 — Обновление viewModel
Чтобы соединить элементы, которые мы только что добавили, нам сначала нужно добавить некоторые новые свойства и методы в нашу viewModel
. Они могут идти после метода navigate
из последней части руководства (не забудьте добавить запятую после navigate
):
01
02
03
04
05
06
07
08
09
10
11
12
|
filterLetter: ko.observable(«»),
filterTerm: ko.observable(«»),
clearLetter: function () {
this.filterLetter(«»);
},
clearTerm: function () {
this.filterTerm(«»);
$(«#term»).val(«»);
},
setTerm: function () {
this.filterTerm($(«#term»).val());
}
|
Нам также понадобятся несколько новых dependentObservables
наблюдаемых dependentObservables
, но мы добавим их чуть позже. Сначала мы добавляем два новых наблюдаемых свойства: filterLetter
, который используется для отслеживания текущей буквы, по которой выполняется фильтрация, и filterTerm
, который отслеживает текущий поисковый filterTerm
. Оба по умолчанию настроены на пустые строки.
Далее мы добавим несколько методов; первый метод, clearLetter
, устанавливает наблюдаемый filterLetter
обратно в пустую строку, которая очищает фильтр, а второй метод, clearTerm
, устанавливает наблюдаемый filterTerm
обратно в пустую строку, которая очищает поиск. Этот метод также удалит строку, введенную в текстовое поле в представлении. Последний новый метод, setTerm
, используется для получения строки, введенной в текстовое поле, и добавления ее в наблюдаемый filterTerm
.
Раунд 3 — Фильтрация по критерию поиска
Теперь, когда у нас есть некоторые новые наблюдаемые свойства, нам нужно добавить некоторые функции, которые будут отслеживать эти свойства и реагировать, когда их значения изменяются. Первый dependentObservable
используется для фильтрации полного набора контактов и возврата объекта, содержащего только контакты, содержащие поисковый запрос:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
viewModel.filteredPeopleByTerm = ko.dependentObservable(function () {
var term = this.filterTerm().toLowerCase();
if (!term) {
return this.people();
}
return ko.utils.arrayFilter(this.people(), function (person) {
var found = false;
for (var prop in person) {
if (typeof (person[prop]) === «string») {
if (person[prop].toLowerCase().search(term) !== -1) {
found = true;
break;
}
}
}
return found;
});
}, viewModel);
|
В рамках функции мы сначала сохраняем поисковый термин в нижнем регистре, чтобы при поиске не учитывался регистр. Если поисковый термин равен false
(если это пустая строка), функция вернет array
people
. Если есть arrayFilter()
поиска, мы используем функцию утилиты arrayFilter()
Knockout для фильтрации array
people
. Эта служебная функция принимает array
для фильтрации и анонимную функцию, которая будет выполняться для каждого элемента в фильтруемом array
.
В нашей анонимной функции мы сначала устанавливаем переменную flag в false
. Затем мы перебираем каждое свойство, которое содержит текущий элемент array
. Мы проверяем, что текущее свойство является строкой, и если так, мы определяем, содержит ли свойство поисковый термин. Это делается путем преобразования свойства в нижний регистр и последующего использования встроенного в JavaScript метода search()
. Если метод search()
не возвращает -1
, мы знаем, что совпадение найдено, и поэтому мы устанавливаем для нашей переменной-флага значение true
и выходим из цикла for
с помощью оператора break
.
После того, for
цикл for
завершен (или мы вырвались из него с совпадением), переменная flag будет возвращена и будет иметь значение true
или false
. arrayFilter
метод arrayFilter
будет включать элементы из исходного массива в массив, который он возвращает, если анонимная функция, выполненная для каждого элемента, возвращает true
. Это обеспечивает простой механизм возврата подмножества массива people
для использования другими dependentObservables
.
Раунд 4 — Создание фильтра писем
Наш следующий dependentObservable
объект используется для создания массива букв, который использует шаблон letters
для добавления буквенных ссылок в пользовательский интерфейс:
1
2
3
4
5
6
7
8
9
|
viewModel.letters = ko.dependentObservable(function () {
var result = [];
ko.utils.arrayForEach(this.filteredPeopleByTerm(), function (person) {
result.push(person.name.charAt(0).toUpperCase());
});
return ko.utils.arrayGetDistinctValues(result.sort());
}, viewModel);
|
В этом dependentObservable
мы сначала создаем пустой массив с именем result
. Мы используем arrayForEach
утилиты arrayForEach
Knockout для обработки каждого элемента в массиве, возвращенного предыдущим dependentObservable
arrayForEach
— arrayForEach
. Для каждого элемента мы просто помещаем первую букву свойства name
каждого элемента в верхнем регистре в массив result
. Затем мы возвращаем этот массив после прохождения его через метод утилиты Knockout arrayGetDistinctValues()
и сортируем его. Используемый здесь служебный метод фильтрует массив и удаляет любые дубликаты.
Раунд 5 — Фильтрация по письму
Последняя dependentObservable
наблюдательная filterLetter
нам нужно добавить, фильтрует контакты по буквам и запускается, когда наблюдаемая filterLetter
меняет значение:
01
02
03
04
05
06
07
08
09
10
|
viewModel.filteredPeople = ko.dependentObservable(function () {
var letter = this.filterLetter();
if (!letter) {
return this.filteredPeopleByTerm();
}
return ko.utils.arrayFilter(this.filteredPeopleByTerm(), function (person) {
return person.name.charAt(0).toUpperCase() === letter;
});
}, viewModel);
|
В этом dependentObservable
filterLetter
мы сначала сохраняем содержимое объекта filterLetter
наблюдаемого в массиве. Если переменная letter
равна false
(например, если это пустая строка), мы просто возвращаем массив, который возвращает метод filteredPeopleByTerm()
не изменяя его.
Если есть буква для фильтрации, мы снова используем метод утилиты arrayFilter()
чтобы отфильтровать массив, возвращаемый filteredPeopleByTerm
arrayFilter()
. На этот раз мы конвертируем первую букву свойства name
каждого элемента в верхний регистр и возвращаем, равно ли оно букве. Помните, что элементы останутся в массиве, который мы фильтруем, только если анонимная функция вернет true
.
Раунд 6 — Обновление пейджинга
В последнем уроке этой мини-серии мы добавили функцию подкачки, которая работала непосредственно с массивом people
. Если мы хотим, чтобы подкачка работала с нашей новой функцией фильтрации, нам нужно обновить showCurrentPage dependentObservable
в предыдущей статье. Все, что нам нужно сделать, — это изменить оператор return
в конце функции, чтобы он возвращал часть массива, возвращаемого filteredPeople() dependentObservable
вместо массива people
:
1
|
return this.filteredPeople().slice(startIndex, startIndex + this.pageSize());
|
На этом этапе мы теперь можем запустить страницу и отфильтровать отображаемые контакты по букве или поисковому запросу. Эти две новые функции не являются взаимоисключающими, поэтому мы можем отфильтровать контакты по букве, а затем выполнить поиск в отфильтрованном списке с помощью поискового запроса. Или наоборот — фильтрация искомого набора контактов. И наш пейджинг будет по-прежнему соответствовать текущему набору контактов.
Обзор после боя
В этой последней главе этой серии мы объединили то, что мы знаем об использовании Knockout, добавив функции фильтрации по буквам или поисковым словам, чтобы пользователи могли просматривать подмножество данных, viewModel
в viewModel
. Как и раньше, добавление этих новых функций в наше приложение намного проще сделать с Knockout, чем если бы мы пытались поддерживать наши представления и viewModels
вручную, используя только jQuery.
Наряду с простотой синхронизации нашего пользовательского интерфейса и данных, мы также получаем ряд служебных функций, включая arrayGetDistinctValues()
и arrayFilter()
которые мы можем использовать, чтобы сэкономить некоторое ручное кодирование при выполнении обычных задач.
Это подводит нас к концу серии, но я надеюсь, что это не конец вашего опыта с самим нокаутом; библиотека является фантастическим дополнением к инструментарию любого разработчика и значительно ускоряет создание интерактивных приложений с использованием JavaScript.