Создание простого Todo приложения с Webix

Библиотека по большей части ориентирована для построения сложных клиентских приложений, но также может быть успешно использована для решения простых задач, например, для создания простого «ToDo» приложения. «ToDo» — это приложение, напоминающее «Hello World» для фреймворков. Это базовое приложение, показывающее основную функциональность библиотеки.

todo app

Вы можете получить код финальной версии на GitHub или посмотреть онлайн демо.

Первые шаги

Давайте начнем с создания главной страницы нашего приложения — index.html:

<!DOCTYPE html>
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
        <title>ToDo List</title>
        <link rel="stylesheet" href="http://cdn.webix.io/edge/webix.css" type="text/css" media="screen" charset="utf-8">
        <script src="http://cdn.webix.io/edge/webix.js" type="text/javascript" charset="utf-8"></script>    

        <link rel="stylesheet" href="./app.css" type="text/css" media="screen" charset="utf-8">
        <script src="./app.js" type="text/javascript" charset="utf-8"></script>
    </head>
    <body>

    <h2>todos</h2>
    <div id="app_area"></div>
    <footer id="info">
        <p>Click the "Add" button to add new task</p>
        <p>Single-click to edit a todo, drag to reorder</p>
    </footer>

    <script type="text/javascript">
        webix.ready(function(){
            webix.ui(app.ui);
        });
    </script>
    </body>
</html>

 

В разметке страницы подключены два типа сторонних файлов. Первый блок включает js и css файлы webix.

Во втором блоке на страницу добавляются файлы нашего приложения — app.js и app.css, которые будут использоваться для хранения логики и стиля нашего приложения. В теле страницы вызывается webix.ui() конструктор, который инициализирует приложение. Этот код размещен внутри функции webix.ready, чтобы инициализация webix компонентов началась только после полной загрузки страницы.

В завершение первого шага, давайте зададим конфигурацию пользовательского интерфейса в файле app.js:

var toolbar = [
    {view:"button", value:"And new task", align:"left", width: 150 },
    {},
    {view:"segmented", id:"selector", width: 200, align:"right", multiview:true, options: [
        { id:-1, value:"Active"},
        { id:1, value:"Finished"}
    ]}
];

var list = {
    view:"list", id:"list", borderless:true, autoheight:true,
    type:{ height:55, width:800 },
    data:[{ value:"Test data"}]
};

app = {};
app = {};
app.ui = {
    container:"app_area",
    paddingX:20,
    rows: [
        { paddingY:20, cols:toolbar },
        list    
    ]
};

Пока ничего сложного. Мы создаем тулбар с двумя кнопками. Одна из них — обычная кнопка (button) для добавления записей, а вторая — сегментная кнопка (segmented) для выбора элементов, которые мы хотим видеть в данный момент. Чтобы выровнять левую и правую кнопки вдоль левой и правой границы соответственно, добавляем один пустой плейсхолдер между кнопками. У этого компонента нет фиксированной ширины, поэтому он займёт все свободное пространство между кнопками.

Кроме того, мы создаем список с помощью webix list компонента, который будет содержать все Todo записи. Пока что наше приложение не содержит логики, и поэтому не интерактивно.

Как видите, вместо размещения всех элементов внутри единой json структуры, мы поместили различные части в отдельные переменные, а затем присоединили их к основной структуре app.ui. Такой подход позволяет упростить создание интерфейса, так как теперь вы можете работать с относительно небольшими блоками кода, а не с одной большой структурой.

Здесь и далее, для проверки результатов вы можете открыть index.html в браузере (не забудьте открыть его через http).

Добавление Dynamic

Давайте начнем с создания кнопки “Add new Task”. Сначала нам нужно написать js код, который будет осуществлять логику добавления задачи. Это достаточно просто (здесь и далее мы будем добавлять js код в файл app.js):

function add_task(){
//добавляем новую запись в начало списка
var id = $$("list").add({
            done:-1,
            task:"TODO"
        }, 0);
 //изменяем размеры списка, чтобы разместить все элементы
//resize list to fit all elements
        $$("list").resize();
    }

для того, чтобы прикрепить данное действие к кнопке, изменим код кнопки следующим образом:

 {view:"button", value:"And new task", align:"left", width: 150,
        click:add_task },

Теперь, запустив приложение в браузере, вы можете увидеть, что нажатие на кнопку “add” будет добавлять новую запись в лист.

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

function mark_task(id){
    $$("list").getItem(id).done = 1;
    $$("list").updateItem(id);
}

После этого, давайте настроим внешний вид списка для активных и закрытых задач соответственно. То, каким образом представляются элементы списка, задаётся шаблоном template, который может быть строкой или функцией. Мы будем использовать второй подход:

var list = {
    view:"list", id:"list", borderless:true, autoheight:true,
    type:{  
        height:55, width:800,
        active:"<div class='todo_check'><input type='button' value='Finish'></div>",
        closed:"<div class='todo_check_done'> Done </div>",
        template:function(obj, common){
            var active = (obj.done == -1);
            if (active)
                return common.active + obj.value + common.drag;
            else
                return common.closed + obj.value;
        }
    },
    onClick:{
        "todo_check" : mark_task
    }
};

Помимо обновления шаблона, мы добавили обработчик событию onClick, который вызывает функцию mark_task каждый раз, когда мы кликаем по элементу с «todo_check» css классом.

Теперь, если вы откроете «index.html» в браузере, у вас появится возможность добавлять задачу и отмечать ее сделанной(“done”), нажимая на кнопку “finish”.

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

//фильтрация грида
    function list_filter(obj){
        var val = $$('selector').getValue() || -1;

        $$('list').filter(function(obj){
            return obj.done == val;
        });
        $$('list').resize();
    }
[/c]

Чтобы этот код выполнялся каждый раз, когда состояние сегментной кнопки меняется, нам необходимо изменить её код, добавив обработчик её событию onChange:

[cc lang="js"]
{view:"segmented", id:"selector", width: 200, options: [
            { id:-1, value:"Active"},
            { id:1, value:"Finished"}
        ],
        on:{ onChange : list_filter }
    }

Фильтрацию следует применять также и при изменении состояния отдельного элемента (когда активная задача переходит в выполненные), поэтому давайте настроим функцию «mark_task» следующим образом:

function mark_task(e, id){
$$(“list”).getItem(id).done = 1;
$$(“list”).updateItem(id);
list_filter();
}

Теперь элементы списка list показываются в соответствии с состоянием сегментной кнопки. В каждый момент времени видны либо только активные либо только закрытые задачи.

Сохранение данных

На данный момент мы теряем все данные после каждого обновления, что несколько разочаровывает. Давайте решим эту проблему. Поскольку серверного кода у нас нет, будем сохранять данные в localStorage (хранилище, которое имеется во всех современных браузерах).

var data_proxy = {
    $proxy :true,
    load:function(view, callback){
        view.parse(webix.storage.local.get("data"));
    },
    save:function(view, data){
        webix.storage.local.put("data", view.serialize(true) );
        webix.dp(view).reset();
    }
};

Data_proxy — это объект, который будет обрабатывать все операции с данными. Есть два метода. Метод save() будет использоваться в случае, когда нам нужно будет сохранить данные. Он будет отправлять текущие данные в локальном хранилище в виде объектов. Второй метод, load() будет загружать сохранённые данные из хранилища.

Такой метод хранения актуальных данных можно легко прикрепить к компоненту list, с помощью которого сделан наш список:

var list = {
    view:"list", id:"list", borderless:true, autoheight:true,
    // ... skipping the list's config ...
    ready: list_filter,
    url: data_proxy,
    save:data_proxy
};

Этот код говорит листу загружать и сохранять данные, используя data_proxy объект ( который работает с локальным хранилищем). Кроме того, мы вызываем фильтрацию списка (функция list_filter) после первой загрузки данных.

Откройте «index.html» в браузере. Теперь все задачи, которые вы будете добавлять, а также состояние задач, будет сохраняться после перезагрузки страницы.

Редактирование Списка

Последнее, что нам нужно добавить, — это возможность редактирования элементов. Мы можем добавлять и маркировать элементы, но не можем менять текст внутри них. В Webix есть возможность добавлять редакторы к различным компонентам. У некоторых из них (datatable и tree) эта функциональность является встроенной, другие же требуют выполнения дополнительных действий.

Поскольку компонент list не поддерживает операции редактирования по умолчанию, мы должны создать наш собственный компонент, который наследует от листа, но обладает возможностью редактирования своих элементов.

webix.protoUI({
    name:"editlist"
}, webix.EditAbility, webix.ui.list);

Теперь в код нашего списка выглядит следующим образом:

var list = {
    view:"editlist", id:"list", borderless:true, autoheight:true,
    // ... skipping the list's config ...
    editable:true, editor:"text"
};

Мы создали наш собственный editlist компонент на основе list компонента и добавили ему два параметра при инициализации — подключенли редактирование (editable=true) и определили типа редактора(editor=”text”).

Откройте страницу в браузере. Теперь вы можете открыть редактор, дважды кликнув по любому элементу списка. Закрыть редактор можно, кликнув в любом месте страницы или нажав на одну из клавиш: Enter или Escape (чтобы подтвердить или отменить редактирование, соответственно). Результаты редактирования будут автоматически сохранены, и отобразятся в списке после обновления страницы.

Чтобы редактирование работало с большей пользой, давайте добавим еще два обработчика событий. Первый будет блокировать операции редактирования для завершенной задачи, а второй будет удалять задачи, в которых содержится пустой текст.

var list = {
    view:"editlist", id:"list", borderless:true, autoheight:true,
    // ... skipping the list's config ...
    on:{
        //disallow edit for finished tasks
        onBeforeEditStart:function(id){
            if (this.getItem(id).done == 1)
                return false;
        },
        //delete empty task
        onAfterEditStop:function(state, editor){
            if (state.value == "")
                this.remove(editor.id);
        }
    }
};

Итак, главная функциональность нашего приложения готова.

Drag-n-drop

Поскольку у нас уже есть работающее приложение “todo”, давайте добавим еще одну возможность — изменение порядка элементов с помощью функциональности drag-n-drop. При работе с обычным html, это задача не из легких, в то время как Webix предоставляет готовое решение, которое нужно лишь немного настроить:

var list = {
    view:"editlist", id:"list", borderless:true, autoheight:true,
    // ... skipping the list's config ...
    on:{
        // ... skipping the list's config ...
        //save data after reordering
        onAfterDropOrder:function(id){
            webix.dp(this).save(id);
        }
    },
    drag:"order"

Опция “drag” активирует функциональность drag-n-drop. Ее значение “order” дает компоненту “list” инструкцию использовать drag-n-drop только для изменения порядка элементов. Дополнительный обработчик будет запускать сохранение данных после окончания операции drag-n-drop.

Анализ результатов

Итак, пройдя все этапы, мы создали простое «Todo» приложение. Возможно, вам показалось, что было написано слишком много кода для его построения, однако, на самом деле, мы использовали всего 100 строк нашего приложения. Это и есть отличительная черта Webix — широкий набор функций с малым количеством кода. Webix позволяет сконцентрироваться на описании функциональных возможностей приложений, вместо того чтобы работать с сырым HTML и изобретать собственное решение для каждой задачи.

Таким образом, Webix — это не просто набор компонентов с предопределенными свойствами. Библиотека также содержит множество надстроек для создания общей функциональности, например, для сохранения данных, редактирования, drag-n-drop и т.д. Более того, если существующие компоненты не вполне отвечают вашим потребностям, можно создать свои собственные путем наследования от имеющихся, а также реорганизации существующих блоков функциональности.