15.01.2018

Биндинг «foreach» — Knockout.js

Назначение

Биндинг foreach дублирует участок разметки для каждого элемента в массиве, и связывает каждую копию этой разметки с соответствующим элементом массива. Это особенно удобно для визуализации списков или таблиц.

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

Конечно, вы можете произвольно внедрять любое число биндингов foreach вместе с другими биндингами для контроля потока данных (англ. control-flow), такими как if и with.

Пример 1: Итерация по массиву

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

<table>
    <thead>
        <tr><th>Имя</th><th>Фамилия</th></tr>
    </thead>
    <tbody data-bind="foreach: people">
        <tr>
            <td data-bind="text: firstName"></td>
            <td data-bind="text: lastName"></td>
        </tr>
    </tbody>
</table>
 
<script type="text/javascript">
ko.applyBindings({
    people: [
        { firstName: 'Вася', lastName: 'Пупкин' },
        { firstName: 'Лена', lastName: 'Головач' },
        { firstName: 'Иван', lastName: 'Иванов' }
    ]
});
</script>

Пример 2: Реальный пример с добавлением/удалением

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

See the Pen Knockout Пример 2: Реальный пример с добавлением/удалением (foreach binding) by Kirill (@skv1991) on CodePen.

Исходный код: Представление

<h4>Люди</h4>
<ul data-bind="foreach: people">
    <li>
        Имя в элементе массива <span data-bind="text: $index"> </span>:
        <span data-bind="text: name"> </span>
        <a href="#" data-bind="click: $parent.removePerson">Удалить</a>
    </li>
</ul>
<button data-bind="click: addPerson">Добавить</button>

Исходный код: Модель представления

function AppViewModel() {
    var self = this;
 
    self.people = ko.observableArray([
        { name: 'Вася' },
        { name: 'Катя' },
        { name: 'Алексей' }
    ]);
 
    self.addPerson = function() {
        self.people.push({ name: "Новый, добавлен в " + new Date() });
    };
 
    self.removePerson = function() {
        self.people.remove(this);
    }
}
 
ko.applyBindings(new AppViewModel());

Параметры

  • Основной параметр
    Передача массива, по которому требуется провести итерации. Биндинг выдаст участок разметки для каждого элемента массива.Альтернативным способом, можно передать JavaScript объект со свойством data, которое является массивом, по которому производить итерации. Объект так же может иметь другие свойства, такие как afterAdd или includeDestroyed — ниже описана более детальная информация по этим дополнительным опциям, и примеры их использования.Если переданный массив имеет тип observable, то биндинг foreach будет отвечать на любые будущие изменения в содержимом массива, добавляя или удаляя соответствующие участки разметки в DOM.
  • Дополнительные параметры
    • Нет

Примечание 1: Ссылка на каждый элемент массива через $data

Как было показано в примерах выше, биндинги внутри блока foreach могут ссылаться на свойства элементов массива. Например, Пример 1 ссылается на свойства firstName и lastName у каждого элемента массива.

Но что же делать, если нужно сделать ссылку на сам элемент массива (а не одно из его свойств)? В этом случае, можно использовать специальное свойство контекста $data. В рамках блока foreach, оно означает “текущий элемент массива”. Например,

<ul data-bind="foreach: months">
    <li>
        Текущий элемент: <b data-bind="text: $data"></b>
    </li>
</ul>
 
<script type="text/javascript">
    ko.applyBindings({
        months: [ 'Янв', 'Фев', 'Мар', 'и т.д.' ]
    });
</script>

Если нужно, можно использовать $data в качестве префикса, когда указываете на свойства каждого элемента массива. Например, можно переписать часть Примера 1 следующим образом:

<td data-bind="text: $data.firstName"></td>

… но в данном случае это излишне, потому что по-умолчанию, свойство firstName в любом случае будет искаться внутри контекста $data.

Примечание 2: Использование $index, $parent, и других свойств контекста

Как вы могли заметить в Примере 2 выше, можно использовать $index для ссылки на индекс (отсчет с нуля) текущего элемента массива. $index является observable-переменной и обновляется, как только изменяется индекс элемента массива (например, если был добавлен или удален элемент массива).

Также, можно использовать $parent для ссылки на данные вне foreach, например:

<h1 data-bind="text: blogPostTitle"></h1>
<ul data-bind="foreach: likes">
    <li>
        <b data-bind="text: name"></b> нравится запись <b data-bind="text: $parent.blogPostTitle"> из блога</b>
    </li>
</ul>

Чтобы узнать больше о $index и других свойствах контекста, вроде $parent, смотри документацию для свойств контекста биндингов.

Примечание 3: Использование “as” для присвоения алиаса элементам “foreach”

Как описано в Примечании 1, можно ссылаться на каждый элемент массива через переменную контекста $data. Хотя в некоторых случаях, может быть более полезно присвоить текущему элементу массива более описывающее имя используя опцию as таким образом:

<ul data-bind="foreach: { data: people, as: 'person' }"></ul>

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

<ul data-bind="foreach: { data: categories, as: 'category' }">
    <li>
        <ul data-bind="foreach: { data: items, as: 'item' }">
            <li>
                <span data-bind="text: category.name"></span>:
                <span data-bind="text: item"></span>
            </li>
        </ul>
    </li>
</ul>
 
<script>
    var viewModel = {
        categories: ko.observableArray([
            { name: 'Фрукты', items: [ 'Яблоко', 'Апельсин', 'Банан' ] },
            { name: 'Овощи', items: [ 'Сельдерей', 'Кукуруза', 'Шпинат' ] }
        ])
    };
    ko.applyBindings(viewModel);
</script>

Совет: Запомните, что нужно указывать строковой литерал в as (например, as: ‘category’а не as: category), because you are giving a name for a new variable, not reading the value of a variable that already exists.

Примечание 4: Использование foreach без контейнера

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

<ul>
    <li class="header">Заголовочный элемент</li>
    <!-- Следующее сгенерировано автоматически из массива -->
    <li>Элемент А</li>
    <li>Элемент Б</li>
    <li>Элемент В</li>
</ul>

В этом примере, негде разместить обычный биндинг foreach. Вы не можете поставить его ни на <ul>(потому что в этом случае продублируется заголовочный элемент), на ни другой вложенный в <ul> контейнер (потому что внутри <ul> элементов можно использовать лишь <li> элементы).

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

<ul>
    <li class="header">Заголовочный элемент</li>
    <!-- ko foreach: myItems -->
    <li>Элемент <span data-bind="text: $data"></span></li>
    <!-- /ko -->
</ul>
 
<script type="text/javascript">
    ko.applyBindings({
        myItems: [ 'А', 'Б', 'В' ]
    });
</script>

Теги комментариев <!— ko —> и <!— /ko —> работают как маркеры начала/конца, определяющие “виртуальный элемент” который внутри содержит разметку. Knockout понимает такой синтаксис виртуальных элементов и применяет биндинг, как если бы у вас был реальный элемент для контейнера.

Примечание 5: Как отслеживается и обрабатывается изменение массива

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

  • Когда вы добавляете элементы в массив, foreach визуализирует новые копии вашего шаблона и вставит их в существующий DOM
  • Когда вы удаляете элементы массива, foreach просто удалит соответствующие DOM элементы
  • Когда вы переставляете элементы массива (сохраняя те же экземпляры объектов), foreach обычно просто переместит соответствующие DOM элементы на их новые позиции

Обратите внимание, что отслеживание перестановки элементов не гарантировано: чтобы алгоритм завершался быстро, он оптимизирован на “простые” перемещения небольшого числа записей в массиве. Если алгоритм определяет слишком много одновременных перестановок вкупе с несвязанныи вставками и удалениями, то для скорости выполнения он может выбрать вместо перестановки делать “удаление” плюс “добавление” вместо одиночного “перемещения”, и в этом случае соответствующие DOM элементы будут снесены и воссозданы заново. Большинство разработчиков не столкнется с этой гранью, а даже, если и столкнется, то конечное взаимодействие пользователя с интерфейсом обычно остается идентичным.

Примечание 6: Удаленные элементы скрыты по-умолчанию

Иногда может понадобится пометить элемент массива, как удаленный, но без реальной потери существующего элемента. Этот подход называется неразрушающее удаление. Чтобы более подробно ознакомиться с этим подходом, смотри the destroy function on observableArray.

По-умолчанию, биндинг foreach будет пропускать (например, скрывать) любые элементы массива, которые были помечены, как удаленные. Если требуется показать удаленные элементы, используйте опцию includeDestroyed. Например,

<div data-bind='foreach: { data: myArray, includeDestroyed: true }'>
    ...
</div>

Примечание 7: Пост-обработка или анимирование сгенерированных DOM элементов

Если нужно запустить какую-то собственную, более продвинутую логику на сгенерированных DOM элементах, вы можете использовать любую из callback-функций afterRender/afterAdd/beforeRemove/beforeMove/afterMove описанных ниже.

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

Вот типичный пример использования afterAdd для применения классического эффекта “желтой подсветки” только что добавленным элементам. Для его работы требуется плагин jQuery Color для включения анимации фоновой заливки.

<ul data-bind="foreach: { data: myItems, afterAdd: yellowFadeIn }">
    <li data-bind="text: $data"></li>
</ul>
 
<button data-bind="click: addItem">Добавить</button>
 
<script type="text/javascript">
    ko.applyBindings({
        myItems: ko.observableArray([ 'А', 'Б', 'В' ]),
        yellowFadeIn: function(element, index, data) {
            $(element).filter("li")
            .animate({ backgroundColor: 'yellow' }, 200)
            .animate({ backgroundColor: 'white' }, 800);
        },
        addItem: function() { this.myItems.push('Новый элемент'); }
    });
</script>

Полное описание:

  • afterRender — дергается каждый раз при дублировании и вставки блока в foreach в тело документа, и при первой инициализации foreach, и когда позднее в связанный массив была добавлена новая запись. Knockout передаст следующие параметры в вашу callback-функцию:
    1. Массив вставленных DOM элементов
    2. Объект данных с которым будет связан этот массив
  • afterAdd — похож на afterRender, за исключением того, что запускается только когда новые записи были добавлены в ваш массив (а не когда foreach производит первую итерацию по начальному содержимому массива). Типичным использованием afterAdd является вызов метода вроде $(domNode).fadeIn() из jQuery, таким образом получится плавная анимация показа когда был добавлен элемент. Knockout передаст следующие параметры в вашу callback-функцию:
    1. DOM узел, добавленный в документ
    2. индекс добавленного элемента в массиве
    3. добавленный элемент массива
  • beforeRemove — дергается, когда элемент массива был удален, но до удаления соответствующего DOM узла. Если вы укажете callback-функцию для beforeRemove, то iто она станет ответственной за удаление DOM узлов. Очевидным вариантом использования здесь является вызов чего-то наподобие $(domNode).fadeOut() из jQuery для анимированного удаления соответствующих DOM узлов — в данном случае, Knockout не может узнать, когда можно физически удалить DOM узлы (кто знает, как много времени займет ваша анимация?), так что удаление уже зависит от вас. Knockout передаст следующие параметры в вашу callback-функцию:
    1. DOM узел, который должен быть удален
    2. индекс удаленного из массива элемента
    3. удаленный элемент массива
  • beforeMove — запускается, когда элемент массива изменил положение в массиве, но ещё до соответствующих перемещений в DOM узлах. Обратите внимание, что beforeMove применяется ко всем элементам массива, чьи индексы изменились, таким образом, если вставите новый элемент в начало массива, то callback-функция (если указана) будет запущена для всех остальных элементов, поскольку индекс их положения увеличился на единицу. Вы можете использовать beforeMove для хранения исходных координат затронутых элементов на экране, так, чтобы можно было анимировать их перемещение в callback-функции для afterMove. Knockout передаст следующие параметры в вашу callback-функцию:
    1. DOM узел, которой возможно перемещается
    2. индекс перемещаемого элемента в массиве
    3. перемещаемый элемент массива
  • afterMove — дергается после того, как изменилось положение элемента в массиве, и после того, как foreach обновил соответствующие DOM узлы. Обратите внимание, что afterMove применяется ко всем элементам массива, чьи индексы изменились, таким образом, если вставите новый элемент в начало массива, то callback-функция (если указана) будет запущена для всех остальных элементов, поскольку индекс их положения увеличился на единицу. Knockout передаст следующие параметры в вашу callback-функцию:
    1. DOM узел, которой возможно был перемещен
    2. индекс перемещенного элемента массива
    3. перемещенный элемент массива

В качестве примеров для afterAdd и beforeRemove смотри анимированные переходы.

Зависимости

Нет, кроме ядра библиотеки Knockout.


Эксклюзивно для сайта skv1991.ru

Перепечатка и копирование строго запрещены.

Оригинал страницы

Опубликовано: 15/01/2018

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *