EXTJS CRUD Example Tutorial

In this example, we will show you step by step how to develop a simple TODO CRUD application using ExtJS 6.

On the server-side, we will build REST APIs using Golang.

Ext JS is a popular JavaScript framework that provides rich UI for building web applications with cross-browser functionality. Ext JS is basically used for creating desktop applications.

Ext JS follows MVC/ MVVM architecture.

MVC − Model View Controller architecture (version 4)

MVVM − Model View Viewmodel (version 5)

This architecture is not mandatory for the program, however, it is a best practice to follow this structure to make your code highly maintainable and organized.

EXTJS TODO CRUD Example

In this example, we use Cloudflare CDN for ExtJS 6. If the CDN doesn't work, you may need to update ExtJS lib URLs in index.html and point to the local ExtJS library path.

Let's create an EXTJS project structure as shown below image:

index.html

Let's create an HTML page that includes the ExtJS library and app.js file:

<!DOCTYPE html>
<html>

<head>
    <title>To Do!</title>

    <link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/extjs/6.0.0/classic/theme-crisp/resources/theme-crisp-all.css">
    <link rel="stylesheet" type="text/css" href="resources/css/app.css">

    <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css">

    <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/extjs/6.0.0/ext-all-debug.js"></script>
    <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/extjs/6.0.0/classic/theme-crisp/theme-crisp-debug.js"></script>
   <script type="text/javascript" src="app.js"></script>

</head>

<body></body>

</html>

app.js

This is the main file from where the flow of the application will start, which should be included in the main HTML file using the <script> tag. The app calls the controller of the application for the rest of the functionality.

Ext.application({
    name: 'ToDo',    
    autoCreateViewport: 'ToDo.view.main.Main'
});

Main.js

Create a file named app/view/main/Main.js and add the following code to it:

Ext.define('ToDo.view.main.Main', {
    extend: 'Ext.panel.Panel',
    
    requires: [
        'ToDo.view.toDoList.ToDoList'
    ],
    autoScroll: true,
    xtype: 'app-main',

    items: [{
            xype: 'container',
            layout: {
                type: 'hbox',
                align: 'center',
                pack: 'center'
            },
            items: [
                {
                    xtype: 'app-todoList',
                    flex:1,
                    maxWidth: 700,
                    cls: 'main'
                }
            ]
        }
    ]
});

MainController.js

It is the controller file of Ext JS MVC architecture. This contains all the control of the application, the events listeners, and most of the functionality of the code. It has the path defined for all the other files used in that application such as store, view, model, require mixins.

Create a file app/view/ToDoListController.js and add the following code to it:

Ext.define('ToDo.view.toDoList.ToDoListController', {
    extend: 'Ext.app.ViewController',
    alias: 'controller.todoList',
    views: ['ToDo.view.toDoList.ToDoList'],

    init: function () {
        var me = this;
        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
        });
    }
});

ToDoListModel.js

Let's create an EXTJS Model to list out all the todos in a table.

Create a file app/view/ToDoListModel.js and add the following code to it:

Ext.define('ToDo.view.toDoList.ToDoListModel', {
    extend: 'Ext.app.ViewModel',
    alias: 'viewmodel.todoList',
    stores: {
        todos: {
            fields: [
                {   name: 'id', type: 'string'  },
                {   name: 'desc', type: 'string'    },
                {   name: 'done', type: 'boolean'   }
            ],
            autoLoad: true,
            sorters: [{
                property: 'done',
                direction: 'ASC'
            }],
            proxy: {
                type: 'rest',
                url: 'tasks',
                reader: {
                    type: 'json',
                },
                writer: {
                    type: 'json'
                }
            }
        }
    }
});

ToDoList.js

Let's create an EXTJS Model to add a TODO.

Create a file app/view/ToDoList.js and add the following code to it:
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'
                }]
            }
        ]
    }]
});

app.css

Create a file resources/app.css and add the following code to it:
.hidden {
    visibility: hidden;
}

.row:hover .hidden {
    visibility: visible;
    color: white;
}

.btn-orange,
.btn-orange.x-btn-focus.x-btn-default-small {
    background-color: #F5923E;
    border-color: #F5923E;
    box-shadow: none;
}

.btn-orange.x-btn-over.x-btn-default-small {
    border-color: #BE702E;
    background-color: #BE702E;
}

.row:hover {
    background-color: #92A7B1;
    color: white;
    font-weight: 800;
}

.row a,
.row div {
    background-color: transparent;
    /*    color: black;*/
}

.row a:hover,
.row div:hover {
    color: white;
}

.row,
.task-entry-panel {
    padding: 10px;
    border-bottom: none;
}

.x-form-text-default {
    height: 30px;
}

.task-entry-panel .x-btn-default-small {
    height: 32px;
}

.task-entry-panel {
    background-color: #69787F;
    padding-bottom: 20px;
}

.main {
    background-color: #EEEEF1;
    padding: 20px;
}

CRUD REST API's using GoLang

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)
}
Run GoLang:
go run server/ToDO.go

Run ExtJS App

Open the index.html file in the browser to access this application:



Comments