Pull to refresh

KnockoutJS: фильтрация списка с сохранением состояния

Reading time 4 min
Views 12K


Если вы хорошо смыслите в 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 равен значению атрибута 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); // привязываем данные 
})();


Материалы по теме




Мастера, как же сделать код ещё лучше?


UPD: код обновлен.
По совету xdenser
  • Код filters.ko.js завернут в self-invoking функцию. Это оставит пространство имен в чистоте.
  • Сортировки сгруппированы под одним объектом sortTypes

По совету mac2000 и xdenser параметрам даны более логичные имена, которые лучше отображают их суть.

UPD2:
По совету porcelanosa и serjoga добавлены пояснения по коду.
Tags:
Hubs:
+14
Comments 31
Comments Comments 31

Articles