Статьи

Интерактивные привязки

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

Рисунок 19: Knockout.js, распространяющий изменения в обоих направлениях

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

Knockout.js включает в себя 11 привязок для взаимодействия с пользователем:

  • click : <метод> — вызывать метод ViewModel при щелчке элемента.
  • value : <property> — связать значение элемента формы со свойством ViewModel.
  • event : <объект> — вызывать метод при возникновении события, инициированного пользователем.
  • submit : <метод> — вызов метода при отправке формы.
  • enable : <property> — включить элемент формы на основе определенного условия.
  • disable : <property> — disable элемента формы на основе определенного условия.
  • checked : <свойство> — привязать переключатель или флажок к свойству ViewModel.
  • options : <array> — Определить элемент <select> с массивом ViewModel.
  • selectedOptions : <array> — Определить активные элементы в поле <select> .
  • hasfocus : <property> — Определить, является ли элемент сфокусированным.

Как и привязки внешнего вида, представленные в предыдущем уроке, все они определены в атрибуте привязки данных элемента HTML. Некоторые из них (например, привязка click ) работают с любым элементом, но другие (например, отмеченные) могут использоваться только с конкретными элементами.

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


Этот урок использует новую страницу HTML для работающего примера. Вместо страницы отображения корзины покупок мы будем работать с формой регистрации для новых клиентов. Создайте новый HTML-файл с именем interactive-bindings.html и добавьте следующее:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<html lang=’en’>
<head>
  <title>Interactive Bindings</title>
  <meta charset=’utf-8′ />
  <link rel=’stylesheet’ href=’../style.css’ />
</head>
<body>
  <h2>
 
  <form action=»#» method=»post»>
    <!— ToDo —>
  </form>
 
  <script src=’knockout-2.1.0.js’></script>
  <script>
    function PersonViewModel() {
      var self = this;
      this.firstName = ko.observable(«John»);
      this.lastName = ko.observable(«Smith»);
    }
 
    ko.applyBindings(new PersonViewModel());
  </script>
</body>
</html>

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


Привязка кликов является одной из самых простых интерактивных привязок. Он просто вызывает метод вашей ViewModel, когда пользователь щелкает элемент. Например, добавьте следующую кнопку внутри элемента <form> :

1
<p><button data-bind=’click: saveUserData’>Submit</button></p>

Когда пользователь нажимает кнопку, Knockout.js вызывает метод saveUserData() для PersonViewModel . Кроме того, он передает два параметра методу-обработчику: текущую модель и событие DOM. Метод saveUserData (), использующий оба эти параметра, будет выглядеть примерно так:

1
2
3
4
5
6
this.saveUserData = function(model, event) {
  alert(model.firstName() + » is trying to checkout!»);
  if (event.ctrlKey) {
    alert(«He was holding down the Control key for some reason.»);
  }
};

В этом конкретном примере model ссылается на экземпляр ViewModel верхнего уровня, а event — это событие DOM, инициируемое щелчком пользователя. Аргумент модели всегда будет текущим ViewModel, что позволяет получить доступ к отдельным элементам списка в цикле foreach. Вот как мы реализовали метод removeProduct () в уроке 3:.


Привязка значений очень похожа на привязку текста, которую мы использовали в этой серии. Основное отличие состоит в том, что он может быть изменен пользователем , и ViewModel будет обновляться соответствующим образом. Например, мы можем связать наблюдаемые lastName firstName и lastName с полем ввода, добавив следующий HTML-код в форму (перед <button>):

1
2
<p>First name: <input data-bind=’value: firstName’ /></p>
<p>Last name: <input data-bind=’value: lastName’ /></p>

Привязка value: firstName гарантирует, что текст элемента <input> всегда совпадает со свойством firstName ViewModel , независимо от того, было ли оно изменено пользователем или вашим приложением. То же самое относится и к свойству lastName.

Рисунок 20: Двусторонние связи между наблюдаемыми и полями формы

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

1
2
3
4
5
6
7
8
<p>
  <button data-bind=’click: displayName’>
    Display Name
  </button>
  <button data-bind=’click: setName’>
    Set Name
  </button>
</p>

Методы обработчика должны выглядеть примерно так:

1
2
3
4
5
6
this.displayName = function() {
  alert(this.firstName());
};
this.setName = function() {
  this.firstName(«Bob»);
};

При нажатии « Отображаемое имя» будет прочитано свойство firstName ViewModel, которое должно соответствовать элементу <input> , даже если он был отредактирован пользователем. Кнопка « Задать имя» устанавливает значение свойства ViewModel, заставляя элемент <input> обновляться. Поведение последнего по сути такое же, как при обычном связывании текста.

Еще раз, весь смысл этой двусторонней синхронизации состоит в том, чтобы позволить вам сосредоточиться на ваших данных. После настройки привязки value вы можете полностью забыть об элементах HTML-формы. Просто получите или установите соответствующее свойство в ViewModel, а Knockout.js позаботится обо всем остальном.

Нам не понадобятся методы displayName и setName или их соответствующие кнопки, поэтому вы можете удалить их, если хотите.


Привязка event позволяет прослушивать произвольные события DOM для любого элемента HTML. Это как общая версия привязки click . Но поскольку он может прослушивать несколько событий, ему требуется объект для сопоставления событий с методами (это похоже на параметр привязки attr ). Например, мы можем прослушивать события mouseover и mouseout для первого элемента <input> следующим образом:

1
2
3
<p data-bind=’event: {mouseover: showDetails, mouseout: hideDetails}’>
   First name: <input data-bind=’value: firstName’ />
</p>

Когда пользователь запускает событие mouseover , Knockout.js вызывает метод showDetails() нашего ViewModel. Аналогично, когда он или она покидает элемент, вызывается hideDetails (). Оба они принимают те же параметры, что и обработчики привязки кликов: цель события и сам объект события. Давайте реализуем эти методы сейчас:

1
2
3
4
5
6
this.showDetails = function(target, event) {
  alert(«Mouse over»);
};
this.hideDetails = function(target, event) {
  alert(«Mouse out»);
};

Теперь, когда вы взаимодействуете с полем « Имя» , вы должны увидеть оба сообщения. Но вместо того, чтобы просто отображать предупреждающее сообщение, давайте покажем некоторую дополнительную информацию для каждого поля формы, когда пользователь переворачивает его. Для этого нам понадобится другая наблюдаемая на PersonViewModel :

1
this.details = ko.observable(false);

Свойство details действует как переключатель, который мы можем включать и выключать с помощью наших методов-обработчиков событий:

1
2
3
4
5
6
this.showDetails = function(target, event) {
  this.details(true);
};
this.hideDetails = function(target, event) {
  this.details(false);
};

Затем мы можем объединить переключатель с visible привязкой, чтобы показать или скрыть детали поля формы в представлении:

1
2
3
4
<p data-bind=’event: {mouseover: showDetails, mouseout: hideDetails}’>
  First name: <input data-bind=’value: firstName’ />
  <span data-bind=’visible: details’>Your given name
</p>

Содержимое <span> должно появляться всякий раз, когда вы наводите курсор мыши на поле « Имя», и исчезать при наведении мыши. Это довольно близко к нашей желаемой функциональности, но все становится сложнее, когда мы хотим отобразить детали для более чем одного поля формы. Поскольку у нас есть только одна переменная переключения, отображение деталей является предложением «все или ничего» — либо детали отображаются для всех полей, либо ни для одного из них.

Рисунок 21: Переключение всех деталей поля формы одновременно

Один из способов исправить это — передать пользовательский параметр в функцию-обработчик.

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

1
2
3
4
5
6
7
8
this.details = ko.observable(«»);
 
this.showDetails = function(target, event, details) {
  this.details(details);
}
this.hideDetails = function(target, event) {
  this.details(«»);
}

Единственное большое изменение здесь — это добавление параметра details в метод showDetails() . Нам не нужен пользовательский параметр для функции hideDetails (), поскольку он просто очищает наблюдаемые детали .

Далее мы будем использовать функциональный литерал в привязке event для передачи пользовательского параметра в showDetails ():

1
2
3
<p data-bind=’event: {mouseover: function(data, event) {
         showDetails(data, event, «firstName»)
       }, mouseout: hideDetails}’>

Функциональный литерал для mouseover является оберткой для нашего обработчика showDetails() , предоставляя простые средства для передачи дополнительной информации. Обработчик мыши остается неизменным. Наконец, нам нужно обновить <span>, содержащий детали:

1
<span data-bind=’visible: details() == «firstName»‘>Your given name

Поле формы « Имя» должно отображать подробное описание при наведении курсора мыши и скрываться при наведении мыши, как это было в предыдущем разделе. Только теперь можно добавить детали в более чем одно поле, изменив пользовательский параметр. Например, вы можете включить детали для элемента ввода Фамилия с помощью:

1
2
3
4
5
6
<p data-bind=’event: {mouseover: function(data, event) {
         showDetails(data, event, «lastName»)
       }, mouseout: hideDetails}’>
 
Last name: <input data-bind=’value: lastName’ />
<span data-bind=’visible: details() == «lastName»‘>Your surname

Привязки событий могут быть немного сложны в настройке, но как только вы поймете, как они работают, они предоставят безграничные возможности для реактивного проектирования. Привязка к event может даже соединяться с функциональностью анимации jQuery, которая обсуждается в уроке 8:. На данный момент мы закончим изучение остальных интерактивных привязок Knockout.js. К счастью для нас, ни один из них не настолько сложен, как привязки событий.


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

1
2
this.primaryPhone = ko.observable(«»);
this.secondaryPhone = ko.observable(«»);

Наблюдаемый primaryPhone может быть связан с полем формы с обычной привязкой value :

1
2
3
<p>
  Primary phone: <input data-bind=’value: primaryPhone’ />
</p>

Однако не имеет смысла вводить дополнительный номер телефона без указания основного, поэтому мы активируем <input> для дополнительного номера телефона, только если primaryPhone не пуст:

1
2
3
4
<p>
  Secondary phone: <input data-bind=’value: secondaryPhone,
          enable: primaryPhone’ />
</p>

Теперь пользователи смогут взаимодействовать с полем « Дополнительный телефон» только в том случае, если они ввели значение для primaryPhone . disable привязки — это удобный способ отменить условие, но в остальном работает точно так же, как и enable.


checked — это универсальная привязка, которая демонстрирует различное поведение в зависимости от того, как вы ее используете. Как правило, проверенная привязка используется для выбора и отмены выбора проверяемых элементов формы HTML: флажков и переключателей.

Давайте начнем с простого флажка:

1
<p>Annoy me with special offers: <input data-bind=’checked: annoyMe’ type=’checkbox’ /></p>

Это добавляет флажок к нашей форме и связывает его со свойством annoyMe ViewModel. Как всегда, это двусторонняя связь. Когда пользователь выбирает или снимает флажок, Knockout.js обновляет ViewModel, а когда вы устанавливаете значение свойства ViewModel, он обновляет представление. Не забудьте определить раздражаемую наблюдаемую:

1
this.annoyMe = ko.observable(true);

Использование checked связывания таким образом похоже на создание взаимно-однозначного отношения между одним флажком и булевой наблюдаемой.

Рисунок 22: Соединение логической наблюдаемой с помощью одного флажка

Также возможно использовать checked привязку с массивами. Когда вы привязываете флажок к наблюдаемому массиву, выбранные поля соответствуют элементам, содержащимся в массиве, как показано на следующем рисунке:

Рисунок 23: Подключение наблюдаемого массива с несколькими флажками

Например, рассмотрим следующее наблюдаемое:

1
this.annoyTimes = ko.observableArray([‘morning’, ‘evening’]);

Мы можем соединить элементы в этом наблюдаемом массиве с флажками, используя атрибут value каждого элемента <input> :

1
2
3
4
5
6
<p>Annoy me with special offers: <input data-bind=’checked: annoyMe’ type=’checkbox’ /></p>
<div data-bind=’visible: annoyMe’>
  <div>
    <input data-bind=’checked: annoyTimes’
           value=’morning’
           type=’checkbox’ />

При этом используется свойство annoyMe из предыдущего урока, чтобы переключать список флажков для выбора, когда будет подходящее время для раздражения. Поскольку value='morning' находится в первом флажке , оно будет выбрано, когда строка "morning" находится в массиве annoyTimes . То же самое касается других флажков. «утро» и «вечер» — это начальное содержимое массива, поэтому вы должны увидеть что-то вроде следующего на своей веб-странице:

Рисунок 24. Флажки, отображающие начальное состояние наблюдаемого массива annoyTimes

И поскольку мы используем наблюдаемый массив, соединение является двусторонним: annoyTimes любого из блоков приведет к удалению соответствующей строки из массива annoyTimes .

Последний контекст для checked привязки находится в группе переключателей. Вместо логического значения или массива переключатели связывают свой атрибут value со строковым свойством во ViewModel. Например, мы можем превратить наш массив флажков в группу переключателей, сначала изменив annoyTimes, наблюдаемый, на строку:

1
this.annoyTimes = ko.observable(‘morning’);

Затем все, что нам нужно сделать, это превратить элементы <input> в переключатели:

1
2
3
4
<input data-bind=’checked: annoyTimes’
               value=’morning’
               type=’radio’
               name=’annoyGroup’ />

Каждый <input> должен иметь "radio" качестве своего типа и «annoyGroup» в качестве своего имени . Последний не имеет ничего общего с Knockout.js — он просто добавляет их все в одну группу переключателей HTML. Теперь атрибут значения выбранного переключателя всегда будет сохранен в свойстве annoyTimes.

Рисунок 25: Соединение наблюдаемой строки с несколькими переключателями

Привязка options определяет содержимое элемента <select> . Это может принимать форму раскрывающегося списка или списка с множественным выбором. Сначала рассмотрим выпадающие списки. Давайте изменим свойство annoyTimes еще раз:

1
2
3
4
5
this.annoyTimes = ko.observableArray([
  ‘In the morning’,
  ‘In the afternoon’,
  ‘In the evening’
]);

Затем мы можем связать его с полем <select> с помощью:

1
2
<div data-bind=’visible: annoyMe’>
  <select data-bind=’options: annoyTimes’></select>

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

1
<select data-bind=’options: annoyTimes, value: selectedTime’></select>

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

1
this.selectedTime = ko.observable(‘In the afternoon’);

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

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

1
2
3
4
5
this.products = ko.observableArray([
  {name: ‘Beer’, price: 10.99},
  {name: ‘Brats’, price: 7.99},
  {name: ‘Buns’, price: 2.99}
]);

Когда вы попытаетесь создать из этого элемент <select> , все ваши объекты будут отображаться как [object Object]:

Рисунок 26: Попытка использовать объекты с привязкой options

К счастью, Knockout.js позволяет передавать параметр optionsText чтобы определить свойство объекта для отображения в элементе <select>:

1
2
3
<select data-bind=’options: products,
          optionsText: «name»,
          value: favoriteProduct’></select>

Чтобы этот фрагмент работал, вам также нужно определить favoriteProduct продукт, наблюдаемый в вашей ViewModel. Knockout.js заполнит это свойство объектом из PersonViewModel.products, а не строкой, как это было в предыдущем разделе.


Другой возможностью рендеринга для элемента HTML <select> является список с множественным выбором. Настройка списка с множественным выбором очень похожа на создание раскрывающегося списка, за исключением того, что вместо одного выбранного элемента у вас есть массив выбранных элементов. Таким образом, вместо использования привязки value для сохранения выбора, вы используете привязку selectedOptions:

1
2
3
4
5
<select data-bind=’options: products,
            optionsText: «name»,
            selectedOptions: favoriteProducts’
          size=’3′
          multiple=’true’></select>

Атрибут size определяет количество видимых опций, а multiple='true' превращает его в список с множественным выбором. Вместо строкового свойства FavoritesProducts должен указывать на массив:

1
2
3
4
5
6
7
var brats = {name: ‘Brats’, price: 7.99};
this.products = ko.observableArray([
    {name: ‘Beer’, price: 10.99},
    brats,
    {name: ‘Buns’, price: 2.99}
]);
this.favoriteProducts = ko.observableArray([brats]);

Обратите внимание, что нам нужно было предоставить одну и ту же ссылку на объект ( brats ) для обоих products и favProducts для Knockout.js, чтобы правильно инициализировать выбор.


Итак, мы подошли к нашей окончательной интерактивной привязке: hasfocus . Это точно названное связывание позволяет вам вручную устанавливать фокус интерактивного элемента, используя свойство ViewModel. Если по какой-то странной причине вы хотите, чтобы поле «Основной телефон» было в качестве начального фокуса, вы можете добавить привязку hasfocus, например так:

1
2
3
4
<p>
  Primary phone: <input data-bind=’value: primaryPhone,
        hasfocus: phoneHasFocus’ />
</p>

Затем вы можете добавить наблюдаемую логическую переменную, чтобы сообщить Knockout.js об этом:

1
this.phoneHasFocus = ko.observable(true);

Установив это свойство в другом месте вашего приложения, вы можете точно контролировать процесс фокусировки в ваших формах. Кроме того, вы можете использовать hasfocus для отслеживания прогресса пользователя через несколько полей формы.


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

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

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

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