Статьи

Руководство для начинающих по KnockoutJS: часть 3

Руководство для начинающих по KnockoutJS: шаблоны и многое другое

Существует четыре привязки потока управления: foreach , if , ifnot и with . Эти привязки управления позволяют декларативно определять логику потока управления, не создавая именованный шаблон, как вы увидите ниже.

Привязка foreach дублирует раздел разметки для каждой записи в массиве и связывает каждую копию этой разметки с соответствующим элементом массива. Это подходит для отображения списков или таблиц. Если ваш массив является наблюдаемым массивом, то при каждом последующем добавлении или удалении записей массива привязка будет обновлять пользовательский интерфейс, чтобы соответствовать, вставляя или удаляя дополнительные копии элементов списка или строк таблицы, не затрагивая другие элементы DOM. Смотрите следующий пример:

  <Таблица>
  <THEAD>
   <TR> <й> Заголовок </ й> <й> Автор </ й> </ TR>
  </ THEAD>
  <tbody data-bind = "foreach: books">
   <TR>
    <td data-bind = "text: title"> </ td>
    <td data-bind = "text: author"> </ td>      
   </ TR>
  </ TBODY>
 </ Table>

 <script type = "text / javascript">
   function viewModel () {
    var self = this;
    self.books = ko.observableArray ([
      {title: 'The Secret', автор: 'Rhonda Byrne'},
      {title: 'The Power', автор: 'Rhonda Byrne'},
      {title: 'The Magic', автор: 'Rhonda Byrne'}
    ]);
   }
   ko.applyBindings (new viewModel ());    
 </ Скрипт> 

Здесь строка таблицы будет создана автоматически для каждой записи массива в массиве books.

Иногда вам может понадобиться обратиться к самой записи массива, а не только к одному из его свойств. В этом случае вы можете использовать псевдопеременные $data . Это означает «текущий элемент», когда используется внутри блока foreach .

  <ul data-bind = "foreach: daysOfWeek">
  <Li>
  <span data-bind = "text: $ data"> </ span>
  </ Li>
 </ UL>

 <script type = "text / javascript">
 function viewModel () {
   var self = this;
   self.daysOfWeek = ko.observableArray ([
    'Понедельник вторник среда Четверг Пятница Суббота воскресенье'
   ]);
 };

 ko.applyBindings (new viewModel ());
 </ Скрипт> 

Это перечислит все дни недели без необходимости повторять код для каждого элемента в отдельности.

В Knockout вы можете вкладывать столько привязок потока управления, сколько пожелаете. И когда вы делаете это, часто желательно выполнить резервное копирование иерархии и получить доступ к данным или функциям из родительского контекста. В таких случаях вы можете использовать следующие псевдопеременные:

$parent — представляет элемент данных вне текущего блока foreach

$parents — массив, представляющий элементы данных из всех внешних областей потока управления. $parents[0] $parent $parents[0] совпадает с $parent . $parents[1] представляет элемент из области действия потока управления дедушки и так далее.

$root — представляет элемент из самой внешней области действия потока управления. Обычно это ваш объект модели представления верхнего уровня.

В следующем примере мы используем псевдопеременную $parent , чтобы правильно удалить элемент книги из массива books:

  <Таблица>
  <THEAD>
   <TR> <й> Заголовок </ й> <й> Автор </ й> </ TR>
  </ THEAD>
  <tbody data-bind = "foreach: books">
   <TR>
    <td data-bind = "text: title"> </ td>
    <td data-bind = "text: author"> </ td>
    <td> <a href="#" data-bind="click: $parent.removeBook"> Удалить </a> </ td>
   </ TR>
  </ TBODY>
 </ Table>

 <script type = "text / javascript">
   function viewModel () {
    var self = this;
    self.books = ko.observableArray ([
      {title: 'The Secret', автор: 'Rhonda Byrne'},
      {title: 'The Power', автор: 'Rhonda Byrne'},
      {title: 'The Magic', автор: 'Rhonda Byrne'}
    ]);

   self.removeBook = function () {
    self.books.remove (это);
   }
   }
   ko.applyBindings (new viewModel ());    
 </ Скрипт> 

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

  <UL>
 <li> <strong> Дни недели: </ strong> </ li>
  <! - ко foreach: daysOfWeek ->
  <Li>
   <span data-bind = "text: $ data"> </ span>
  </ Li>
  <! - / ko ->
 </ UL>

 <script type = "text / javascript">
 function viewModel () {
   var self = this;
   self.daysOfWeek = ko.observableArray ([
    'Понедельник вторник среда Четверг Пятница Суббота воскресенье'
   ]);
 };

 ko.applyBindings (new viewModel ());
 </ Скрипт> 

В этом примере вы не можете использовать обычную привязку foreach . Если вы поместите его в <ul> это будет дублировать элемент заголовка, и если вы хотите поместить дополнительный контейнер в <ul> вы не сможете этого сделать, потому что внутри <ul> разрешены только элементы <li> . Решение состоит в том, чтобы использовать синтаксис потока управления без контейнера, где комментарии <!-- ko --> и <!-- /ko --> определяют «виртуальный элемент», который содержит разметку внутри, синтаксис которого Knockout понимает и связывает этот виртуальный элемент, как если бы у вас был настоящий контейнерный элемент. Этот тип синтаксиса также действителен для if и with привязками.

При связывании if в документе появляется раздел разметки, только если указанное выражение имеет значение true. Затем содержащаяся разметка будет присутствовать в документе, и к ней будут применены любые атрибуты привязки данных. С другой стороны, если ваше выражение оценивается как ложное, содержащаяся разметка будет удалена из вашего документа без предварительной привязки к нему.

  <label> <input type = "checkbox" data-bind = "checked: showList" /> Показать список </ label>
 <ul data-bind = "if: showList">
   <Li> Пункт </ li>
   <Li> Пункт </ li>
   <Li> Пункт </ li>
 </ UL>

 <script type = "text / javascript">
    function viewModel () {
     var self = this;
     self.showList = ko.observable (false);
    }
   ko.applyBindings (new viewModel ());    
 </ Скрипт> 

Привязка with создает новый контекст привязки, поэтому элементы-потомки связываются в контексте указанного объекта. Объект, который вы хотите использовать в качестве контекста для привязки дочерних элементов. Если указанное вами выражение оценивается как нулевое или неопределенное, элементы-потомки не будут связаны вообще, а вместо этого будут удалены из документа. Привязка с изменяет контекст данных на любой указанный вами объект. Это особенно полезно при работе с графами объектов с несколькими родительскими / дочерними отношениями.

 <p data-bind = "text: book"> </ p> 
 <ul data-bind = "with: details">
  <li> Категория: <span data-bind = "text: category"> </ span> </ li>
  <li> Автор: <span data-bind = "text: author"> </ span> </ li>
  <li> Издатель: <span data-bind = "text: publisher"> </ span> </ li>
 </ UL>

 <script type = "text / javascript">
   function viewModel () {
    var self = this;
    self.book = ko.observable («Секрет»);
    self.details = ko.observable ({категория: «Психология», автор: «Ронда Бирн», издатель: «Simon & Schuster Ltd»});
   }
  ko.applyBindings (new viewModel ());    
 </ Скрипт> 

Шаблонирование

Привязка template заполняет связанный элемент DOM результатами рендеринга шаблона. Шаблоны — это простой и удобный способ построения сложных структур пользовательского интерфейса — возможно, с повторяющимися или вложенными блоками — в зависимости от данных модели представления. Существует два основных способа использования шаблонов. Первый, нативный шаблонизатор, — это механизм, который лежит в основе foreach , if , with другими привязками потока управления. Внутренне эти привязки потока управления захватывают разметку HTML, содержащуюся в вашем элементе, и используют ее в качестве шаблона для рендеринга против произвольного элемента данных. Эта функция встроена в Knockout и не требует никакой внешней библиотеки. Вы можете увидеть базовую схему для создания шаблона здесь:

  <div data-bind = "template: 'myTemplate'"> </ div>

 <script type = "text / html" id = "myTemplate">
 // код шаблона здесь
 </ Скрипт>

В следующем примере вы можете увидеть, как использовать его в действии:

  <div data-bind = "template: 'book-template'"> </ div>

 <script type = "text / html" id = "book-template">  
   <h3 data-bind = "text: title"> </ h3>
   <p> Автор: <span data-bind = "text: author"> </ span> </ p>
 </ Скрипт>

 <script type = "text / javascript">
    function viewModel () {
     var self = this;
     self.title = ko.observable («Секрет»)
     self.author = ko.observable ('Ронда Бирн')
    }
  ko.applyBindings (new viewModel ());    
 </ Скрипт> 

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

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

 // синтаксис: <div data-bind = "template: {name: 'myTemplate', data: myData, afterRender: myLogic}"> </ div>

 <div data-bind = "template: {name: 'book-template', data: bestseller, afterRender: msg}"> </ div>

 // шаблон здесь

 <script type = "text / javascript">
    function MyViewModel () {
     var self = this;
     self.bestseller = {title: 'The Secret', автор: 'Rhonda Byrne'};
     self.nor = {title: 'Some Name', автор: 'Some Author'};
     self.msg = function (elements) {
       alert ('Hip Hip Hooray !!! :)'); 
     }
    }
  ko.applyBindings (new MyViewModel ());    
 </ Скрипт> 

Здесь name — это идентификатор элемента, который содержит шаблон, который вы хотите отобразить; data являются объектом для предоставления в качестве данных для шаблона для визуализации; и afterRender — это функция обратного вызова, которая вызывается для визуализированных элементов DOM.

Следующий пример является эквивалентом привязки foreach . Здесь foreach передается в качестве параметра для привязки template .

 // синтаксис: <div data-bind = "template: {name: 'myTemplate', foreach: myArray}"> </ div>

 <div data-bind = "template: {name: 'book-template', foreach: books}"> </ div>

 // шаблон здесь

 <script type = "text / javascript">
    function MyViewModel () {
     var self = this;
     self.books = [
     {title: 'The Secret', автор: 'Rhonda Byrne'},
     {title: 'The Power', автор: 'Ронда Бирн'}
     ]
    }
  ko.applyBindings (new MyViewModel ());    
 </ Скрипт> 

Вы можете получить точно такой же результат, встроив анонимный шаблон непосредственно в элемент, к которому вы используете привязку foreach:

  <div data-bind = "foreach: books">  
   <h3 data-bind = "text: title"> </ h3>
   <p> Автор: <span data-bind = "text: author"> </ span> </ p>
 </ DIV> 

Второй способ использования шаблонов — подключить Knockout к стороннему движку шаблонов. Knockout передаст значения вашей модели внешнему шаблонному движку и вставит полученную строку разметки в ваш документ. Для примеров, которые используют движки шаблонов jquery.tmpl и Underscore, проверьте документацию .

Расширение наблюдаемых

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

Создание экстендера включает добавление функции к объекту ko.extenders . Функция принимает сам наблюдаемый в качестве первого аргумента и любые опции во втором аргументе. Затем он может либо возвращать наблюдаемую информацию, либо возвращать что-то новое, например вычисляемую наблюдаемую, которая каким-то образом использует исходную наблюдаемую.

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

  <input data-bind = 'value: name, hasfocus: name.on' />
 <span data-bind = "visible: name.on, text: name.hint"> </ span>
 <br />
 <input data-bind = 'value: pass, hasfocus: pass.on' />
 <span data-bind = "visible: pass.on, text: pass.hint"> </ span>

 <script type = "text / javascript">

 // начинаем наблюдаемый расширитель
 ko.extenders.hints = function (target, hint) {
  target.on = ko.observable () 
  target.hint = ko.observable ()

  function showHint (value) {
   target.on (значение? false: true);
   target.hint (значение? "": подсказка);
  }

  showHint (мишень ());

  вернуть цель;
 }; 
 // конец наблюдаемого расширителя

  function viewModel () {
   var self = this;
   self.name = ko.observable (). extend ({подсказки: «Введите здесь свое имя»})
   self.pass = ko.observable (). extend ({подсказки: «Введите здесь свой пароль»})
  };
 ko.applyBindings (new viewModel ());
 </ Скрипт> 

Пользовательские привязки

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

Связывание нокаута состоит из двух методов: init и update . Создать привязку так же просто, как создать объект с помощью этих двух методов и зарегистрировать этот объект с помощью Knockout с помощью ko.bindingHandlers как показано ниже.

  ko.bindingHandlers.yourBindingName = {   
   init: function (element, valueAccessor, allBindingsAccessor, viewModel) {

   },   
   update: function (element, valueAccessor, allBindingsAccessor, viewModel) {

   } 
 };

 // после того, как вы создали, вы можете использовать вашу пользовательскую привязку так же, как и любую встроенную
 <div data-bind = "yourBindingName: someValue"> </ div>

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

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

Функции init и update имеют четыре параметра. Как правило, вам нужно сосредоточиться на element и параметрах valueAccessor , поскольку они являются стандартным способом связать вашу модель представления с вашим пользовательским интерфейсом. На самом деле вам не нужно предоставлять обратные вызовы init и update — вы можете просто предоставить один или другой, если это все, что вам нужно.

Параметр element дает вам прямой доступ к элементу DOM, который содержит привязку.

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

Параметр allBindingsAccessor предоставляет вам доступ ко всем другим привязкам, перечисленным в том же атрибуте привязки данных. Это обычно используется для доступа к другим привязкам, которые взаимодействуют с этой привязкой. Эти привязки, скорее всего, не будут иметь никакого кода, связанного с ними, и являются просто способом передачи дополнительных параметров привязке, если вы не решите передать объект с несколькими свойствами в основную привязку. Например, optionsValue , optionsText и optionsCaption являются привязками, которые используются только для передачи параметров в привязку options .

Параметр viewModel предоставляет доступ к вашей общей модели представления для привязок вне шаблонов. Внутри шаблона это будет установлено для данных, привязанных к шаблону. Например, при использовании опции foreach привязки viewModel параметр viewModel будет установлен на текущий член массива, отправляемый через шаблон. Большую часть времени valueAccessor будет выдавать вам valueAccessor данные, но параметр viewModel особенно полезен, если вам нужен объект, который будет вашей целью при вызове / применении функций.

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

  <textarea data-bind = "scaleOnFocus: scaleArea, scaleUp: {height: '200', ширина: '400'}, scaleDown: {height: '15', ширина: '150'}"> </ textarea>

 <script type = "text / javascript">

 // начинаем привязку
 ko.bindingHandlers.scaleOnFocus = {

  init: function (element, valueAccessor) {
   $ (element) .focus (function () {
    var value = valueAccessor ();
    значение (истина);
   });
   $ (element) .blur (function () {
    var value = valueAccessor ();
    значение (ложь);
   });     
  },

  update: function (element, valueAccessor, allBindingsAccessor) {
   var value = valueAccessor ();
   var allBindings = allBindingsAccessor ();
   var up = allBindings.scaleUp;
   var down = allBindings.scaleDown;
    if (ko.utils.unwrapObservable (value))
     $ (Элемент) .animate (вверх);
    еще 
     $ (Элемент) .animate (вниз);
  }
 };
 // конец пользовательского связывания

 function viewModel () {
  var self = this;
  self.scaleArea = ko.observable ()
 };

 ko.applyBindings (new viewModel ());
 </ Скрипт>

Во-первых, в функции init мы объявляем, что, когда элемент находится в фокусе, тогда его значение будет установлено в true, и наоборот. Затем в функции update мы используем параметр allBindingAccessor для добавления дополнительных опций в нашу привязку — scaleUp и scaleDown . Мы используем ko.utils.unwrapObservable чтобы получить значение текущей привязки и проверить, установлено ли оно в значение true. Если это так, элемент DOM масштабируется вверх, в противном случае он уменьшается.

Наконец, давайте посмотрим на пример, который сочетает в себе наблюдаемые подсказки extender и пользовательскую привязку scaleOnFocus:

 <input data-bind = 'value: name, hasfocus: name.on' />
 <span data-bind = "visible: name.on, text: name.hint"> </ span>
 <br />
 <input data-bind = 'value: email, hasfocus: email.on' />
 <span data-bind = "visible: email.on, text: email.hint"> </ span>
 <br />
 <textarea data-bind = "value: msg.hint, scaleOnFocus: scaleArea, scaleUp: {height: '200', width: '400'}, scaleDown: {height: '50', width: '150'}"> </ TextArea>

 <script type = "text / javascript">
 ko.extenders.hints = function (target, hint) {
  target.on = ko.observable () 
  target.hint = ko.observable ()

  function showHint (value) {
   target.on (значение? false: true);
   target.hint (значение? "": подсказка);
  }

  showHint (мишень ());

  вернуть цель;
 }; 

 ko.bindingHandlers.scaleOnFocus = {

  init: function (element, valueAccessor) {
   $ (element) .focus (function () {
    var value = valueAccessor ();
    значение (истина);
   });
   $ (element) .blur (function () {
    var value = valueAccessor ();
    значение (ложь);
   });     
  },

  update: function (element, valueAccessor, allBindingsAccessor) {
   var value = valueAccessor ();
   var allBindings = allBindingsAccessor ();
   var up = allBindings.scaleUp;
   var down = allBindings.scaleDown;
    if (ko.utils.unwrapObservable (value))
     $ (Элемент) .animate (вверх);
    еще 
     $ (Элемент) .animate (вниз);
  }
 };

 function viewModel () {
  var self = this;
  self.name = ko.observable (). extend ({подсказки: «Введите ваше полное имя»})
  self.email = ko.observable (). extend ({подсказки: введите правильный адрес электронной почты}})
  self.msg = ko.observable (). extend ({подсказки: «Оставить сообщение ...»})
  self.scaleArea = ko.observable ()
 };
 ko.applyBindings (new viewModel ());
 </ Скрипт> 

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

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