Grid with Ajax, ExtJS and ASP.NET MVC

Introduction

In this article I want to cover a very good feature of ExtJS JavaScript library. I like a lot the use of ExtJS for the client scripting. This is the creation of very good experience grid with paging and sorting techniques. This is a common requirement in any enterprise application where we need to display a list of business entities in a fancy way. ExtJS, which is a very good JavaScript library for the development of client-side components in Web applications, has support for grid with paging and sorting with a very good look and feel providing a nice user experience. In this article, I will illustrate step by step how to implement a listing requirement using a paging and sorting grid, ExtJS and ASP.NET MVC.

Getting started with the solution

The first step is to open Visual Studio.NET 2008 and create an ASP.NET MVC Web application (see Figure 1).

 

Figure 1

In order to distribute the ExtJS libraries into our project, we need to create a directory named ext in the Scripts directory in the project and copy the following files and directories from the latest ExtJS distribution: ext-base.js file, ext-all.js file from the adapter/ext directory, and the resources directory. Then, you must include these artifacts in your project (see Figure 2).

 

Figure 2

Next step is to include the supporting ExtJS files in your project in a common place as the Site.Master file. This must be included inside the head element. We also need to include a new asp:ContentPlaceHolder to include the scripts particular for each page. We need to comment the default styles in ASP.NET MVC (highlighted in yellow in the Listing 1), because they make the ExtJS grid to look odd, for example, the columns of the grid don’t align with the underlying data and the styles of ExtJS don’t apply well. I need to research about this strange issue later (see Listing 1).

<head runat=”server”>

    <title><asp:ContentPlaceHolder ID=”TitleContent” runat=”server” /></title>

    <!–

    <link href=”../../Content/Site.css” rel=”stylesheet” type=”text/css” />

    –>

    <link href=”../../Scripts/ext/resources/css/ext-all.css” rel=”stylesheet” type=”text/css” />

      <script src=”../../Scripts/ext/ext-base.js” type=”text/javascript”></script>

      <script src=”../../Scripts/ext/ext-all.js” type=”text/javascript”></script>

    <asp:ContentPlaceHolder ID=”Scripts” runat=”server” />

</head>

Listing 1

Next step is to define the domain model for the enterprise application. We’re going to display a list of products and support paging and sorting techniques. The information related to the Product entity is stored in the Production.Product table on the AdventureWorks database. As the persistent layer, we’re going to use Linq to SQL technology, so right-click on the model directory on the solution and select Add | New Item option from the context menu and create a Linq to SQL classes artifact (see Figure 3).

 

Figure 3

Then create the definition of the Product entity (see Figure 4).

 

Figure 4

Next step is to create the DAL component with the CRUD operations by implementing the Repository design pattern. According to our use case, we just need a list of products, so we define two methods: one for getting the total number of products, and the other one to get the underlying list of products. You can see that we have added support to paging and sorting techniques in our GetAllProducts method in order to get a list of products more efficiently (see Listing 2).

using System;

using System.Collections.Generic;

using System.Data;

using System.Configuration;

using System.Linq;

using System.Linq.Dynamic;

using System.Web;

using System.Web.Security;

using System.Web.UI;

using System.Web.UI.HtmlControls;

using System.Web.UI.WebControls;

using System.Web.UI.WebControls.WebParts;

using System.Xml.Linq;

 

namespace GridAjaxExtJSMvcApp.Models

{

    public class ProductsRepository

    {

        private ProductsDataContext _productDataContext = new ProductsDataContext();

 

        public int GetCount()

        {

            return this._productDataContext.Products.Count();

        }

 

        public IQueryable<Product> GetAllProducts(int nStartRow, int nRowCount, string strSort, string strSortDir)

        {

            IQueryable<Product> qryProducts = from product in this._productDataContext.Products

                                              orderby product.ProductID

                                              select product;

            string strCriteria = strSort + ” ” + strSortDir;

            return qryProducts.OrderBy(strCriteria).Skip(nStartRow).Take(nRowCount);

        }

    }

}

Listing 2

In the code in the Listing 2, you can see that we are using dynamic queries in Linq to SQL, that is, converting strings to SQL queries that can be parsed into an expression tree. Because creating dynamic queries in Linq is not a very trivial task, we’re going to use a library written in C# which can be downloaded from http://msdn2.microsoft.com/en-us/vcsharp/bb894665.aspx. After that, I create a Class Library project include the Dynamic.cs file and compile in order to create a distributable assembly. After that, we need to include this assembly and add the “using System.Linq.Dynamic;” statement to our code. Finally, let create a dynamic sort criteria in a string and use this criteria in the OrderBy method of the Linq query (highlighted in yellow in the Listing 2).

Next step is to add a controller to our application to handle the HTTP request in order to get the list of products and displaying them. Right-click on the controller folder and select Add | Controller option from the context menu. Let’s call this controller ProductController (see Figure 5).

 

Figure 5

Then, we need to create a view for each Index action method (the default action method) of this controller, so right-click on this method and select Add View option from the context menu for defining the view (see Figure 6).

 

Figure 6

Then the Index.aspx view inside the Views/Products directory is created. We’re going to display a list of products using ExtJS grid support, so we need to create a JavaScript file inside the Scripts folder and reference it from this view inside asp:Content tag to be included in the final HTML output (highlighted in yellow). We also need to add a container for the ExtJS grid using the div element (highlighted in green) (see Listing 3).

<%@ Page Title=”” Language=”C#” MasterPageFile=”~/Views/Shared/Site.Master” Inherits=”System.Web.Mvc.ViewPage” %>

 

<asp:Content ID=”Scripts” ContentPlaceHolderID=”Scripts” runat=”server”>

 

      <script src=”../../Scripts/productsgrid.js” type=”text/javascript”></script>

</asp:Content>

 

<asp:Content ID=”Content1″ ContentPlaceHolderID=”TitleContent” runat=”server”>

      Index

</asp:Content>

 

<asp:Content ID=”Content2″ ContentPlaceHolderID=”MainContent” runat=”server”>

 

    <h2>Products</h2>

    <div id=”gridProductList”></div>

</asp:Content>

Listing 3

Now it’s the time to work hard with Ext libraries. You need to go to the http://www.extjs.com/products/extjs/ site and select the paging grid from the example. You can also see the examples in the examples directory in the ExtJS distribution. After that, we need to get the underlying JavaScript example file for this component. In this case, the JavaScript file is paging.js and the example code is shown in the following listing (see Listing 4).

/*!

 * Ext JS Library 3.0.3

 * Copyright(c) 2006-2009 Ext JS, LLC

 * licensing@extjs.com

 * http://www.extjs.com/license

 */

Ext.onReady(function(){

    // create the Data Store

    var store = new Ext.data.JsonStore({

        root: ‘topics’,

        totalProperty: ‘totalCount’,

        idProperty: ‘threadid’,

        remoteSort: true,

 

        fields: [

            ‘title’, ‘forumtitle’, ‘forumid’, ‘author’,

            {name: ‘replycount’, type: ‘int’},

            {name: ‘lastpost’, mapping: ‘lastpost’, type: ‘date’, dateFormat: ‘timestamp’},

            ‘lastposter’, ‘excerpt’

        ],

        // load using script tags for cross domain, if the data in on the same domain as

        // this page, an HttpProxy would be better

        proxy: new Ext.data.ScriptTagProxy({

            url: ‘http://extjs.com/forum/topics-browse-remote.php&#8217;

        })

    });

    store.setDefaultSort(‘lastpost’, ‘desc’);

    // pluggable renders

    function renderTopic(value, p, record){

        return String.format(

                ‘<b><a href=”http://extjs.com/forum/showthread.php?t={2}” target=”_blank”>{0}</a></b><a href=”http://extjs.com/forum/forumdisplay.php?f={3}” target=”_blank”>{1} Forum</a>’,

                value, record.data.forumtitle, record.id, record.data.forumid);

    }

    function renderLast(value, p, r){

        return String.format(‘{0}<br/>by {1}’, value.dateFormat(‘M j, Y, g:i a’), r.data[‘lastposter’]);

    }

    var grid = new Ext.grid.GridPanel({

        width:700,

        height:500,

        title:’ExtJS.com – Browse Forums’,

        store: store,

        trackMouseOver:false,

        disableSelection:true,

        loadMask: true,

        // grid columns

        columns:[{

            id: ‘topic’, // id assigned so we can apply custom css (e.g. .x-grid-col-topic b { color:#333 })

            header: “Topic”,

            dataIndex: ‘title’,

            width: 420,

            renderer: renderTopic,

            sortable: true

        },{

            header: “Author”,

            dataIndex: ‘author’,

            width: 100,

            hidden: true,

            sortable: true

        },{

            header: “Replies”,

            dataIndex: ‘replycount’,

            width: 70,

            align: ‘right’,

            sortable: true

        },{

            id: ‘last’,

            header: “Last Post”,

            dataIndex: ‘lastpost’,

            width: 150,

            renderer: renderLast,

            sortable: true

        }],

        // customize view config

        viewConfig: {

            forceFit:true,

            enableRowBody:true,

            showPreview:true,

            getRowClass : function(record, rowIndex, p, store){

                if(this.showPreview){

                    p.body = ‘<p>’+record.data.excerpt+'</p>’;

                    return ‘x-grid3-row-expanded’;

                }

                return ‘x-grid3-row-collapsed’;

            }

        },

        // paging bar on the bottom

        bbar: new Ext.PagingToolbar({

            pageSize: 25,

            store: store,

            displayInfo: true,

            displayMsg: ‘Displaying topics {0} – {1} of {2}’,

            emptyMsg: “No topics to display”,

            items:[

                ‘-‘, {

                pressed: true,

                enableToggle:true,

                text: ‘Show Preview’,

                cls: ‘x-btn-text-icon details’,

                toggleHandler: function(btn, pressed){

                    var view = grid.getView();

                    view.showPreview = pressed;

                    view.refresh();

                }

            }]

        })

    });

    // render it

    grid.render(‘topic-grid’);

    // trigger the data store load

    store.load({params:{start:0, limit:25}});

});

Listing 4

What we need is to adapt this to our own configuration. Copy the code in our productsgrid.js JavaScript file. Let’s start configuring. The Ext.onReady function is called when the DOM is ready to be used. It’s a good practice to configure all the ExtJS code in there, because we know that we can access safely to the DOM elements.

The first element to be configured is the data store object which is an Ext.data.JsonStore object. It defines the HTTP method to access to the data (GET, POST) and information about the object list serialization. The root property specifies the root of the list of entities in JSON format. The totalProperty property specifies how many records of the products there exist in the database. The id property specifies the primary key of the records. The remoteSort property specificies if we want to do a sort on the server. The method property specifies the HTTP method used to get the data. The url property specifies the url to the data source, in this case, it’s the same action to show the view on the controller but using the POST method. The fields property specifies the list of fields to display. This is a JavaScript array of objects. You need to specify the name and type of each element. The setDefaultSort method is self-descriptive. For our example, we have the following datastore element. Remember to delete the proxy: config on the original ExtJS grid file (see Listing 5).

    // create the Data Store

    var store = new Ext.data.JsonStore({

        root: ‘products’,

        totalProperty: ‘totalRecords’,

        id: “ProductID”,

        remoteSort: true,

        autoLoad: false,

        method: “POST”,

        url: “product”,

        fields: [ {name:’ProductID’, type:’int’},

                  {name:’Name’, type:’string’},

                  {name:’Color’, type: ‘string’},

                  {name:’ReorderPoint’, type:’float’},

                  {name:’StandardCost’, type:’float’},

                  {name:’ListPrice’, type:’float’}

        ]

    });

    store.setDefaultSort(‘ProductID’, ‘asc’);

Listing 5

Then we have to define two renders for custom output. The first one is to output the product name enclosed in a reference for its edition. The second one is the output of possible operations on each record, in this case, we can edit or delete each record (see Listing 6).

    // pluggable renders

    function renderProduct(value, p, record)

    {

        return String.format(

                ‘<span><b><a href=”product/edit/{1}” target=”_blank”>{0}</a></b></span>’,

                value, record.id);       

    }

 

    function renderOperations(value, p, record)

    {

        return String.format(

                ‘<span><b><a href=”product/edit/{0}” target=”_blank”>Edit</a></b></span><br/><span><b><a href=”product/delete/{0}” target=”_blank”>Delete</a></b></span>’,

                record.id);       

    }

Listing 6

Next element is the grid that shows the list of products which is an instance of Ext.grid.GridPanel. The width, height, autoheight,title, trackMouseOver, disableSelection, stripeRows, viewConfig and loadMask properties are self-descriptive. The store property is the reference to the JSON store defined before (see Listing 7).

        width:740,

        autoHeight: true,

        title:’List of Products’,

        store: store,

        stripeRows: true,

        autoload: true,

Listing 7

Then, the columns definition comes, and for each column we must specify the header, dataIndex, width, a renderer for the data, if sortable properties (see Listing 8).

        // grid columns

        columns:[{

            id: ‘ProductID’,

            header: “Name”,

            dataIndex: ‘Name’,

            width: 80,

            renderer: renderProduct,

            sortable: true

        },{

            header: “Price”,

            dataIndex: ‘ListPrice’,

            width: 40,

            renderer: Ext.util.Format.usMoney,

            align: “right”,

            sortable: true

        },{

            header: “Color”,

            dataIndex: ‘Color’,

            align: “center”,

            width: 40,

            sortable: true

        },{

            header: “Reorder Point”,

            dataIndex: ‘ReorderPoint’,

            width: 40,

            align: “right”,

            sortable: true

        }

        ,{

            header: “Standard Cost”,

            dataIndex: ‘StandardCost’,

            width: 40,

            renderer: Ext.util.Format.usMoney,

            align: “right”,

            sortable: true

        }

        ,{

            header: “Operations”,

            id: ‘ProductID’,

            width: 50,

            renderer: renderOperations,

            sortable: false,

            menuDisabled: true

        }       

        ],

Listing 8

Next we need to configure the grid view (see Listing 9).

        // customize view config

        viewConfig: {

            forceFit:true

        },

Listing 9

Next we go to configure the paging bar element (see Listing 10).

        // paging bar on the bottom

        bbar: new Ext.PagingToolbar({

            pageSize: 10,

            store: store,

            displayInfo: true,

            displayMsg: ‘Displaying products {0} – {1} of {2}’,

            emptyMsg: “No products to display”

        })

Listing 10

And finally, we need to set where the grid will be rendered and load the data (see Listing 11).

    // render it

    grid.render(‘gridProductList’);

    // trigger the data store load

    store.load({params:{start:0, limit:10}});

Listing 11

The whole code for the ExtJS grid is shown in the following list (see Listing 12).

 

Ext.onReady(function(){

 

    // create the Data Store

    var store = new Ext.data.JsonStore({

        root: ‘products’,

        totalProperty: ‘totalRecords’,

        id: “ProductID”,

        remoteSort: true,

        autoLoad: false,

        method: “POST”,

        url: “product”,

        fields: [ {name:’ProductID’, type:’int’},

                  {name:’Name’, type:’string’},

                  {name:’Color’, type: ‘string’},

                  {name:’ReorderPoint’, type:’float’},

                  {name:’StandardCost’, type:’float’},

                  {name:’ListPrice’, type:’float’}

        ]

    });

    store.setDefaultSort(‘ProductID’, ‘asc’);

 

 

    // pluggable renders

    function renderProduct(value, p, record)

    {

        return String.format(

                ‘<span><b><a href=”product/edit/{1}” target=”_blank”>{0}</a></b></span>’,

                value, record.id);       

    }

 

    function renderOperations(value, p, record)

    {

        return String.format(

                ‘<span><b><a href=”product/edit/{0}” target=”_blank”>Edit</a></b></span><br/><span><b><a href=”product/delete/{0}” target=”_blank”>Delete</a></b></span>’,

                record.id);       

    }   

 

    var grid = new Ext.grid.GridPanel({

        width:740,

        autoHeight: true,

        title:’List of Products’,

        store: store,

        stripeRows: true,

        autoload: true,

 

        // grid columns

        columns:[{

            id: ‘ProductID’,

            header: “Name”,

            dataIndex: ‘Name’,

            width: 80,

            renderer: renderProduct,

            sortable: true

        },{

            header: “Price”,

            dataIndex: ‘ListPrice’,

            width: 40,

            renderer: Ext.util.Format.usMoney,

            align: “right”,

            sortable: true

        },{

            header: “Color”,

            dataIndex: ‘Color’,

            align: “center”,

            width: 40,

            sortable: true

        },{

            header: “Reorder Point”,

            dataIndex: ‘ReorderPoint’,

            width: 40,

            align: “right”,

            sortable: true

        }

        ,{

            header: “Standard Cost”,

            dataIndex: ‘StandardCost’,

            width: 40,

            renderer: Ext.util.Format.usMoney,

            align: “right”,

            sortable: true

        }

        ,{

            header: “Operations”,

            id: ‘ProductID’,

            width: 50,

            renderer: renderOperations,

            sortable: false,

            menuDisabled: true

        }       

        ],

 

        // customize view config

        viewConfig: {

            forceFit:true

        },

 

        // paging bar on the bottom

        bbar: new Ext.PagingToolbar({

            pageSize: 10,

            store: store,

            displayInfo: true,

            displayMsg: ‘Displaying products {0} – {1} of {2}’,

            emptyMsg: “No products to display”

        })

    });

 

    // render it

    grid.render(‘gridProductList’);

    // trigger the data store load

    store.load({params:{start:0, limit:10}});

});

 

Listing 12

The last part of the solution is to develop the server-side code to which the ExtJS grid communicates. We need to support the GET method to show the initial view and the POST method to implement the logic to get a list of products. In this case, the Index action method has the following signature to get the paging and sorting information (see Listing 13).

public ActionResult Index(int start, int limit, string dir, string sort)

Listing 13

And the implementation for the controller is shown in the Listing 14:

using System;

using System.Collections.Generic;

using System.Linq;

using System.Web;

using System.Web.Mvc;

using System.Web.Mvc.Ajax;

using GridAjaxExtJSMvcApp.Models;

 

namespace GridAjaxExtJSMvcApp.Controllers

{

    public class ProductController : Controller

    {

        private ProductsRepository _productsRepository = new ProductsRepository();

 

        //

        // GET: /Product/

        [AcceptVerbs(HttpVerbs.Get)]

        public ActionResult Index()

        {

            return View();

        }

 

        //

        // POST: /Products/

        [AcceptVerbs(HttpVerbs.Post)]

        public ActionResult Index(int start, int limit, string dir, string sort)

        {

            if (Request.IsAjaxRequest())

            {

                var arrProducts = this._productsRepository.GetAllProducts(start, limit, sort, dir);

                var nProductCount = this._productsRepository.GetCount();

 

                var results = (new

                {

                    totalRecords = nProductCount,

                    products = arrProducts

                });

 

                return Json(results);

            }

            else

            {

                return RedirectToAction(“Index”);

            }

        }

 

    }

}

 

Listing 14

Now let’s test the solution (see Figure 7).

 

Figure 7

Conclusion

In this article, I’ve illustrated how to implement a grid with paging and sorting using ExtJS and ASP.NET MVC.

Advertisements

5 thoughts on “Grid with Ajax, ExtJS and ASP.NET MVC

  1. Pingback: MVP Factor

  2. Pingback: Fernando Garcia Lorea

  3. Great article! Thanks JC, on your ProductRepository, it select all fields in table Product, I’m wondering now how to select just particular fields to be send onto Json result
    Cheers,

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