Я — начинающий front-end разработчик. Сейчас я учусь и стажируюсь в одной минской IT компании. Изучение основ web-ui проходит на примере JS библиотеки Webix. Я хочу поделиться своим скромным опытом и сохранить его в виде небольшого учебного пособия по этой интересной UI библиотеке.

ПЯТАЯ ЗАДАЧА


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


В документации можно ознакомиться с использованными в статье виджетами List, Treetable, Table.

Исходники находятся по ссылке.

С готовым приложением можно ознакомиться тут.

Фильтрация и сортировка данных таблицы


Начнем с таблиц — их я чаще всего использовал для работы с большим количеством данных. Таблицы в библиотеке Webix имеют ряд встроенных фильтров, которые устанавливаются прямо в header виджетов Table и TreeTable. В виджете Table я использую два варианта: простой текстовый фильтр (textFilter) и фильтр с набором опций в выпадающем списке (selectFilter). Таблица позволяет добавить по одному фильтру для каждой колонки. Сделаю это для двух: title и year. Header в них вместо строки задам массивом — чтобы уместить заголовок и фильтр. Второй элемент массива, это объект со свойством content и именем фильтра.
Код виджета Table находится в файле table.js и отрисован во вкладке “Dashboard”.

При вводе символов в "textFilter" данные будут отфильтрованы совпадением по подстроке. При выборе опции в "selectFilter" — по выбранному значению.

columns:[
    { id:"rank", header:"", width:50, css:"rank"},
    { id:"title", header:["Film title", { content:"textFilter"}], fillspace:true },
    { id:"year",  header:["Released", {content:"selectFilter" }], width:100 },
    { id:"votes", header:"Votes", width:100 },
    { id:"rating", header:"Rating", width:100 },
    { header:"", template:"<span class='webix_icon wxi-close'></span>", width:35}
]

Результат фильтрации по подстроке “star”:



Результат фильтрации элементов по выбранному значению “1991”:



Сортировка. Как и в случае с фильтрами, сортировку так же легко сделать доступной для пользователя. Для этого достаточно дополнить конфигурацию колонок свойством sort. Есть несколько готовых типов сортировки: по числовым значениям, по дате и по строке. Столбцам year, votes и rating я установлю настройку sort: “int” для сортировки по числовым значениям. Для столбца title значение будет “string”.

    columns:[
        { id:"rank", header:"", width:50, css:"rank"},
        { id:"title", header:["Film title", { content:"textFilter"}], fillspace:true, 
         sort:"string"},
        { id:"year",  header:["Released", {content:"selectFilter" }], width:100, sort:"int"},
        { id:"votes", header:"Votes", width:100, sort:"int"},
        { id:"rating", header:"Rating", width:100, sort:"int"},
        { header:"", template:"<span class='webix_icon wxi-close'></span>", width:35}
    ]

По клику на header колонки данные будут отсортированы в соответствии с их типом. Результат сортировки по рейтингу:



Сортировка и фильтрация через API


Готовые решения для фильтрации и сортировке элементов имеют только таблицы. Но в целом все виджеты поддерживают эти возможности через соответствующие методы API — filter и sort. Фильтрацию и сортировку при помощи API я продемонстрирую у виджета List.

Код виджета List находится в файле users_module.js и отрисован во вкладке “Users”.

Фильтрация. Во вкладке Users, после кнопки “Add new person”, я установлю виджет Text, который использую в качестве фильтра для имен из списка.

cols:[
    { 
        view:"button", id:"btn_add_person", 
        value:"Add new person", width:150, css:"webix_primary", 
        click:addPerson
    },
    { 
        view:"text", id:"list_input" 
    },
]

Теперь открою файл script.js и добавлю логику отвечающую за фильтрацию элементов.

$$("list_input").attachEvent("onTimedKeyPress",function(){
    var value = this.getValue().toLowerCase();
    $$("user_list").filter(function(obj){
        return obj.name.toLowerCase().indexOf(value) !== -1;
    })
});

Фильтрация элементов происходит по такому принципу:
  • при помощи метода attachEvent я добавляю обработчик на событие onTimedKeyPress;
  • событие onTimedKeyPress вызывается вводом символов в текстовое поле, но с короткой задержкой, чтобы не задействовать фильтр при каждом нажатии клавиш;
  • далее я получаю введенный текст и методом filter запускаю фильтрацию — поиск совпадений в виджете List.

Результат фильтрации через API:



Сортировка. Сортировка элементов виджета List будет происходить по клику кнопок “Sort asc” и “Sort desc”.

Чтобы создать кнопки во вкладке Users, после текстового поля я добавлю два виджета Button с обработчиком событий click.

cols:[
    { 
        view:"button", id:"btn_add_person", 
        value:"Add new person", width:150, css:"webix_primary", 
        click:addPerson
    },
    { 
        view:"text", id:"list_input" 
    },
    { view:"button", id:"btn_asc", width:150, value:"Sort asc", css:"webix_primary", 
        click:()=>{
            $$("user_list").sort("#name#","asc")
     }},
    { view:"button", id:"btn_desc", width:150, value:"Sort desc", css:"webix_primary", 
        click:()=>{
            $$("user_list").sort("#name#","desc")
    }},
]

Внутри обработчика click, метод sort принимает для параметра: имя поля, по которому сортируем данные, и направление сортировки “asc” (ascending) — по возрастанию, и “desc” (descending) — по убыванию. По умолчанию данные считаются строками и сортируются соответствующим образом.

Результат (имена в листе отсортированы по алфавиту):



Группировка данных древовидной таблицы


Рассмотрим ситуацию когда данные надо сгруппировать по произвольным параметрам.
Изучать группировку я буду на примере виджета TreeTable.

Код виджета TreeTable находится в файле products_module.js и отрисован во вкладке “Poducts”.

В статье: Модули, диаграммы, древовидные таблицы и Работа с данными. CRUD, в древовидной таблице я использовал иерархические данные. Для решения этой задачи я их изменил, чтобы получить линейный массив. Я избавился от иерархии и перенес из нее в каждую запись поле “category”.

[
    {"id": "1.1",   "title": "Standard Ticket",  "price": 21, "category":"Cinema", "rank":1.1},
    {"id": "2.1",   "title": "Cola",  "price": 10, "category":"Cafe", "rank":2.1},
    {"id": "3.1",   "title": "Flowers",  "price": 10, "category":"Other", "rank":3.1}
]

Сгруппировать данные в таблице можно двумя способами:

  • сразу при их загрузке;
  • динамически, при помощи методов group и ungroup.

Параметры у этих методов одинаковые и мне нужно сгруппировать данные один раз, как только они пришли, поэтому я использую первый вариант.
В конфигурации виджета TreeTable добавлю свойство scheme. Это свойство определяет по какой схеме будут обработаны данные в разных ситуациях. Среди обработчиков в sheme есть метод $group, который мне нужен для группировки данных.

const products = {
    editable:true,
    view:"treetable",
    scrollX:false,
    columns:[
        { id:"rank", header:"", width:50 },
        { id:"title", header:"Title", fillspace:true, template:"{common.treetable()} #title#"},
        { id:"price", header:"Price", width:200, editor:"text" }
    ],
    select:"row",
    url:"data/products.js",
    scheme:{
        $group:{
            by:"category",
            map:{
                title:["category"]
            }
        },
        $sort:{ by:"value", dir:"asc" }
    }
}

Внутри обработчика $group использовано два параметра:
  • обязательный параметр by, по которому группируются данные. Здесь — одно из полей (“category”);
  • объект map — здесь опишем поля данных для создаваемых групп. Группировка разбивает исходные данные по указанным параметрам и создает для них «родительские записи». Через map мы можем добавить в эти записи новые поля. Чтобы данные в таблице отображались корректно, добавлю поле “title”. Передам в него значение параметра, по которому происходит группировка.

Дополнительно я установил функцию $sort, чтобы отсортировать сгруппированные данные в алфавитном порядке.

Результат группировки:



Синхронизация компонентов


В задаче использованы виджеты Chart и List, код которых в файле users_module.js и отрисованы во вкладке “Users”

Виджеты Chart и List используют одни и те же данные — массив JSON. Эти компоненты можно связать так, чтобы все изменения данных в одном из них (источнике) транслировались в другой. Для этого используется метод sync.

Метод sync позволяет копировать данные из одного компонента и передавать их другому. При этом, изменения в основном компоненте, такие как добавление, удаление, и пр., сразу отражаются в другом.

Для начала, в виджете Chart — диаграмма — я удалю ссылку на данные.

{
    view:"chart",
    id:"chart",
    type:"bar",
    value:"#age#",
    label:"#age#",
    xAxis:{
        template:"#name#",
        title:"Age"
    }
}

Теперь, в файле script.js методом sync я синхронизирую виджет Chart с виджетом List.

$$("chart").sync($$("user_list"));

В функции addPerson добавление оставляем только для виджета List.

let addPerson = () => {
    let obj = {
        name:"Some name",
        age:Math.floor(Math.random() * 80) + 10, 
        country:"Some country"
    }
    $$("user_list").add(obj);
};

Теперь при добавлении и удалении записей из списка, изменения будут происходить и в диаграмме. Сортировка и фильтрация в виджете List теперь затронет и данные в Chart.



Обобщение


На практических примерах я показал как можно улучшить работу пользователя. Помимо стандартной сортировки и фильтрации при помощи API была освоена встроенная возможность сделать это в виджетах Table и TreeTable одной настройкой. Был продемонстрирован способ группировки данных в таблице, а пример синхронизации расширил возможность усовершенствовать работу виджетов, использующих один и тот же источник данных.

С готовым приложением можно ознакомиться тут.