![](https://habrastorage.org/storage2/46b/48d/358/46b48d358e51a4eb881ecabe6a5be0ad.png)
Если вы хорошо смыслите в KnockoutJS и JavaScript не проходите мимо!, нам нужны ваши знания.
Новичок, не пропусти. В комментариях к этому посту будут советы о том, как правильно писать на KnockoutJS.
Есть: фильтрация списка с сохранением состояния фильтров в Cookie.
Ищем: Лучшие практики KnockoutJS, чтобы не плодить плохой код.
Предостережение: редко пишу статьи, из-за чего оформление страдает. Не пугайтесь. Присылайте ошибки и рекомендации по оформлению в личку. Спасибо.
Скриншот
![](https://habrastorage.org/storage2/16a/7f9/cf0/16a7f9cf03d55de1770e69cfd78d4b57.png)
Исходные коды
Фильтр
<div class="filter-list">
<div class="filter">
<label for="filter_ended"><input type="checkbox" data-bind="checked: filterEnded" id="filter_ended" /> Закончившиеся</label>
</div>
<div class="filter">
<label for="filter_simple"><input type="checkbox" data-bind="checked: isSimpleView" id="filter_simple"/>
Упрощенный вид</label>
</div>
<div class="filter">
<label for="filter_sort_date"><input type="radio" data-bind="checked: sorting" value="date" id="filter_sort_date" /> По дате добавления</label>
</div>
<div class="filter">
<label for="filter_sort_abc"><input type="radio" data-bind="checked: sorting" value="asc" id="filter_sort_abc" /> По алфавиту</label>
</div>
</div>
data-bind="..."
— привязывание Knockout к HTML.<input type="checkbox" data-bind="checked: filterEnded" id="filter_ended" />
— ставит атрибут checked
, если filterEnded == true
<input type="radio" data-bind="checked: sorting" value="date" id="filter_sort_date" />
— ставит атрибут checked
, если sorting
равен значению атрибута value
View
<div class="items" data-bind="foreach: filteredItems, css: {items_simple: isSimpleView}">
<div class="item" data-bind="text: name, css: {item_is_ended: is_ended}"></div>
</div>
filteredItems
— массив, хранящий отфильтрованные элементы. Все элементы хранятся в viewModel.items
.foreach: filteredItems
— проход по всем элементам массива и применение к ним шаблона внутри .items
. css: {items_simple: isSimpleView}
— добавляет класс .items_simple
на .items
, если isSimpleView == true
.text: name
— показывает текстовое значение параметра name
(альтернатива $data.name
) элемента массива из filteredItems
.Данные
$items_json = json_encode( array(
array(
'name' => 'Ван-Пис',
'is_ended' => false,
'order_date' => 1
),
array(
'name' => 'Наруто',
'is_ended' => false,
'order_date' => 2
),
array(
'name' => 'Радость рыбалки',
'is_ended' => true,
'order_date' => 3
),
array(
'name' => 'GTO',
'is_ended' => true,
'order_date' => 4
),
))
Подключаемый JS (перед закрывающим body
)
<script type="text/javascript" src="/assets/js/cookie.js"></script>
<script type="text/javascript" src="/assets/js/libs/knockout-2.1.0.js"></script>
<script type="text/javascript">
var data_items = <?= $items_json ?>;
</script>
<script type="text/javascript" src="/assets/js/filters.ko.js"></script>
ViewModel (filters.ko.js
)
(function () {
// получаем по имени из кук булевый параметр или false
var getParamBool = function (paramName) {
return !!((getCookie(paramName) === 'true'));
};
// получаем по имени из кук строковый параметр или defaultValue, если в куках пусто
var getParam = function (paramName, defaultValue) {
return getCookie(paramName) ? getCookie(paramName) : defaultValue;
};
var viewModel = {
items: ko.observableArray(data_items), // список, хранящий все элементы для фильтрации
filterEnded: ko.observable(getParamBool('filterEnded')),
isSimpleView: ko.observable(getParamBool('isSimpleView')),
sorting: ko.observable(getParam('sorting', 'date')),
sortTypes: { // функции сортировки по алфавиту и дате
'asc': function (left, right) {
return left.name === right.name ? 0 : (left.name < right.name ? -1 : 1)
},
'date': function (left, right) {
return left.order_date === right.order_date ? 0 : (left.order_date < right.order_date ? -1 : 1)
}
},
sortProcesssing: function (sortType) { // применение сортировки
sortType = sortType || this.sorting();
if (this.sortTypes[sortType]) {
setCookie('sorting', sortType, 30);
this.items.sort(this.sortTypes[sortType]); // в sort() передаем функцию сортировки
}
}
};
// отфильтрованные из items элементы
viewModel.filteredItems = ko.computed(function () {
var onlyEnded = this.filterEnded();
if (onlyEnded)
return ko.utils.arrayFilter(this.items(), function (item) { // фильтация
return item.is_ended == onlyEnded;
});
else
return this.items();
}, viewModel);
// подписываемся на изменение типа сортировки, чтобы сохранять в куки
viewModel.sorting.subscribe(function (sortType) {
viewModel.sortProcesssing(sortType);
});
viewModel.filterEnded.subscribe(function (newValue) {
setCookie('filterEnded', newValue, 30);
});
viewModel.isSimpleView.subscribe(function (newValue) {
setCookie('isSimpleView', newValue, 30);
});
// применяем сортировку перед привязыванием данных к View
viewModel.sortProcesssing();
ko.applyBindings(viewModel); // привязываем данные
})();
Материалы по теме
- Отслеживание изменений
Observable
- Работа с формами: привязка
checked
кcheckbox
иradio
- Утилита
arrayFilter
, как и где стоит её применять - Манипуляции над
observableArray
Мастера, как же сделать код ещё лучше?
UPD: код обновлен.
По совету xdenser
- Код
filters.ko.js
завернут вself-invoking
функцию. Это оставит пространство имен в чистоте. - Сортировки сгруппированы под одним объектом
sortTypes
По совету mac2000 и xdenser параметрам даны более логичные имена, которые лучше отображают их суть.
UPD2:
По совету porcelanosa и serjoga добавлены пояснения по коду.