Статьи

In the Ring с knockout.js: Часть 2 — реванш

В первой части этого урока мы собрали базовый пример списка контактов. Представление (видимое представление данных на странице) было создано с использованием шаблона, наряду с некоторыми другими элементами, привязанными к методам и свойствам viewModel с использованием атрибутов data-bind . Некоторые из значений в viewModel были наблюдаемыми и отслеживались на предмет изменений, которые затем автоматически распространялись на наше представление.

В этой части мы продолжим работу над примером, чтобы добавить дополнительную функциональность. Например, если в нашей модели было много контактов, полезной функцией для добавления была бы подкачка страниц, чтобы разбить представление на более управляемые разделы.


Сначала мы обновим наше представление, чтобы оно содержало новые элементы, необходимые для нашей подкачки страниц. Откройте файл index.html из первой части этого руководства и добавьте следующий новый код в контейнер #people сразу после элемента <a> «Добавить нового человека»:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
<nav id=»paging»>
    <a id=»all» href=»#» data-bind=»click: function () { pageSize(ko.utils.unwrapObservable(people).length); currentPage(0); }, css: { disabled: pageSize() === ko.utils.unwrapObservable(people).length }»>Show all</a>
    <a id=»last» title=»Last Page» href=»#» data-bind=»click: function() { currentPage(totalPages() — 1); }, css: { disabled: currentPage() === totalPages() — 1 }»>Last</a>
    <a id=»next» title=»Next Page» href=»#» data-bind=»click: function (event) { navigate(event) }, css: { disabled: currentPage() === totalPages() — 1 }»>»</a>
    <ul data-bind=»template: ‘pagingTemplate'»></ul>
    <a id=»prev» title=»Previous Page» href=»#» data-bind=»click: function (event) { navigate(event) }, css: { disabled: currentPage() === 0 }»>«</a>
    <a id=»first» title=»First Page» href=»#» data-bind=»click: function() { currentPage(0); }, css: { disabled: currentPage() === 0 }»>First</a>
</nav>
<label id=»pageSize»>Show <input data-bind=»value: pageSize» /> per page</label>
<script id=»pagingTemplate» type=»text/x-jquery-tmpl»>
    {{each(i) ko.utils.range(1, totalPages)}}
        <li>
            <a href=»#» title=»View page ${ i + 1 }» data-bind=»click: function() { currentPage(i) }, css: { on: i === currentPage() }»>${ i + 1 }</a>
        </li>
    {{/each}}
<script>

Первый из наших новых элементов — это <nav> , который содержит некоторые служебные ссылки, в том числе;

  • ссылка, чтобы показать все контакты,
  • один, чтобы показать предыдущую страницу контактов,
  • один, чтобы показать следующую страницу контактов,
  • один, чтобы показать первую страницу и
  • другой, чтобы показать последнюю страницу.

У нас также есть пустой элемент <ul> который будет заполнен количеством ссылок, соответствующих количеству страниц. Все эти элементы имеют ряд специальных нокаутирующих привязок.

Ссылка, используемая для одновременного отображения всех контактов из viewModel , использует привязки click и css . Мы видели привязку click в первой части этой мини-серии, поэтому мы знаем, что она выполняет функцию всякий раз, когда щелкает связанный элемент. В первой части мы использовали имена методов, определенных в нашей viewModel , но, как вы можете видеть, мы также можем указывать встроенные функции.

Указанная нами встроенная функция просто обновляет наблюдаемое свойство pageSize для viewModel (которое мы еще не определили, но вскоре viewModel ). Мы устанавливаем значение, равное количеству отдельных элементов в нашем массиве people . Мы также устанавливаем другое наблюдаемое свойство (свойство currentPage ) на 0 как при отображении всех контактов отображается только одна страница.

В этой привязке мы используем другую вспомогательную функцию нокаута — функцию unwrapObservable . Мы должны использовать это, потому что наш массив people не просто обычный массив JavaScript — это наблюдаемый массив. Таким образом, чтобы получить базовый фактический массив и проверить его свойство length нам нужно развернуть его. Нам не нужно использовать эту утилиту для проверки значения стандартных наблюдаемых свойств.

Привязка css особенно полезна и позволяет нам установить класс для элемента, если условие возвращает true . В этом случае мы проверяем, является ли наблюдаемое свойство pageSize равным количеству объектов в массиве people . Если это так, т. Е. Если отображаются все люди, то disabled имя класса будет добавлено к элементу.

Следующая, предыдущая, первая и последняя ссылки также имеют привязки click и css . Показать первую страницу очень просто: мы просто устанавливаем для свойства currentPage значение 0 чтобы показать первую страницу. Привязка css применяет disabled класс, когда свойство currentPage равно 0 . Последняя ссылка показывает последнюю страницу, установив в свойстве currentPage общее количество страниц. Он добавляет disabled класс, когда значение currentPage равно общему количеству страниц.

Привязка click для предыдущей и следующей ссылок указывает на один и тот же метод viewModelnavigate , и объект события также передается этому методу. Мы увидим, как этот метод работает через некоторое время, когда мы обновим viewModel . Оба этих элемента также используют привязку css чтобы определить, применять ли disabled класс или нет. Следующая ссылка получит имя класса, если в данный момент отображается последняя страница, а предыдущая ссылка получит имя класса, если отображается первая страница.

Привязка value особенно полезна, поскольку мы можем использовать ее для установки значения по умолчанию для <input> в нашем представлении, а также для того, чтобы пользователь мог легко изменить соответствующее значение свойства в viewModel — это двусторонняя привязка.

Элемент <ul> заполняется с использованием шаблона jQuery; в отличие от шаблона, использованного в первой части, мы не используем свойства name и foreach knockout для указания шаблона, мы просто используем имя шаблона.

После элемента <nav> мы также добавили <label> содержащий некоторый текст и <input> . <input> использует привязку value , которая устанавливает значение элемента <input> в свойство нашего viewModel . Привязка value особенно полезна, поскольку мы можем использовать ее для установки значения по умолчанию для <input> в нашем представлении, а также для того, чтобы пользователь мог легко изменить соответствующее значение свойства в viewModel — это двусторонняя привязка.

Наконец, мы добавили шаблон, который будет использоваться для отображения пронумерованных ссылок, которые указывают на каждую страницу данных. В этом шаблоне мы используем собственный шаблонный тег {{each}} tmpl, чтобы выполнить итерацию заданное число раз, которое вычисляется с помощью функции полезности удаления range . Эта функция принимает минимальное число диапазона, viewModel 1 в этом примере, и максимальное число, которое мы вернем, используя метод нашей viewModel .

Каждая ссылка на страницу состоит из <li> содержащего <a> . <a> присваивается хэшированная ссылка (нокаут автоматически останавливает браузер по этой ссылке) и title который указывает номер страницы. Каждая ссылка использует привязку click чтобы установить для свойства currentPage количество щелчков по любой ссылке (на основе 0) и привязку css для добавления имени класса, если текущая ссылка равна свойству currentPage .

Нам также нужно внести небольшие изменения в существующий контейнер #people . Нам нужно изменить свойство foreach привязки шаблона, чтобы оно viewModel на новый метод в нашей viewModel именем showCurrentPage .

Таким образом, это дает нам все элементы, необходимые для добавления полнофункциональной подкачки в наше представление. Дополнительный CSS также необходим для отображения новых элементов, как и предполагалось, но в духе предыдущей части этого мини-сериала я не буду утомлять вас спецификой, поскольку это просто базовый CSS. Любопытные из вас могут взглянуть на файлы примеров.


Чтобы наши новые элементы подкачки viewModel , нам нужно добавить соответствующие свойства и методы, которые мы использовали в наших привязках, к viewModel . Непосредственно после метода addPerson в viewModel добавьте следующий код:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
currentPage: ko.observable(0),
pageSize: ko.observable(5),
navigate: function (e) {
    var el = e.target;
 
    if (el.id === «next») {
        if (this.currentPage() < ko.utils.unwrapObservable(this.totalPages()) — 1) {
            this.currentPage(this.currentPage() + 1);
        }
    } else {
        if (this.currentPage() > 0) {
            this.currentPage(this.currentPage() — 1);
        }
    }
}

Это не весь новый код, который нам понадобится, но сейчас мы кратко рассмотрим эти простые дополнения, прежде чем перейти к оставшемуся коду. Первые два новых свойства являются наблюдаемыми с простыми числовыми значениями. Свойство currentPage определяет, какая страница отображается в данный момент, а pageSize определяет, сколько контактов отображается на каждой странице.

Далее мы определяем метод navigate , который используется нашими предыдущими и последующими ссылками для последовательной навигации по каждой странице. В привязке мы передали объект события, поэтому в методе мы сначала сохраняем target свойство объекта события, которое соответствует элементу, по которому щелкнули.

Knockout автоматически устанавливает this чтобы ссылаться на нашу viewModel когда мы находимся внутри любого из наших viewModel .

Мы проверяем атрибут id элемента и, если была нажата следующая ссылка, мы сначала проверяем, не находимся ли мы уже на последней странице, и, если нет, увеличиваем значение свойства currentPage на единицу. Если id элемента не next он должен иметь значение prev , поэтому в этом случае мы проверяем, что мы еще не на первой странице, и если нет, мы уменьшаем значение свойства currentPage .

Вы заметите, что мы используем ключевое слово this в нашем методе navigate . Knockout автоматически устанавливает this чтобы ссылаться на нашу viewModel когда мы находимся внутри любого из наших viewModel . В качестве альтернативы, мы можем ссылаться на viewModel по ссылке (как мы это делали в первой части этой серии).

Теперь нам нужно добавить три дополнительных метода в viewModel , но из-за их природы мы не можем добавить их встроенными в другие свойства и методы, которые мы добавили до сих пор.


Зависимые наблюдаемые являются еще одной центральной опорой knockout.js, позволяющей нам создавать отношения между элементами в нашей viewModel .

Например, если у нас есть два отдельных свойства в нашей viewModel , мы можем добавить специальный метод, который отслеживает их оба и выполняет при изменении любого из них. Это позволяет нам создавать невероятно интерактивные интерфейсы всего за несколько строк кода!

Непосредственно после viewModel (но перед методом applyBindings ) добавьте следующие три метода:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
viewModel.totalPages = ko.dependentObservable(function () {
       return Math.ceil(ko.utils.unwrapObservable(this.people).length / this.pageSize());
   }, viewModel);
 
   viewModel.showCurrentPage = ko.dependentObservable(function () {
       if (this.currentPage() > Math.ceil(ko.utils.unwrapObservable(this.people).length / this.pageSize())) {
           this.currentPage(ko.utils.unwrapObservable(this.totalPages()) — 1);
       }
       var startIndex = this.pageSize() * this.currentPage();
       return this.people.slice(startIndex, startIndex + this.pageSize());
   }, viewModel);
 
   viewModel.numericPageSize = ko.dependentObservable(function () {
       if (typeof (this.pageSize()) !== «number») {
           this.pageSize(parseInt(this.pageSize()));
       }
   }, viewModel);

Нам нужно определить эти методы вне нашей viewModel потому что они являются зависимыми наблюдаемыми; при определении функций мы передаем нашу viewModel в функцию viewModel чтобы ключевое слово this все еще viewModel на нее.

Первый метод — totalPages , служебный метод, который просто возвращает количество требуемых страниц на основе количества элементов в массиве people разделенного на pageSize property . Размер pageSize будет изменяться в зависимости от значения, введенного в текстовом поле, но поскольку этот метод будет контролировать размер страницы, он всегда будет возвращать правильное количество страниц.

Второй метод — showCurrentPage , который мы и переключили на использование нашего основного шаблона. В рамках этого метода мы сначала выполняем быструю проверку того, что свойство totalPages не превышает значение, возвращаемое методом totalPages . Поначалу его требование может быть неочевидным, но оно предотвращает проблему, когда в некоторых ситуациях отображается пустая страница, например, если вы находитесь на последней странице данных, когда отображаете только пару элементов на страницу, а затем увеличиваете количество элементов на странице существенно. Если currentPage больше чем totalPages , мы просто переключаем currentPage на последнюю страницу.

После завершения этой проверки и возможных корректирующих действий мы определяем индекс, с которого следует начинать выбирать элементы из массива people путем умножения свойств pageSize и pageSize .

Наш последний метод — это еще одна утилита, которая отслеживает свойство pageSize . Всякий раз, когда это свойство меняет значение, метод проверяет, что числовое значение все еще является числом. Если это больше не число, метод преобразует его обратно в число. Помните, что ввод текста в нашем представлении привязан к свойству pageSize , и если этот ввод используется для обновления значения, оно станет строкой, что приведет к сбою некоторых других наших функций.

Наше демо теперь завершено; если мы запустим страницу в браузере, мы должны иметь полностью функциональную, многофункциональную нумерацию страниц, которая полностью работает на внешней стороне, всего с 30 строками кода! Я писал на стороне клиента пейджинг без Knockout раньше, и потребовалось гораздо больше кода, чем это. Вот как это должно выглядеть:

Изображение Tuts

В этой части нокаут-мини-серии мы добавили подкачку в существующее приложение. Это была функция, которая была добавлена ​​после создания первоначального приложения с небольшим изменением существующего кода. Добавление новых функций и возможностей в существующую кодовую базу действительно легко с Knockout.

Шаблонирование является основной функцией нокаута.

Сначала мы добавили некоторые новые привязки и шаблоны на наш взгляд. Несмотря на то, что мы рассмотрели шаблонизацию в первой части этой серии, стоит отметить, что использование шаблонов является основной функцией нокаута. Мы увидели, что мы можем использовать встроенную шаблонную функциональность jQuery tmpl вместе с шаблонными функциями Knockout без каких-либо проблем. Мы также рассмотрели функцию unwrapObservable которая используется для получения базового массива, когда массив является наблюдаемым. Knockout поставляется с целым рядом этих служебных функций, поэтому вам следует ознакомиться с незавершенной версией библиотеки, чтобы узнать, какие еще полезности она содержит.

Мы также рассмотрели заключительную центральную особенность нокаута — создание отношений между нашими свойствами viewModel и методами с использованием dependantObservables . DependantObservables используются для обновления нашей viewModel всякий раз, когда viewModel одно или несколько свойств, и работают, отслеживая изменения в viewModel . Эти методы отлично подходят для реагирования на взаимодействие наших посетителей.

Это делает для нас очень много работы, хотя в этом базовом примере мы на самом деле только поверхностно продемонстрировали, на что он способен. Что вы думаете о knockout.js? Дайте мне знать в комментариях и большое спасибо за чтение!