Статьи

Контроль потока

Как мы видели в предыдущих уроках, проектирование представления для ViewModel похоже на создание HTML-шаблона для объекта JavaScript. Неотъемлемой частью любой системы шаблонов является способность контролировать поток выполнения шаблонов. Возможность циклически просматривать списки данных и включать или исключать визуальные элементы на основе определенных условий позволяет минимизировать разметку и дает вам полный контроль над отображением ваших данных.

Мы уже видели, как привязка foreach может проходить через наблюдаемый массив, но Knockout.js также включает две логические привязки: if и ifnot. Кроме того, с привязкой позволяет вручную изменять область блоков шаблона.

Этот урок знакомит с привязками потока управления Knockout.js, расширяя пример корзины покупок из предыдущего урока. Мы также рассмотрим некоторые нюансы foreach которые были затушеваны на последнем уроке.


Давайте начнем с более внимательного изучения нашего существующего цикла foreach :

1
2
3
4
5
6
7
<tbody data-bind=’foreach: shoppingCart’>
  <tr>
    <td data-bind=’text: name’></td>
    <td data-bind=’text: price’></td>
    <td><button data-bind=’click: $root.removeProduct’>Remove</button></td>
  </tr>
</tbody>

Когда Knockout.js встречает foreach в атрибуте data-bind , он выполняет итерацию по массиву shoppingCart и использует каждый найденный элемент для контекста привязки содержащейся разметки. Этот связывающий контекст — то, как Knockout.js управляет областью действия циклов. В этом случае именно поэтому мы можем использовать свойства name и price, не ссылаясь на экземпляр Product.


Использование каждого элемента в массиве в качестве нового контекста привязки является удобным способом создания циклов, но такое поведение также делает невозможным обращение к объектам вне текущего элемента в итерации. По этой причине Knockout.js делает несколько специальных свойств доступными в каждом контексте привязки. Обратите внимание, что все эти свойства доступны только в представлении , но не в модели представления.

Контекст $root всегда ссылается на ViewModel верхнего уровня, независимо от циклов или других изменений в области видимости. Как мы видели в предыдущем уроке, это позволяет получить доступ к методам верхнего уровня для управления ViewModel.

Свойство $data в контексте привязки ссылается на объект ViewModel для текущего контекста. Это очень похоже на ключевое слово this в объекте JavaScript. Например, внутри нашего цикла foreach: shoppingCart $ data ссылается на текущий элемент списка. В результате следующий код работает точно так же, как и без использования $ data:

1
2
<td data-bind=’text: $data.name’></td>
<td data-bind=’text: $data.price’></td>

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

1
2
3
4
5
6
function Product(name, price, tags) {
  this.name = ko.observable(name);
  this.price = ko.observable(price);
  tags = typeof(tags) !== ‘undefined’ ?
  this.tags = ko.observableArray(tags);
}

Затем определите некоторые теги для одного из продуктов в массиве shoppingCart :

1
new Product(«Buns», 1.49, [‘Baked goods’, ‘Hot dogs’]);

Теперь мы можем увидеть контекст $ data в действии. В <table>, содержащую наши элементы корзины покупок, добавьте элемент <td>, содержащий список <ul>, итерирующий по массиву tags :

01
02
03
04
05
06
07
08
09
10
11
12
13
  <tbody data-bind=’foreach: shoppingCart’>
      <tr>
        <td data-bind=’text: name’></td>
        <td data-bind=’text: price’></td>
        <td> <!— Add a list of tags.
          <ul data-bind=’foreach: tags’>
            <li data-bind=’text: $data’></li>
          </ul>
        </td>
        <td><button data-bind=’click: $root.removeProduct’>Remove</button></td>
      </tr>
    </tbody>
</table>

Внутри цикла foreach: tags , Knockout.js использует нативные строки «Выпечка» и «Хот-доги» в качестве связующего контекста. Но, поскольку мы хотим получить доступ к фактическим строкам вместо их свойств , нам нужен объект $ data.

Внутри цикла foreach свойство $index содержит $index текущего элемента в массиве. Как и большинство вещей в Knockout.js, значение $ index будет обновляться автоматически при добавлении или удалении элемента из связанного наблюдаемого массива. Это полезное свойство, если вам нужно отобразить индекс каждого элемента, например:

1
<td data-bind=’text: $index’></td>

Свойство $parent ссылается на родительский объект ViewModel. Как правило, это понадобится вам только тогда, когда вы работаете с вложенными циклами и вам нужен доступ к свойствам во внешнем цикле. Например, если вам нужен доступ к экземпляру Product из цикла foreach: tags, вы можете использовать свойство $ parent:

1
2
3
4
5
<ul data-bind=»foreach: tags»>
  <li>
    <span data-bind=»text: $parent.name»>
  </li>
</ul>

Между наблюдаемыми массивами, привязкой foreach и свойствами контекста привязки, обсуждавшимися ранее, у вас должны быть все инструменты, необходимые для использования массивов в ваших веб-приложениях Knockout.js.


Прежде чем перейти к условным привязкам, мы добавим свойство discount в наш класс Product:

1
2
3
4
5
6
7
8
function Product(name, price, tags, discount) {
  …
  discount = typeof(discount) !== ‘undefined’ ?
  this.discount = ko.observable(discount);
  this.formattedDiscount = ko.computed(function() {
    return (this.discount() * 100) + «%»;
  }, this);
}

Это дает нам условие, которое мы можем проверить с помощью логических привязок Knockout.js. Во-первых, мы делаем необязательный параметр discount , присваивая ему значение по умолчанию 0. Затем мы создаем заметку для скидки, чтобы Knockout.js мог отслеживать ее изменения. Наконец, мы определяем вычисляемую наблюдаемую величину, которая возвращает удобную для пользователя версию процента скидки.

Давайте продолжим и добавим 20% скидку на первый элемент в PersonViewModel.shoppingCart :

1
2
3
4
5
this.shoppingCart = ko.observableArray([
  new Product(«Beer», 10.99, null, .20),
  new Product(«Brats», 7.99),
  new Product(«Buns», 1.49, [‘Baked goods’, ‘Hot dogs’]);
]);

Привязка if является условной привязкой. Если переданный вами параметр оценивается как true, HTML-код будет отображаться, в противном случае он будет удален из DOM. Например, попробуйте добавить следующую ячейку в <table>, содержащую элементы корзины покупок, прямо перед кнопкой «Удалить».

1
2
3
<td data-bind=’if: discount() > 0′ style=’color: red’>
  You saved <span data-bind=’text: formattedDiscount’>
</td>

Все внутри элемента <td> будет отображаться только для товаров со скидкой больше 0 . Кроме того, поскольку скидка является наблюдаемой, Knockout.js будет автоматически переоценивать условие при его изменении. Это просто еще один способ, которым Knockout.js помогает вам сосредоточиться на данных, определяющих ваше приложение.

Рисунок 15: Условное предоставление скидки на каждый товар

Вы можете использовать любое выражение JavaScript в качестве условия: Knockout.js попытается оценить строку как код JavaScript и использовать результат, чтобы показать или скрыть элемент. Как вы уже догадались, привязка ifnot просто отрицает выражение.


Привязка с может использоваться для ручного объявления области действия конкретного блока. Попробуйте добавить следующий фрагмент вверху экрана перед кнопками «Оформить заказ» и «Добавить пиво»:

1
2
3
4
<p data-bind=’with: featuredProduct’>
  Do you need <strong data-bind=’text: name’></strong>?
  Get one now for only <strong data-bind=’text: price’></strong>.
</p>

Внутри блока with Knockout.js использует PersonViewModel.featuredProduct в качестве контекста привязки. Таким образом, привязки text: name и text: price работают, как ожидается, без ссылки на их родительский объект.

Конечно, чтобы предыдущий HTML работал, вам нужно определить свойство featuredProduct в PersonViewModel:

1
2
var featured = new Product(«Acme BBQ Sauce», 3.99);
this.featuredProduct = ko.observable(featured);

Этот урок представил foreach , if , if , и с привязками. Эти привязки потока управления дают вам полный контроль над тем, как ваша ViewModel отображается в представлении.

Важно понять связь между привязками Knockout.js и наблюдаемыми. Технически, эти два полностью независимы. Как мы видели в самом начале этой серии, вы можете использовать обычный объект с собственными свойствами JavaScript (т.е. не наблюдаемыми) в качестве вашей ViewModel, и Knockout.js будет правильно отображать привязки представления. Тем не менее, Knockout.js будет обрабатывать шаблон только в первый раз — без наблюдаемых, он не может автоматически обновлять представление при изменении базовых данных. Рассматривая весь смысл Knockout.js, вы обычно увидите, что привязки относятся к наблюдаемым свойствам, как наша привязка foreach: shoppingCart в предыдущих примерах.

Теперь, когда мы можем контролировать логику наших шаблонов представления, мы можем перейти к контролю внешнего вида отдельных элементов HTML. Следующий урок посвящен интересной части Knockout.js: привязкам внешнего вида.

Этот урок представляет собой главу от Knockout Succinctly , бесплатной электронной книги от команды Syncfusion .