ToDo приложение на JavaScript

https://github.com/ananddayalan/extjs-by-example-todo
Структура проекта:

Мы воспользуемся REST proxy у стора для того чтобы соединится с REST-сервисом. Для создания самого RESTful-сервиса мы воспользуемся языком статически типизированным Go. Его синтаксис смахивает на синтаксис Си, поэтому необязательно изучать Golang чтобы понять код.

Сначала нужно создать стор. Мы будем хранить его во ViewModel, но возможно его хранить отдельно. Нижеприведенная ViewModel имеет три поля:  id, desc (description), done (indicates whether or not ToDo has been completed). В качестве прокси используется rest с URL-ом tasks. Так как это REST proxy, то сервис должен оперировать в рамках HTTP-операций.

Если вы удаляете запись, то запрос будет по адресу <base URL>/tasks/{id} например,  http://localhost:9001/tasks/23333 и будет использоваться HTTP-команда DELETE. При добавлении записи URL будет http://localhost:9001/tasks, а метод запроса будет POST. Сама запись посылается как JSON.
Ext.define('ToDo.view.toDoList.ToDoListModel', {
    extend: 'Ext.app.ViewModel',
    alias: 'viewmodel.todoList',
    stores: {
        todos: {
            fields: [{
                name: 'id',
                type: 'string'
            }, {
                name: 'desc',
                type: 'string'
            }],
            autoLoad: true,
            sorters: [{
                property: 'done',
                direction: 'ASC'
            }],
            proxy: {
                type: 'rest',
                url: 'tasks',
                reader: {
                    type: 'json',
                },
                writer: {
                    type: 'json'
                }
            }
        }
    }
});

Дальше надо создать вьюху. Нужен список для дел, текстовое поле для добавления нового дела и кнопка Добавить. Вьюха будет автоматически обновляться на основании записей в сторе. В основном вьюха будет создаваться динамически из ViewController-а. В исходной вьюхе оставим только текстовое поле и кнопку для добавления нового дела.
Ext.define('ToDo.view.toDoList.ToDoList', {
    extend: 'Ext.panel.Panel',

    /* Marks these are required classes to be to loaded before
loading this view */
    requires: ['ToDo.view.toDoList.ToDoListController', 'ToDo.view.toDoList.ToDoListModel'],

    xtype: 'app-todoList',
    controller: 'todoList',

    /* View model of the view. */

    viewModel: {
        type: 'todoList'
    },

    items: [{
        xype: 'container',
        items: [{
            xtype: 'container',
            layout: 'hbox',
            cls: 'task-entry-panel',
            defaults: {
                flex: 1
            },
            items: [{
                reference: 'newToDo',
                xtype: 'textfield',
                emptyText: 'Enter a new todo here'
            }, {
                xtype: 'button',
                name: 'addNewToDo',
                cls: 'btn-orange',
                text: 'Add',
                maxWidth: 50,
                handler: 'onAddToDo'
            }]
        }]
    }]
});
Тут мы для кастомизации использовали пару CSS-классов: btn-orange и task-entrypanel.

Далее создадим ViewController. В функции init мы будем читать записи из стора и для каждой записи добавлять строку во вьюху.

Для того чтобы дела можно было удалять добавим соответствующую иконку. Так как UI будет генерировать динамически, то для добавления обработчика события клика по иконке надо использовать следующий код:
Ext.getBody().on('click', function(event, target) {
    me.onDelete(event, target);
}, null, {
    delegate: '.fa-times'
});

Если бы UI не генерировался автоматически, то использовался бы следующий код:
Ext.select('fa-times').on('click', function(event, target) {
    me.onDelete(event, target);
});

Ниже приводится код для ViewController. Метод onAddToDo использует метод lookupReference передавая ссылочное имя из вьюхи.
Ext.define('ToDo.view.toDoList.ToDoListController', {
    extend: 'Ext.app.ViewController',
    alias: 'controller.todoList',
    views: ['ToDo.view.toDoList.ToDoList'],

    init: function() {
        var me = this;

        //Here we're calling the load method of the store and passing
        an anonymous method
        for the callback.So, the load will call the
        server to get the data first and calls the anonymous method.The
        anonymous method then, add the data to the view.
        this.getViewModel().data.todos.load(function(records) {

            Ext.each(records, function(record) {
                //Add a container for each record
                me.addToDoToView(record);
            });
        });

        Ext.getBody().on('click', function(event, target) {
            me.onDelete(event, target);
        }, null, {
            delegate: '.fa-times'
        });

    },

    onAddToDo: function() {

        var store = this.getViewModel().data.todos;

        var desc = this.lookupReference('newToDo').value.trim();
        if (desc != '') {
            store.add({
                desc: desc
            });
            store.sync({
                success: function(batch, options) {
                    this.lookupReference('newToDo').setValue('');
                    this.addToDoToView(options.operations.create[0]);
                },
                scope: this
            });
        }

    },

    addToDoToView: function(record) {
        this.view.add([{
            xtype: 'container',
            layout: 'hbox',
            cls: 'row',
            items: [{
                xtype: 'checkbox',
                boxLabel: record.get('desc'),
                checked: record.get('done'),
                flex: 1
            }, {
                html: '<a class="hidden" href="#"><i taskId="' + record.get('id') + '" class="fa fa-times"></i></a>',
            }]
        }]);
    },

    onDelete: function(event, target) {
        var store = this.getViewModel().data.todos;
        var targetCmp = Ext.get(target);
        var id = targetCmp.getAttribute('taskId');
        store.remove(store.getById(id));
        store.sync({
            success: function() {
                this.view.remove(targetCmp.up('.row').id)
            },
            scope: this
        });
    }
});

Далее создадим сервис на языке программирования Go. После установки инсталляционного пакета надо установить значение для переменной окружения GOROOT. Если установка проводилась в домашнюю директорию, то нужно в файле $HOME/.profile добавить следующие строки:
export GOROOT=/usr/local/go
export PATH=$PATH:$GOROOT/bin

Для этого проекта используется роутер Gorilla mux, поэтому его также необходимо установить:
go get github.com/gorilla/mux
package main

import (
    "fmt"
    "encoding/json"
    "net/http"
    "strconv"
    "github.com/gorilla/mux"
)

type Task struct {
    Id string `json:"id"`
    Desc string `json:"desc"`
    Done bool `json:"done"`
}

var tasks map[string] * Task

func GetToDo(rw http.ResponseWriter, req * http.Request) {

    vars: = mux.Vars(req)
    task: = tasks[vars["id"]]

        js,
    err: = json.Marshal(task)
    if err != nil {
        http.Error(rw, err.Error(), http.StatusInternalServerError)
        return
    }

    fmt.Fprint(rw, string(js))
}

func UpdateToDo(rw http.ResponseWriter, req * http.Request) {
    vars: = mux.Vars(req)
    task: = tasks[vars["id"]]
    dec: = json.NewDecoder(req.Body)
    err: = dec.Decode( & task)
    if err != nil {
        http.Error(rw, err.Error(), http.StatusInternalServerError)
        return
    }

    task.Id = vars["id"]

    retjs,
    err: = json.Marshal(task)
    if err != nil {
        http.Error(rw, err.Error(), http.StatusInternalServerError)
        return
    }

    fmt.Fprint(rw, string(retjs))
}

func DeleteToDo(rw http.ResponseWriter, req * http.Request) {

    vars: = mux.Vars(req)
    delete(tasks, vars["id"])
    fmt.Fprint(rw, "{status : 'success'}")
}

func AddToDo(rw http.ResponseWriter, req * http.Request) {

    task: = new(Task)

        dec: = json.NewDecoder(req.Body)
    err: = dec.Decode( & task)
    if err != nil {
        http.Error(rw, err.Error(), http.StatusInternalServerError)
        return
    }

    tasks[task.Id] = task

    retjs,
    err: = json.Marshal(task)
    if err != nil {
        http.Error(rw, err.Error(), http.StatusInternalServerError)
        return
    }

    fmt.Fprint(rw, string(retjs))
}

func GetToDos(rw http.ResponseWriter, req * http.Request) {

    v: = make([] * Task, 0, len(tasks))

    for _,
    value: = range tasks {
        v = append(v, value)
    }
    js,
    err: = json.Marshal(v)
    if err != nil {
        http.Error(rw, err.Error(), http.StatusInternalServerError)
        return
    }

    fmt.Fprint(rw, string(js))
}

func main() {

    var port = 9001
    router: = mux.NewRouter()
    tasks = make(map[string] * Task)
    router.HandleFunc("/tasks", GetToDos).Methods("GET")
    router.HandleFunc("/tasks", AddToDo).Methods("POST")
    router.HandleFunc("/tasks/{id}", GetToDo).Methods("GET")
    router.HandleFunc("/tasks/{id}", UpdateToDo).Methods("PUT")
    router.HandleFunc("/tasks/{id}", DeleteToDo).Methods("DELETE")
    router.PathPrefix("/").Handler(http.FileServer(http.Dir("../")))

    fmt.Println("Listening on port", port)
    http.ListenAndServe("localhost:" + strconv.Itoa(port), router)
}

Для запуска сервиса надо набрать в терминале:
go run ToDo.go 
Должно появиться что-то вроде:
Listening on port 9001
Если нет никаких ошибок, то можно открыть localhost:9001 чтобы увидеть приложение.