Vlad's blog

In programming veritas

Ext JS – modern framework for creating RIA

leave a comment »

Modern web application has long ceased to be a set of HTML pages, when interaction is reduced to a simple processing on the server. The growing popularity of RIA requires an infrastructure that allows to program complex logic on the client. In connection with this trend towards the creation of JavaScript frameworks that provide a framework for application design in accordance with such well-known patterns like MVC and MVVM.
Ext JS is one of the popular frameworks for building Web applications that look and behave like traditional desktop application. Typically, such an application is a single HTML page container that contains a hierarchy of widgets. A server role is reduced to the processing of AJAX requests and implement the business logic that can be realized only on the server.
To illustrate the above, consider an example using Ext JS. Suppose we are developing a project management application. The basic application abstraction is a task that has the following attributes.

  • Project which is a container of task
  • Description
  • Status (Not Started,In Progress,Closed)
  • Estimation (man hours)
  • Progress (percentage of completion)


Our task – to develop a page that will display a list of tasks, grouped by project.

Model

The first thing to start is to define a model for a task.

Ext.define('PM.model.Task', {
    extend: 'Ext.data.Model',
    fields: [
        { name: 'descr', type: 'string' },
        { name: 'status', type: 'integer' },
        { name: 'estimate', type: 'integer' },
        { name: 'progress', type: 'integer' },
        { name: 'project', type: 'string'}
        ]
});

In this case, the model is simply a set of related fields. The field descr contains a description of task, status is integer constant (0-Not Started ,1-In Progress ,2-Closed), estimate is estimation in man hours, progress stores the percentage of completion and project is the project name. A model must be inherited from Ext.data.model. As you can see, Ext JS offers its own syntax for defining classes, which significantly facilitates the creation of object-oriented applications. For example the method getRemainingHours looks like below.

Ext.define('PM.model.Task', {
    extend: 'Ext.data.Model',
    ...

    getRemainingHours: function () {
        return this.get('estimate') - (100 - this.get('progress')) * this.get('estimate')/100
    }
});

Store

An important aspect when developing an application on the client side is the question of how to receive and store data from the server, and synchronize changes. To solve this problem, Ext JS provides the concept of storage (Store), which is associated with the model. The store implements Repository pattern.

Ext.define('PM.store.Tasks', {
    extend: 'Ext.data.Store',
    model: 'PM.model.Task',
    groupField: 'project'
});

The above code sets PM.model.Task as data type for a store. Objects are grouped by project field. In addition, you can specify criteria for filtering and sorting. Store is responsible for providing the data requested by other system components. The easiest option is to define an array of data at the store’s initialization.

Ext.define('PM.store.Tasks', {
    extend: 'Ext.data.Store',
    model: 'PM.model.Task',
    groupField: 'project',
    data: [
        { project: 'Flex.Demo', descr: 'Support for build labels within versions', status: 0, estimate: 1, progress: 0 },
        { project: 'OPC server 2.0', descr: 'Customise emails template', status: 1, estimate: 5, progress: 25 },
        { project: 'Flex.Demo', descr: 'Add new type of report', status: 0, estimate: 10, progress: 0 }
    ]
});

View

The view is responsible for showing the model. As a rule, creating a view is to use one or more widgets that are available in Ext JS. In our case, we will use Ext.grid.Panel, which allows you to display a list of tasks in tabular form.

Ext.define('PM.view.List', {
    extend: 'Ext.grid.Panel',
    alias: 'widget.tasklist',

    title: 'My Tasks',
    features: [groupingFeature],
    store: 'Tasks',

    initComponent: function () {
        this.columns = [
            { header: 'Task', dataIndex: 'descr', flex: 1 },
            { header: 'Status', dataIndex: 'status', flex: 1, renderer: formatStatus },
            { header: 'Estimate', dataIndex: 'estimate', flex: 1, renderer: function (val) { return val + 'h'; } },
            { header: 'Progress', dataIndex: 'progress', flex: 1, renderer: function (val) { return val + '%'; } }
        ];

        this.callParent(arguments);
    }
});

When you register we specify the widget’s name (alias), which will be used to create instances of it. In addition, you must specify the repository. The method initComponent set the table structure by configuring the columns.In the simplest case is sufficient to setup the title and name of the field from which to take data. If data needs to be specially formatted, you can specify function that renders the data before displaying. In our case, we format the field status, replacing numeric constants textual representation.

function formatStatus(val) {
    var res;

    switch(val)
    {
    case 0:
      res = 'Not started';
      break;
    case 1:
      res = 'In progress';
      break;
    case 2:
      res = 'Closed';
      break;
    default:
      res = 'Unknown status';
    }

    return res;
}

The grouping of the project is enabled by creating a special object Ext.grid.feature.Grouping. This facility will be used in constructing the table.

var groupingFeature = Ext.create('Ext.grid.feature.Grouping', {
    groupHeaderTpl: 'Project: {project} ({rows.length} Item{[values.rows.length > 1 ? "s" : ""]})'
});

Controller

The controller combines the model, the storage and presentation together. In our example, we do not process any UI events, so the controller is simple.

Ext.define('PM.controller.Tasks', {
    extend: 'Ext.app.Controller',
    stores: ['Tasks'],
    models: ['Task'],
    views: ['List']
});

Putting it all together

In order to run our example we must do two things. First of all, somewhere must be the code to initialize the application.
Тhe initialization code is usually placed in a separate module app.js, which contains the following code.

Ext.application({
    name: 'PM',

    appFolder: 'scripts/app',

    controllers: [
        'Tasks'
    ],

    launch: function () {
        Ext.create('Ext.container.Viewport', {
            layout: 'fit',
            items: [
                {
                    xtype: 'tasklist'
                }
            ]
        });
    }
});

In the launch method, we create a widget tasklist, which was already defined in the view. The widget itself is located inside the container Ext.container.Viewport. In addition, we need html page, which will serve as a container for the application.

<html>
<head>
    <title>Test</title>
 
    <link rel="stylesheet" type="text/css" href="extjs/resources/css/ext-all.css">
    <script type="text/javascript" src="extjs/ext-debug.js"></script>
    <script type="text/javascript" src="app/app.js"></script>
</head>
<body></body>
</html>

As a result, our test application looks like below.

Directories structure

Ext JS assumes that the directory structure created in accordance with certain agreements. Controllers, views, and stores placed in the appropriate folder. You can certainly use their own hierarchy, but it is better to adhere to generally accepted.

Interaction with the server

In our example we directly associate data with store. In a real application data is received from the server. In addition, HTML5 offers the ability to store data on the client using the concept of Local Storage. To abstract from all the possible ways to interact with data sources Ext JS proposes to use a proxy. The proxy defines the method of interaction (AJAX, REST, Local Storage), and classes to serialize requests. Typically, a proxy is defined with the store.

Ext.define('PM.store.Tasks', {
    extend: 'Ext.data.Store',
    model: 'PM.model.Task',
    autoLoad: true,
    proxy: {
        type: 'ajax',
        url: '/home/tasks',
        reader: {
            type: 'json'
        },
        writer: {
            type: 'json'
        }
    },
    groupField: 'project'
});

Here we specify that the type of interaction is AJAX, and the format of requests is JSON. ‘/home/tasks’ specifies url for handling requests. On the server side the handler looks like this (ASP.Net MVC).

public class HomeController : Controller
{
    [HttpGet]
    public JsonResult Tasks()
    {
        var tasks = new[]
                        {
                            new {project="Project1",descr="description1",status=1, estimate=1,progress=10},
                            new {project="Project2",descr="description2",status=2, estimate=2,progress=20}
                        };

        return Json(tasks, JsonRequestBehavior.AllowGet);
    }
}

Ext JS also offers its own implementation of the Object Request Broker – Ext JS Direct. It provides infrastructure to facilitate client-server communication. Also implemented Batching mechanism to combine multiple AJAX requests to the one that significantly reduces the load on the server. Unfortunately Sencha does not provide its own implementation of this technology for ASP.Net MVC, although there are fairly stable party implementations.

Advertisements

Written by vsukhachev

December 16, 2011 at 5:14 am

Posted in Development

Tagged with

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: