
Если вы хорошо смыслите в KnockoutJS и JavaScript не проходите мимо!, нам нужны ваши знания.
Новичок, не пропусти. В комментариях к этому посту будут советы о том, как правильно писать на KnockoutJS.
Есть: фильтрация списка с сохранением состояния фильтров в Cookie.
Ищем: Лучшие практики KnockoutJS, чтобы не плодить плохой код.
Предостережение: редко пишу статьи, из-за чего оформление страдает. Не пугайтесь. Присылайте ошибки и рекомендации по оформлению в личку. Спасибо.
Скриншот

Исходные коды
Фильтр
<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 равен значению атрибута valueView
<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 добавлены пояснения по коду.
