Каждый день нам приходится работать с огромным количеством различных задач. Это могут быть рабочие встречи, ужин с родственниками, занятие йогой или банальная уборка. А для того чтобы все это помнить и везде успевать, нам нужно либо развить у себя феноменальную память, либо использовать «плоды технического прогресса».
Так уж случилось, что у меня нет достаточной компетенции, чтобы давать вам советы по улучшению памяти 🙂 Возможно, здоровый образ жизни и правильное питание могут улучшить ситуацию, но этим мои знания и ограничиваются.
А в этой статье я хочу поговорить о том, как создать небольшое ToDoList приложение на основе компонентов библиотеки Webix, которое поможет вам эффективно управлять ежедневными задачами и всегда будет у вас под рукой (в телефоне или лэптопе).
С кодом готового приложения и живой демкой вы можете ознакомиться здесь.
Обзор приложения
И так, нам нужно реализовать компактное приложение с дружественным интерфейсом, в котором пользователи смогут управлять своими ежедневными задачами, а именно:
- создавать новые задачи
- редактировать активные задачи
- закреплять важные задачи в начале списка
- завершать выполненные задачи
- возвращать выполненные задачи в список активных
- удалять ненужные задачи
- просматривать актуальные и выполненные задачи
- искать необходимые задачи среди актуальных и выполненных
- сортировать активные задачи по степени их важности.
С функционалом мы определились. Теперь давайте подумаем, как реализовать все это таким образом, чтобы сделать интерфейс максимально удобным для потенциальных пользователей. И для начала, мы можем разделить приложение на 2 условных части: тулбар и список задач.
В тулбаре нам следует поместить кнопку для создания новых задач, строку поиска и контролы для переключения между актуальными и выполненными задачами. Это весьма удобно, поскольку тулбар будет находиться в верхней части приложения и основные контролы всегда будут у пользователей под рукой.
А в списке задач, который мы разместим под тулбаром, пользователи смогут просматривать задачи и управлять ими через соответствующие иконки на каждой из них. В браузере приложение будет выглядеть так:
Список активных задач
Список выполненных задач
Поскольку у Webix есть все необходимые инструменты для реализации задуманного, мы можем смело переходить к разработке самого приложения.
Подготавливаем почву
Перед началом, нам следует подключить ресурсы библиотеки. В приложении мы будем использовать базовую GPL версию Webix, которую можно скачать здесь. Помимо этого, вы также можете подключить необходимые файлы через CDN:
<link rel="stylesheet" type="text/css" href="http://cdn.webix.com/edge/webix.css">
А теперь самое время заняться лейаутом приложения. И здесь стоит рассказать о том, что Webix использует декларативный подход в построении интерфейсов. Это значит, что каждый компонент представляет собой отдельный JS объект, в котором нужно указать все его параметры (основные настройки и обработчики событий) в виде пар ключ:значение.
Вкладывая такие объекты друг в друга, мы формируем лейаут приложения. А для инициализации лейаута, нам нужно передать его объект конструктору webix.ui() в качестве параметра.
Исходя из того, что хранить один большой JS объект не совсем удобно, мы разделим его на 2 логические части, о которых уже упоминали выше: тулбар и список задач. Каждую из них мы будем разрабатывать в отдельном файле и сохраним в соответствующую переменную. Эти файлы также нужно подключить в index.html.
<script type="text/javascript" src="js/list.js"></script>
В итоге, лейаут нашего приложения будет состоять из двух рядов. Для этого мы поместим переменные с конфигурациями в массив свойства rows:
rows:[
toolbar,
list
]
});
А для полной уверенности в том, что код приложения начнет выполняться после полной загрузки HTML страницы, нам следует передать конструкцию лейаута методу webix.ready(function(){}).
webix.ui({
rows:[
...
]
});
});
Мы подключили все необходимые ресурсы и создали лейаут приложения. А теперь давайте перейдем к разработке соответствующих частей интерфейса и реализуем вышеописанный функционал.
Создаем список задач
В первую очередь, давайте создадим список задач нашего приложения. И здесь не лишним будет упомянуть о том, что у библиотеки есть целая линейка виджетов, которые позволяют отображать данные в различном виде (таблицы, блоки, списки и т.д.). Речь идет о таких компонентах как datatable, treetable, dataview, list и другие. С полным перечнем можно ознакомиться здесь.
Исходя из того, что нам нужно отобразить задачи в виде обычного списка, давайте воспользуемся виджетом list. Каждый элемент нашего списка будет содержать название задачи и, в зависимости от ее статуса (активная или выполненная), иконки для управления этой задачей. Но об этом мы поговорим чуть позже.
Базовая конфигурация
В файле list.js мы создаем список и сохраняем его в переменную, которую будем использовать при построении лейаута. Базовая конфигурация виджета будет выглядеть следующим образом:
view:"list",
id:"todo_list",
template:"#task#",
url:"./data/list_data.json"
}
Сам компонент объявляется через выражение view: «list». Дальше мы задаем ему уникальный ID, с помощью которого будем обращаться к виджету для вызова его методов.
Чтобы загрузить данные о существующих задачах, которые находятся на удаленном сервере, мы используем свойство url и задаем ему путь к данным, которые загрузятся в компонент сразу после инициализации. А структура данных будет такой:
{ "id":1, "task":"English lessons", "status":true, "star":false },
{ "id":2, "task":"Yoga", "status":true, "star":true }, ...
]
Здесь также стоит уточнить, что для загрузки данных, которые находятся на клиенте, мы бы использовали свойство data вместо url.
И так, компонент мы объявили и загрузили в него необходимые данные. Теперь нужно отобразить их в надлежащем виде. А за визуализацию данных отвечает свойство template, которому мы задали строковый шаблон «#task#». Этот шаблон будет отображать данные каждой задачи, которые хранятся в ее объекте под ключом task. В браузере список задач будет выглядеть так:
Смотрится уже хорошо, но это пока не совсем то, что нам нужно. Давайте немного кастомизируем отрисовку задач и добавим соответствующие иконки для управления.
Выше я уже упоминал о том, что в зависимости от статуса задачи (активная или выполненная), ее иконки будут отличаться. Помимо самого названия, для активных задач мы зададим:
- иконку для перетаскивания задач в списке
- иконку для добавления задач в избранные (и удаления из избранных)
- иконку для завершения задач
- иконку для удаления задач.
А для выполненных задач:
- лейбл «Done»
- иконку для возвращения задач обратно в активные.
Чтобы это реализовать, нам нужно создать специальную темплейт-функцию, которая будет возвращать необходимый шаблон задачи в зависимости от ее статуса. И здесь следует упомянуть о том, что статус каждой задачи хранится в объекте ее данных под ключом status.
Для удобства, давайте сохраним темплейт-функцию в переменную list_template, которую и будем использовать в конфигурации виджета. Код функции будет выглядеть следующим образом:
if(obj.status){ // шаблон для активных задач
return `
<span class='drag_icon webix_icon mdi mdi-drag'></span>`+
obj.task +`
<span class='delete_icon webix_icon mdi mdi-delete'></span>
<span class='complete_icon webix_icon mdi mdi-check-circle'></span>
<span class='star_icon webix_icon mdi mdi-`+`
(obj.star ? "star" : "star-outline")'></span>`;
}else{ // шаблон для завершенных задач
return `
<span class='done'>Done</span>`+
obj.task +`
<span class='undo_icon webix_icon mdi mdi-undo-variant'></span>`;
}
}
Параметр obj — это объект данных конкретной задачи. Для начала, давайте проверим ее статус, который хранится в объекте. Если он равен true, то функция возвращает шаблон активной задачи, если же false — завершенной. Учитывайте, что при любых изменениях в данных задачи (включая смену статуса), она будет заново перерисована по этому шаблону.
Когда новый шаблон готов, давайте применим его к нашему приложению, а заодно установим фиксированную высоту задачи через свойство height и зададим ей стили через свойство css. А сделать все это можно в объекте свойства type. Конфигурация виджета теперь будет выглядеть следующим образом:
view:"list",
id:"todo_list",
type:{
height:45,
css:"custom_item",
template:list_template,
},
url:...
}
В браузере список задач будет выглядеть уже так:
Список активных задач
Как вы видите, сейчас активные и выполненные задачи отображаются вместе. Давайте это исправим, отфильтровав активные задачи при первоначальной загрузке.
Для этого мы создадим специальную функцию filterToDoList(status) которая будет принимать статус задачи и возвращать соответствующий результат: если статус равен true — активные задачи, если false — выполненные. Если же статуса нет вообще, мы будем сбрасывать фильтрацию списка в первоначальное состояние. Код функции будет выглядеть следующим образом:
const list = $$("todo_list");
if (status === undefined){
list.filter();
} else {
list.filter(function(obj){
return obj.status === status;
});
}
}
Внутри функции мы используем метод filter(), который, в зависимости от аргументов, будет либо фильтровать задачи в соответствии с их статусом, либо сбрасывать фильтрацию.
Когда функция готова, нам следует вызвать ее в коллбэке свойства ready. Она выполнится, как только в виджет придут данные.
view:"list",
…,
ready:function(){
filterToDoList(true);
}
}
Теперь, при первоначальной загрузке, пользователь увидит только список активных задач.
Редактирование задач
При описании функционала приложения, мы упомянули о том, что пользователь сможет редактировать активные задачи. Давайте дадим ему такую возможность.
По умолчанию, у компонента list нету функции редактирования. Но это легко исправить, добавив виджету дополнительный функционал.
Всё API для редактирования Webix виджетов лежит в отдельном модуле webix.EditAbility. Чтобы получить редактируемый список, давайте создадим свой компонент на основе webix.ui.list с этим модулем.
А для того чтобы создать кастомный компонент, у библиотеки предусмотрен специальный конструктор protoUI. Ему следует передать название нового виджета (пусть это будет editlist), модуль редактирования и виджет, от которого будет наследоваться базовый функционал:
name:"editlist"
}, webix.EditAbility, webix.ui.list);
Теперь нам необходимо инициализировать новый виджет. А для этого, в конфигурации ранее описанного компонента, мы заменяем выражение view: «list» на view: «editlist» и добавляем следующие настройки редактирования:
-
editable:true — включает редактирование для всех элементов списка
editor:»text» — устанавливает редактор «text»
editValue:»task» — определяет значение, которое будет редактироваться
editaction:»dblclick» — открывает редактор при двойном клике по элементу (задаче).
Таким образом, если пользователь кликнет 2 раза по какой либо задаче, в браузере он увидит следующий результат:
Давайте немного усложним задачу и добавим валидацию, при которой редактируемое поле не должно оставаться пустым. А для этого мы воспользуемся свойством rules, в объекте которого пропишем правило webix.rules.isNotEmpty:
view:"editlist",
id:"todo_list",
editable:true,
…,
rules:{
task:webix.rules.isNotEmpty
}
}
После того как пользователь отредактировал задачу, виджет проверит введенное им значение в соответствии с указанным правилом. Если поле окажется пустым, оно будет подсвечено красным цветом.
Сейчас пользователь может редактировать все задачи из списка (активные и выполненные). А поскольку менять уже выполненные задачи смысла особого нету, давайте запретим их редактировать.
Чтобы это реализовать, нам следует установить специальный обработчик на событие onBeforeEditStart, которое будет срабатывать перед началом редактирования. В этом обработчике мы будем проверять и возвращать статус задачи. Если статус равен true (задача активная) — редактор запустится, а если false (задача выполненная) — нет.
Для установки обработчиков на те или иные события виджета мы можем использовать универсальное свойство on. В объекте этого свойства нам необходимо указать желаемое событие и присвоить ему соответствующий обработчик. В коде это выглядит следующим образом:
view:"editlist",
…,
on:{
onBeforeEditStart:function(id, e){
const obj = $$("todo_list").getItem(data);
return obj.status;
}
}
}
С помощью метода getItem(), который принимает ID выбранного элемента списка, мы получаем объект задачи и возвращаем ее статус. Теперь пользователь может редактировать только активные задачи.
Перетаскивание задач
Бывают случаи, когда пользователю нужно отсортировать задачи, разместив их в том порядке, в котором они должны выполняться. Удобнее всего менять порядок задач, перетаскивая их в желаемое место.
А за перетаскивание элементов списка отвечает свойство drag, которому можно задать одно из нескольких возможных значений. Например, для того чтобы пользователь мог перетаскивать задачи только в пределах списка задач, мы зададим свойству drag значение «inner»:
view:"editlist",
…,
drag:"inner"
}
Более детальную информацию о перетаскивании элементов списка вы найдете в этой статье.
Теперь пользователь может сортировать список, перетащив конкретную задачу в желаемое место. И здесь не лишним будет напомнить, что при создании шаблона активных задач мы определили специальную иконку для их перетаскивания:
Давайте сделаем так, чтобы drag-n-drop срабатывал только на вышеуказанной иконке. А для этого, в объекте уже знакомого нам свойства on, мы установим обработчик на событие onBeforeDrag. Внутри обработчика нам нужно указать css класс иконки, на которой будет срабатывать событие. В нашем случае это drag_icon:
view:"editlist",
…,
on:{
onBeforeDrag:function(data, e){
return (e.target||e.srcElement).className == "drag_icon";
},
…
}
}
Теперь пользователь может сортировать задачи, перетаскивая их за специальную иконку, которая есть только у активных задач.
Избранные задачи
Бывают также случаи, когда нужно выделить избранные задачи и поднять их в самое начало списка. Давайте реализуем такой функционал и в нашем приложении. А для этого мы используем ранее созданные иконки, с помощью которых пользователь сможет управлять приоритетом задач:
Стоит учитывать, что в объекте каждой задачи хранится переменная star. Если ее значение равно true — задача находится в избранных и помечается иконкой с классом mdi-star (желтая звездочка), а если false — обычная задача с иконкой mdi-star-outline (простая звездочка). Для удобства, мы задали этим иконкам общий класс star_icon, который понадобится нам дальше.
Теперь давайте сделаем так, чтобы при клике по соответствующим иконкам задачи, ее значение obj.star менялось на противоположное, а избранные задачи поднимались в самый верх списка.
Чтобы это реализовать, нам нужно установить обработчик на событие клика по иконке с классом star_icon. Это можно сделать в объекте свойства onClick, который предусмотрен специально для таких случаев:
star_icon:function(e, id){
const obj = this.getItem(id);
this.updateItem(id, { star:!obj.star });
if(obj.star){
this.moveTop(id);
this.showItem(id);
this.select(id);
}else{
this.moveBottom(id);
}
}
}
В обработчике мы получаем объект данных задачи через метод списка getItem(), передав ее ID в качестве параметра. С помощью метода updateItem() нам нужно поменять значение star на противоположное, а затем проверить его.
Если обновленное значение star равно true — мы подымаем задачу на самый верх списка с помощью метода moveTop(), затем прокручиваем окно браузера к этому элементу через метод showItem(), а после этого выбираем элемент с помощью метода select(). В противном случае, мы отправляем задачу в самый конец списка используя метод moveBottom().
Таким образом, пользователь может добавлять активные задачи в избранные и обратно, используя иконки в виде звездочек. Но и это еще не все. При первоначальной загрузке, избранные задачи все еще отображаются вперемешку с обычными. Давайте это исправим и отобразим их в самом верху списка активных задач.
Для этого мы создадим специальную функцию sortToDoList(), которая будет сортировать задачи по их приоритету (star). Код функции будет таким:
$$("todo_list").sort("#star#", "desc", "string");
}
Метод sort(), который мы используем внутри функции, принимает значение сортировки, ее порядок и тип сортируемых данных, а возвращает отсортированный список задач.
Чтобы отсортировать избранные задачи при первоначальной загрузке, нам следует вызвать эту функцию в коллбэке свойства ready, который выполнится сразу после загрузки данных:
view:"editlist",
…,
ready:function(){
filterToDoList(true);
sortToDoList();
}
}
Теперь, при первоначальной загрузке, все избранные задачи будут находиться в самом верху списка:
Завершение задач
Когда пользователь выполнил ту или иную задачу, он захочет завершить ее. Давайте реализуем такую возможность с помощью иконки, которую мы предусмотрели в шаблоне отрисовки специально для такого случая:
Как и в предыдущем примере, нам нужно установить обработчик на событие клика по этой иконке, которой мы задали css класс complete_icon. А сделать это можно в объекте уже знакомого нам свойства onClick:
star_icon: ...,
complete_icon:function(e, id){
this.updateItem(id, { status:false, star:false });
this.moveTop(id);
filterToDoList(true);
}
}
Внутри обработчика мы делаем следующее:
- сбрасываем статус задачи и ее приоритет с помощью метода updateItem()
- поднимаем задачу в самый верх списка (выполненных задач) методом moveTop()
- фильтруем активные задачи, чтобы скрыть только что завершенную, используя функцию filterToDoList(true).
Теперь пользователь может завершать выполненные задачи при клике по соответствующей иконке.
Удаление задач
В нашем списке активных задач не хватает еще одной важной функции, а именно — возможности удалять ненужные задачи. Давайте реализуем это с помощью еще одной иконки, которую мы указали в шаблоне отрисовки:
Здесь нам следует поступить также, как и с предыдущими иконками, а именно, установить обработчик на иконку с классом delete_icon:
star_icon: ...,
complete_icon: ...,
delete_icon:function(e, id){
this.remove(id);
return false;
}
}
В обработчике мы удаляем выбранный элемент списка используя его метод remove() и возвращаем false, чтобы предотвратить обработку и вызов других событий. Теперь пользователь может удалять ненужные задачи с помощью простого клика по соответствующей иконке.
Список выполненных задач
Когда пользователь завершает ту или иную задачу, она будет доступна только в списке выполненных задач.
Здесь пользователь не может перемещать, редактировать или удалять задачи. Все что он может сделать, это вернуть задачу обратно в активные. А для этого, в шаблоне выполненных задач мы предусмотрели специальную иконку:
По аналогии с активными задачами, давайте установим обработчик на событие клика по этой иконке, которой мы задали класс undo_icon:
star_icon: ...,
complete_icon: ...,
delete_icon: ...,
undo_icon:function(e, id){
this.updateItem(id, { status:true });
this.moveBottom(id);
filterToDoList(false);
}
}
Внутри обработчика мы делаем следующее:
- обновляем статус задачи с помощью метода updateItem()
- перемещаем задачу в конец списка активных задач методом moveBottom()
- фильтруем выполненные задачи, чтобы спрятать ту, которую мы сделали активной, используя функцию filterToDoList(false).
Теперь пользователь может вернуть ранее завершенную задачу обратно в активные, при клике по соответствующей иконке в списке выполненных задач.
Создаем тулбар
А сейчас давайте займемся тулбаром нашего приложения, через который пользователи смогут создавать новые задачи, искать нужные задачи и переключаться между списками активных и выполненных задач. Визуально, тулбар будет выглядеть так:
Для создания этой части интерфейса мы будем использовать компонент toolbar, внутри которого разместим следующие элементы:
- 2 виджета label с иконкой и названием приложения
- контрол button для создания новых задач
- контрол search для поиска задач
- контрол segmented для переключения между активными и завершенными задачами.
Теперь переходим к практике. В файле toolbar.js мы создаем тулбар и сохраняем его в переменную. Сам виджет объявляется с помощью выражения view: «toolbar». Нам следует задать ему встроенную стилизацию через свойство css и фиксированную высоту через свойство height.
Чтобы расположить содержимое виджета по горизонтали, нам необходимо поместить нужные элементы в массив его свойства elements.
view:"toolbar",
css:"webix_dark",
height:45,
elements:[...]
};
Лейблы с иконкой и названием мы объявляем с помощью выражения view: «label» и задаем им фиксированную ширину через свойство width. Название каждого лейбла нужно указать через свойство label.
{ view:"label", width:30, label:"<span class='webix_icon mdi mdi-playlist-check'></span>" },
{ view:"label", width:130, label:"Webix ToDoList" }
]
Дальше у нас идет кнопка для создания новых задач. Для нее мы будем использовать контрол button, которому зададим уникальный ID через свойство id и название через свойство value. ID нам понадобится для того, чтобы получить доступ к кнопке и вызывать ее методы.
{ view:"label", … }, …,
{ view:"button", id:"create_button", value:"+ Create", width:120 }
]
После кнопки, нам следует разместить строку поиска задач, для которой мы используем контрол search. Его особенность заключается в том, что в отличии от обычного текстового поля (ui.text), в левой его части находится кликабельная иконка «поиск». А чтобы при вводе любых значений, вместо иконки «поиск» отображалась иконка «очистить», мы зададим виджету свойство clear в значении «hover». При клике по этой иконке, поле будет очищено.
{ view:"label", … }, …,
{ view:"button", … },
{ view:"search", id:"search_input", clear:"hover", width:200 }
]
И напоследок, у нас остались кнопки для переключения между активными и выполненными задачами. Их мы реализуем с помощью контрола segmented. Помимо базовых ID и размера, нам нужно задать названия для соответствующих сегментов. А сделать это можно в массиве свойства options.
{ view:"label", … }, …,
{ view:"button", … },
{ view:"search", … },
{ view:"segmented", id:"segmented", width: 240,
options: [
{ id:1, value:"Active" },
{ id:2, value:"Completed" }
]
}
]
Интерфейс тулбара мы создали, теперь давайте сделаем его функциональным.
Создание новых задач
При клике по кнопке «+ Create», пользователь сможет добавить новую задачу, которая появится в конец списка активных задач. Чтобы это реализовать, нам нужно создать обработчик и установить его на событие клика по этой кнопке. А для обработки кликов, у кнопки предусмотрено специальное свойство click.
click:createNewTaskHandler }
Код обработчика будет выглядеть следующим образом:
const list = $$("todo_list");
filterToDoList();
const item = list.add({ task:"New Task", status:true, star:false });
filterToDoList(true);
list.showItem(item);
list.select(item);
list.edit(item);
}
Внутри функции мы делаем следующее:
- сбрасываем фильтрацию списка, вызывая функцию filterToDoList() без аргумента
- добавляем новую задачу с помощью метода add(), который принимает объект с нужными данными. Метод возвращает ID задачи, который мы сохраняем в переменную item и будем использовать в следующих методах
- отфильтровываем активные задачи с помощью функции filterToDoList(status), передав ей статус активных задач в качестве аргумента
- показываем новый элемент, передав его ID методу showItem(item)
- выбираем новый элемент методом select(item)
- открываем редактор задачи, используя метод edit(item).
Теперь, при клике по кнопке «+ Create», пользователь может добавить новую задачу в конец списка и сразу же ее редактировать.
Переключение между списками задач
Дальше у нас на очереди контролы для переключения между активными и выполненными задачами. Чтобы реализовать такое переключение, нам нужно установить обработчик на событие onChange контрола segmented. А сделать это можно в объекте уже знакомого свойства on:
options: [
{ id:1, value:"Active" },
{ id:2, value:"Completed" }
],
on:{ onChange:toggleHandler }
}
Код обработчика будет выглядеть следующим образом:
const button = $$("create_button");
if(id == 2){
filterToDoList(false);
button.hide();
}else{
filterToDoList(true);
button.show();
}
}
Функция получает ID выбранного сегмента в качестве параметра. Если выбран сегмент «Completed», ID которого равен 2 — мы фильтруем список выполненных задач с помощью функции filterToDoList(status). Хочу напомнить, что функция принимает статус задач, которые нужно отфильтровать.
После фильтрации, нам нужно спрятать кнопку «+ Create», чтобы пользователь не смог создавать новые задачи находясь в списке выполненных задач. Для этого мы используем метод кнопки hide().
Если же пользователь выбрал сегмент «Active», мы фильтруем список активных задач и отображаем кнопку «+ Create» ее методом show().
Теперь пользователь может переключаться между активными и выполненным задачами с помощью соответствующих контролов «Active» и «Completed» на тулбаре.
Поиск задач
И нам осталось реализовать поиск задач через строку поиска, которую мы также разместили на тулбаре нашего приложения. Как вы уже догадались, нам опять нужно создать обработчик и установить его на событие ввода текста в объекте свойства on.
on:{
onTimedKeyPress:searchHandler
}
}
Событие onTimedKeyPress, упомянутое выше, будет срабатывать через некоторое время после нажатия клавиши, если фокус-покус находится в поле контрола.
А код обработчика будет выглядеть следующим образом:
const search_value = $$("search_input").getValue().toLowerCase();
const segmented_value = $$("segmented").getValue();
const isMatched
$$("todo_list").filter(function(obj){
const is_matched = obj.task.toLowerCase().indexOf(search_value) !== -1;
if(segmented_value == 2){
return !obj.status && is_matched;
}else{
return obj.status && is_matched;
}
});
}
С помощью метода getValue() мы получаем текст из строки поиска и ID активного сегмента. Если выбран сегмент «Completed» (ID которого равен 2), мы фильтруем выполненные задачи, названия которых совпадают со значением строки поиска. В противном случае, нам нужно фильтровать активные задачи по такому же принципу.
Вот и вся магия. Теперь пользователь может искать активные и выполненные задачи, используя строку поиска на тулбаре.
Заключение
С кодом готового приложения и живой демкой вы можете ознакомиться здесь.
В этой статье вы научились создавать небольшое приложение для управления списком задач на основе компонентов библиотеки Webix. Узнать больше о возможностях работы с виджетами Webix вы можете в документации библиотеки.