Клиентские библиотеки

Встроенные в Knockout привязки для HTML и JavaScript

Джон Папа

 

John PapaKnockout предоставляет богатую реализацию связывания с данными для разработок на основе HTML5 и JavaScript. Как только вы поняли смысл наблюдаемых объектов (observables), приступать к разработке с применением Knockout лучше всего так: изучить разнообразные встроенные привязки, предлагаемые этой библиотекой. Встроенные привязки Knockout — самый простой способ нащупать средства связывания с данными и добавить надежное связывание с данными в ваше приложение HTML5. В прошлой статье мы познакомились с Knockout, рассмотрели различные типы наблюдаемых объектов и изучили встроенные управляющие привязки (control of flow built-in bindings). На этот раз мы подробно поговорим о встроенных привязках Knockout. Примеры кода (которые можно скачать для этой статьи) демонстрируют, как и для чего использовать различные встроенные привязки.

Новейшую версию Knockout (текущая версия — 2.0.0) можно скачать с bit.ly/scmtAi и сослаться на нее в своем проекте или воспользоваться расширением NuGet Package Manager для Visual Studio (доступным по ссылке bit.ly/dUeqlu).

Что такое встроенные привязки в Knockout?

На самом базовом уровне для связывания с данными требуется источник привязки (binding source) (например, JavaScript-объект) и мишень (скажем, HTML-элемент). Источник привязки часто называют моделью представления. У целевого элемента может быть несколько свойств, поэтому важно знать, с каким именно целевым свойством должно быть осуществлено связывание. Так, если вы хотите свойство firstName модели представления со значением text тега input, вам нужно использовать Knockout-привязку value. В этом случае Knockout идентифицирует целевое свойство через одну из своих встроенных привязок: value. Встроенные привязки Knockout позволяют осуществлять связывание со свойствами, а также с методами модели представления. В Knockout много встроенных привязок, которые связывают свойства модели представления с целевыми элементами.

Синтаксис для использования встроенных привязок требует указывать пары из имени Knockout-привязки и свойства модели представления внутри свойства data-bind HTML-элемента. Если вы хотите связать данные более чем с одним свойством в HTML-элементе, просто разделяйте привязки запятыми, используя такой синтаксис:

data-bind="built-in-binding:viewmodel-property1, another-built-in-binding:viewmodel-property2"

Следуя этому шаблону, вы могли бы связать значение элемента input со свойством salePrice модели представления, например:

<input type="text" data-bind="value:salePrice " />

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

Основополагающие привязки: text и html

Давайте подробнее рассмотрим встроенные привязки Knockout. На рис. 1 показана модель представления, которая будет использоваться во всех примерах в этой статье. Образцы данных взяты для гитары, но они лишь демонстрируют привязки.

Рис. 1. Модель представления со свойствами, вложенными дочерними элементами и методами

my.showroomViewModel = {

  id: ko.observable("123"),

  salePrice: ko.observable(1995),

  profit: ko.observable(-7250),

  rating: ko.observable(4),

  isInStock: ko.observable(true),

  model: {

          code: ko.observable("314ce"),

          name: ko.observable("Taylor 314 ce")

  },

  colors: ko.observableArray([

          { key: "BR", name: "brown" },

          { key: "BU", name: "blue" },

          { key: "BK", name: "black"}]),

  selectedColor: ko.observable(""),

  selectedColorsForDropdown: ko.observableArray([]),

  selectedColorForRadio: ko.observableArray(),

  allowEditing: ko.observable(true),

  isReadonly: ko.observable(true),

    onSalesFloor: ko.observable(true),

    qty: ko.observable(7),

  photoUrl: ko.observable("/images/314ce.png"),

  url: ko.observable("http://johnpapa.net"),

  details: ko.observable("<strong><em>This guitar rocks!</em></strong>"),

  checkboxHasFocus: ko.observable(false),

  textboxHasFocus: ko.observable(false),

  buttonHasFocus: ko.observable(false),

  userInput: ko.observable(""),

  setFocusToCheckbox: function () {

          this.checkboxHasFocus(true);

  },

  displayValue: function () {

          if (this.userInput().length > 0) {

                  window.alert("You entered: " + this.userInput());

          }

  },

  detailsAreVisible: ko.observable(false),

  showDetails: function () {

          this.detailsAreVisible(true);

  },

  hideDetails: function () {

          this.detailsAreVisible(false);

  },

  useUniqueName: ko.observable(true)

};

ko.applyBindings(my.showroomViewModel);

По-видимому, самая универсальная привязка — text. Когда Knockout видит привязку text, он устанавливает свойство innerText для Internet Explorer или эквивалентное свойство в других браузерах. При использовании привязки text любой предыдущий текст будет перезаписан. Эта привязка часто используется для отображения значений в тегах span или div. В следующем примере свойство model.code модели представления связывается с тегом span:

<span data-bind="text: model.code"></span>

Привязка html применяется не столь часто, но очень удобна для рендеринга HTML-контента в модели представления. В следующем примере осуществляется рендеринг содержимого свойства html, что приводит к выделению текста полужирным курсивом:

<tr>

  <td><div class="caption">html</div></td>

  <td><div data-bind="html: details"></div></td>

  <td><span>details: </span><span data-bind="text: details"></span></td>

</tr>

Результаты выполнения этих примеров показаны на рис. 2 (все примеры можно посмотреть, запустив страницу 04-builtin-bindings.html в прилагаемом пакете для скачивания).

The Knockout Text and HTML Bindings
Рис. 2. Knockout-привязки text и html

Привязка value

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

Привязка value работает со многими типами HTML-элементов input для связывания свойства модели представления непосредственно со значением HTML-элемента input, например текстового поля, флажка или кнопки-переключателя. В следующем примере свойство model.code модели представления связано с текстовым полем (textbox). Свойство определено с использованием Knockout-функции observable, которая заставляет это свойство уведомлять мишень об изменении значения в источнике:

<td><input type="text" data-bind="value: model.code"/></td>

<td><span>model.code: </span><span data-bind="text: model.code"></span></td>

Если пользователь изменяет значение в текстовом поле, новое значение посылается от мишени (текстового поля) источнику (свойству model.code модели представления), как только фокус ввода покидает это поле. Однако вы могли бы также использовать специальную Knockout-привязку, чтобы сообщать Knockout о необходимости обновлять источник значением мишени при каждом нажатии клавиши. В следующем примере значение текстового поля связывается со свойством salePrice модели представления, а привязка valueUpdate связывается с afterkeydown; привязка valueUpdate выступает в роли параметра для привязки value, помогая определять, когда должна обновляться привязка value. Вот как в коде сообщается Knockout о необходимости обновлять источник после каждого нажатия клавиши «стрелка вниз» (вы можете опробовать этот пример, запустив образец кода; результаты показаны на рис. 3):

<td><input type="text" data-bind="value: model.code"/></td>

<td><span>model.code: </span><span data-bind="text: model.code"></span></td>

The Value Binding to Textboxes
Рис. 3. Использование привязки value с текстовыми полями

Связывание данных с флажками и кнопками-переключателями

Флажки можно связывать с данными через Knockout-привязку checked. Эта привязка должна быть связана со свойством или выражением, которое оценивается как true или false. Поскольку свойства модели представления определены как наблюдаемые, флажок обновляется, когда изменяется свойство источника. Аналогично, когда пользователь устанавливает или сбрасывает флажок, обновляется значение свойства модели представления. В следующем примере показан флажок, связанный со свойством isInStock (из модели представления на рис. 1; результаты представлены на рис. 4):

<td><input type="checkbox" data-bind="checked: isInStock"/></td>

<td><span>isInStock: </span><span data-bind="text: isInStock"></span></td>

The Checked Binding
Рис. 4. Привязка checked

Вы можете также использовать привязку checked для выбора переключателя из группы кнопок-переключателей. В следующем примере показан набор кнопок-переключателей, значения которых «зашиты» в виде двухбуквенных кодов, представляющих цвета; когда пользователь выбирает цвет кнопкой-переключателем, устанавливается свойство checked и свойство selectedColorForRadio модели представления обновляется двухбуквенным значением:

<td>

  <input type="radio" value="BR" data-bind=

    "checked:  selectedColorForRadio" /><span>brown</span>

  <input type="radio" value="BU" data-bind=

    "checked: selectedColorForRadio" /><span>blue</span>

  <input type="radio" value="BK" data-bind=

    "checked: selectedColorForRadio" /><span>black</span>

</td>

<td><span>selectedColorForRadio: </span><span data-bind=

  "text: selectedColorForRadio"></span></td>

Хотя это нормально работает, я считаю, что с набором кнопок-переключателей лучше связывать список цветов; это можно реализовать, комбинируя три встроенные привязки: value, checked и foreach. В модели представления на рис. 1 есть свойство colors — массив объектов, каждый из которых содержит название цвета и значение ключа. Привязка foreach в очередном примере обеспечивает перебор свойства-массива colors в цикле, присваивая привязку value каждой кнопки-переключателя свойству key цвета и привязку text тега span свойству name цвета:

<td>

  <div data-bind="foreach: colors">

    <input type="radio" data-bind=

      "value:key, checked: $parent.selectedColorForRadio" />

       <span data-bind="text: name"></span>

  </div>

</td>

<td><span>selectedColorForRadio: </span>

  <span data-bind="text: selectedColorForRadio"></span></td>

Привязка checked кнопки-переключателя присваивается свойству selectedColorForRadio модели представления с использованием функции $parent. Однако эту привязку нельзя просто-так напрямую связать с этим свойством, так как привязка foreach меняет контекст с модели представления на свойство colors. Для корректного связывания со свойством модели представления код должен ссылаться обратно на предка контекста (в данном случае на саму модель представления). Knockout-функция $parent сообщает Knockout ссылаться на один уровень в иерархии контекстов выше, благодаря чему осуществляется связывание привязки checked со свойством selectedColorForRadio модели представления. (Таких полезных функций в этой библиотеке много, и мы поговорим о них в будущих статьях.) Результаты работы этого примера показаны на рис. 5.

The Checked and Value Bindings Used in Radio Buttons
Рис. 5. Привязки checked и value, используемые в кнопках-переключателях

Связывание данных с раскрывающимися списками

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

Привязка options идентифицирует список отображаемых значений — обычно из какого-либо свойства-массива модели представления. Пример в этом разделе устанавливает привязку options для свойства colors модели представления. Иногда в раскрывающемся списке требуется показывать одно значение, но использовать другое, когда пользователь выбирает элемент из списка. В этом помогают встроенные привязки optionsText и optionsValue. Первая присваивается строковому имени свойства для отображения в раскрывающемся списке, и значение извлекается через привязку options. Вторая присваивается строковому имени свойства для связывания с выбранным значением в раскрывающемся списке. В данном примере массив colors содержит объекты со свойствами name и key, из которых извлекаются имя, используемое для optionsText, и ключ для optionsValue. Привязка value присваивается свойству selectedColor модели представления, где сохраняется выбор пользователя:

<td>

  <div class="caption">options, value, optionsText, optionsValue</div>

  <div>select (single selection dropdowns)</div>

</td>

<td><select data-bind="options: colors, value: selectedColor,

   optionsText: 'name', optionsValue: 'key'" ></select></td>

<td><span>selectedColor: </span><span data-bind="text: selectedColor"></span></td>

Если вам нужно разрешить множественный выбор в раскрывающемся списке, вы сначала добавляете свойство multiple для HTML-элемента select, а затем заменяете Knockout-привязку selectedOption (единственное число) на selectedOptions (множественное число):

<td>

  <div class="caption">options, selectedOptions, optionsText, optionsValue</div>

  <div>select (multiple selection dropdowns)</div>

</td>

<td><select data-bind="options: colors,selectedOptions: selectedColorsForDropdown,

  optionsText: 'name', optionsValue: 'key'" multiple="multiple" ></select></td>

<td><span>selectedColorsForDropDown: </span><span data-bind=

  "text: selectedColorsForDropdown"></span></td>

Поскольку HTML-элемент select поддерживает множественный выбор, свойство selectedColorsForDropdown модели представления (которому присвоена встроенная привязка selectedOptions) будет содержать разделяемый запятыми список выбранных значений.

На рис. 6 показаны результаты выбора синего и черного цветов. Заметьте, что раскрывающиеся списки отображают название цвета (blue и black), но в качестве выбранных значений используют ключи (BU и BK).


Рис. 6. Связывание с раскрывающимися списками

Включение и отключение элементов ввода

Knockout предоставляет встроенные привязки для включения и отключения элементов ввода. Привязка enable включает элемент input, если связанное с ним свойство оценивается как true, и отключает этот элемент, если оно оценивается как false. Привязка disable делает прямо противоположное:

<td>

  <input type="checkbox" data-bind="checked: allowEditing"/>

  <input type="text" data-bind="enable: allowEditing, value:salePrice" />

</td>

<td><span>allowEditing: </span><span data-bind="text: allowEditing"></span></td>

Этот пример кода демонстрирует, что значение флажка связано со свойством allowEditing модели представления, которое также связано с привязкой enable текстового поля. Поэтому, когда флажок установлен, текстовое поле включено (доступно), а когда флажок сброшен — оно недоступно.

И напротив, следующий пример иллюстрирует, как привязка checked флажка связывается со свойством isReadonly модели представления, которое также связано с привязкой disable текстового поля. Поэтому, когда флажок помечен, текстовое поле недоступно (результаты для обоих примеров можно увидеть на рис. 7):

<td>

  <input type="checkbox" data-bind="checked: isReadonly"/>

  <input type="text" data-bind="disable: isReadonly, value:salePrice" />

</td>

<td><span>is readonly: </span><span data-bind="text: isReadonly"></span></td>

Bindings for Enabling and Disabling Elements
Рис. 7. Привязки для включения и отключения элементов

Привязка, устанавливающая фокус

В Knockout есть встроенная привязка hasfocus, которая позволяет устанавливать фокус на элемент и определять, какой элемент находится в фокусе. Эта привязка удобна, когда вам нужно установить фокус на конкретный элемент на форме. Если у нескольких элементов имеется привязка hasfocus со значениями, которые оцениваются как true, то фокус будет установлен на элемент, который получил такую привязку самым последним. Вы можете присвоить привязке hasfocus ключевое слово true, чтобы напрямую переместить фокус на нужный элемент. Или связать ее с каким-нибудь свойством модели представления, как показано на рис. 8.

Рис. 8. Установка привязки hasfocus

<td>

  <input type="checkbox" data-bind="hasfocus: checkboxHasFocus"/>

  <input type="text" data-bind="hasfocus: textboxHasFocus"/>

  <button data-bind="click: setFocusToCheckbox, hasfocus:buttonHasFocus">

    set focus to checkbox</button>

  <br/>

  <span data-bind="visible: checkboxHasFocus">checkbox has focus</span>

  <span data-bind="visible: textboxHasFocus">textbox has focus</span>

  <span data-bind="visible: buttonHasFocus">button has focus</span>

</td>

<td>

  <span>checkboxHasFocus: </span><span data-bind="text: checkboxHasFocus">

    checkbox has focus</span>

  <br/>

  <span>textboxHasFocus: </span><span data-bind="text: textboxHasFocus">

    textbox has focus</span>

  <br/>

  <span>buttonHasFocus: </span><span data-bind="text: buttonHasFocus">

    button has focus</span>

</td>

Этот код присваивает привязки hasfocus для флажка, текстового поля и кнопки трем разным свойствам модели представления. Когда фокус устанавливается на одном из этих HTML-элементов, соответствующая привязка hasfocus задает true в свойстве модели представления для этого элемента (остальным присваивается false). Вы можете сами опробовать этот код из пакета для скачивания или посмотреть результаты на рис. 9, где пользователь поместил фокус на текстовое поле.

{Рисунок}

Binding for Setting the Focus
Рис. 9. Привязка для установки фокуса

Привязка, определяющая видимость

Knockout-привязку visible нужно связывать со свойством, которое оценивается как true или false. Эта привязка указывает стиль отображения visible для элемента, если свойство равно true (либо true, либо ненулевое значение), или none, если свойство равно false (false, 0, неопределенное значение или null).

В следующем примере привязка checked флажка и привязка visible текстового поля вместе присваиваются свойству onSalesFloor модели представления. Если флажок помечен, свойство onSalesFloor получает значение true и текстовое поле становится видимым. А если флажок сброшен, то же свойство устанавливается в false и текстовое поле скрывается (рис. 10):

<td>

  <input type="checkbox" data-bind="checked: onSalesFloor"/>

  <input type="text" data-bind="visible: onSalesFloor, value:qty" />

</td>

<td>

  <span>onSalesFloor: </span><span data-bind="text: onSalesFloor"></span>

</td>

Binding for Visibility
Рис. 10. Привязка, определяющая видимость

Связывание с событиями

Knockout поддерживает связывание с любым событием через встроенную привязку event, но, кроме того, имеет специализированные встроенные привязки click и submit. Привязку click для элемента следует использовать, когда вам нужно связать событие click с каким-либо методом в модели представления. Чаще всего она применяется для кнопок и элемента, но может быть задействована с любым HTML-элементом.

В следующем коде привязка click кнопки присваивается методу displayValue в модели представления; на рис. 1 видно, что этот метод просто отображает значение свойства userInput (привязанное к текстовому полю) с оповещением:

<td>

  <input type="text" data-bind="value: userInput"/>

  <button data-bind="click: displayValue">display value</button>

</td>

<td>

  <span>userInput: </span><span data-bind="text: userInput"></span>

</td>

Когда некий метод модели представления требуется связать с событием, отличным от click, вы можете использовать Knockout-привязку event. Привязка click, наиболее часто используемая для событий, — просто сокращение привязки event.

Привязка event обеспечивает связывание с любым событием. Чтобы задействовать ее, передайте объектный литерал, который содержит пары «имя-значение» для имени события и метода модели представления, разделенные запятыми. В следующем примере код устанавливает встроенную привязку event так, чтобы события mouseover и mouseout были связаны с методами showDetails и hideDetails в модели представления. Эти методы устанавливают наблюдаемое свойство detailsAreVisible соответственно в true или false:

<td>

  <div data-bind="text:model.code, event: {mouseover: showDetails,

    mouseout: hideDetails}"></div>

  <div data-bind="visible: detailsAreVisible" style="background-color: yellow">

    <div data-bind="text:model.name"></div>

    <div data-bind="text:salePrice"></div>

  </div>

</td>

<td>

  <span>detailsAreVisible: </span><span data-bind="text: detailsAreVisible"></span>

</td>

Второй тег div присваивает привязку visible свойству details­AreVisible модели представления, поэтому, когда пользователь перемещает курсор мыши над первым тегом div, содержимое второго div становится видимым. Когда курсор мыши покидает первый div, второй div становится невидимым. Результаты представлены на рис. 11. Привязка submit (не показана на рис. 11) принимает любой жест ввода, который приводит к передаче HTML-формы.

The Click and Event Bindings
Рис. 11. Привязки click и event

Связывание стилей

Вы можете связывать стили с Knockout, используя встроенные привязки css и style. Привязке css можно присвоить одно или более допустимых имен css-классов. В следующем примере привязка value текстового поля присваивается свойству profit модели представления, а его привязка css — объектному литералу. Этот объектный литерал содержит одно или более имен css-классов и соответствующее выражение, которое должно оцениваться как true или false:

<td>

  <input data-bind="value:profit, css: {negative: profit() < 0,

    positive: !(profit() < 0), }"/>

</td>

<td>

  <span>profit < 0: </span><span data-bind="text: profit() < 0 ?

    'negative' : 'positive'"></span>

</td>

Например, если свойство profit оценивается как меньшее 0, будет применен css-класс negative. Аналогично оценивается второе выражение и, если оно равно true, будет применен css-класс positive.

Хотя я советую по возможности всегда использовать css-классы, иногда требуется задавать еще и специфический стиль. Knockout поддерживает это за счет встроенной привязки style. В следующем примере цвет текстового поля меняется на красный, если profit меньше 0, или на зеленый, если profit больше 0 (рис. 12):

<td>

  <input data-bind="value:profit, style: {color: profit() < 0 ? 'red' :

    'green'}"></input>

</td>

<td>

  <span>profit < 0: </span><span data-bind="text: profit() < 0 ? 'red' :

    'green'"></span>

</td>

Style Bindings
Рис. 12. Связывание стилей

Связывание с другими HTML-атрибутами

Хотя в Knockout есть много встроенных привязок, вы наверняка встретитесь с ситуациями, где вам не удастся подобрать подходящую привязку. В таких случаях Knockout предлагает встроенную привязку attr, которая позволяет связывать любой атрибут со свойством модели представления. Это особенно полезно во многих распространенных сценариях, например при связывании href и title какого-либо элемента:

<td>

  <a data-bind="attr: {href: url, title: model.name}, text:model.code"></a>

</td>

<td><span>url: </span><span data-bind="text: url"></span></td>

Другое распространенное применение привязки attr — связывание атрибута src элемента img со свойством photoUrl модели представления (результаты можно посмотреть на рис. 13):

<td>

  <img data-bind="attr: {src: photoUrl, alt: model.code}" class="photoThumbnail"/>

</td>

<td><span>photoUrl: </span><span data-bind="text: photoUrl"></span></td>

Binding to Element Attributes
Рис. 13. Связывание с атрибутами элемента

Заключение

В этой статье мы рассмотрели многие из встроенных привязок Knockout. Есть и некоторые другие, из которых наиболее примечательна привязка template — о ней мы поговорим в одной из будущих статей. В любом случае концепции те же. Определите свойство привязки, которое вы хотите использовать в целевом элементе, и член модели представления, с которым вам нужно его связать. Разобравшись в наблюдаемых объектах и множестве встроенных привязок Knockout, вы получаете фундаментальные строительные блоки для создания надежных веб-приложений на основе шаблона Model View ViewModel (MVVM).


Исходный код можно скачать по ссылке code.msdn.microsoft.com/mag201203ClientInsight.

Джон Папа (John Papa) — бывший идеолог Microsoft в группах Silverlight и Windows 8, вел популярное шоу Silverlight TV. Выступал с программными речами и докладами на различных секциях конференций BUILD, MIX, PDC, Tech•Ed, Visual Studio Live! и DevConnections. Сейчас является ведущим рубрики «Papa’s Perspective» в журнале «Visual Studio Magazine» и автором обучающих видеороликов в Pluralsight. Следите за его заметками в twitter.com/john_papa.

Выражаю благодарность за рецензирование статьи эксперту Стиву Сандерсону (Steve Sanderson).