theKindOfMe

February 5, 2009

A Grid with Ajax/Pagination/Sorting/Filtering on ASP.net MVC with ExtJS and Enitiy Framework

Filed under: Uncategorized — Tags: , , , , , , , — yasi8h @ 9:18 am

I am new to all these JS libraries like jQuery and ExtJS(there are many more out there…). when i got started with asp.net mvc i looked in to jQuery. and i was absolutely amazed with all the functionality it offered with very simple and neat syntax! a while ago i did a post on “Ajax Enabling MVCContrib Grid’s Pagination with jQuery”. In which i added AJAX/pagination support to MVCContrib’s Grid. but with time i needed more and more features from the grid. like sorting and filtering(through AJAX of course). then i came across the wonderful JS library called ExtJS. it have grate UI components done and ready to be used. i love all ExtJS stuff that is related to UI. and their components seems to be very extensible as well.

Ext JS Overview

Ext JS is a cross-browser JavaScript library for building rich Internet applications. It includes:

  • High performance, customizable UI widgets
  • Well designed and extensible Component model
  • An intuitive, easy to use API
  • Commercial and Open Source licenses available

i liked there Grid and it had all the features i needed. so i decided to go ahead with it.

extjs_grid_screenshot

the main points i had to look at:

  • including a ExtJS distribution with my asp.net mvc project which is already using jQuery
  • inserting the relevant JS to render the grid in my views
  • creating the necessary controller actions to support the grid’s actions(get data/sort/filter data…etc)

Including a ExtJS distribution

ExtJS JS library can use other libraries(such as jQuery…) as its base library. so been a fan of jQuery, i choose to build a custom version of ExtJS which uses jQuery as its base.  this version of the library includes the jQuery adapter within it self. but you still need to include the jQuery library by your self. it is NOT included in the custom version that you built for your self.  so in your site.master(you can put these where ever you like, not necessary in the site.master. just make sure these are included where ever you use them) you have to include the libraries in the following order: include jQuery first, then include any jQuery plugins you use and finally include ExtJS(any of your custom JS files should be included afterward).

now you are done including the JS libraries necessary . but ExtJS’s UI components need a few more things to be included in your site.master for them to work correctly. you need to include the necessary style sheets/images used by ExtJS components. to get these download ExtJS SDK. this includes all the style sheets and the images among other things. you will need to include the necessary plugins as well.

  • ext-2.2resourcescssext-all.css
  • ext-2.2resourcesimages – images used by ExtJS components
  • all JS files under ext-2.2examplesgrid-filteringgrid – includes necessary plugins needed for the grid filtering functionality

now you are ready to roll. note that we can use what ever jQuery we want when we are working with extjs components. and as we are using the jQuery core(and also because we are already using jQuery in out mvc project) the size of the ExtJS library is much lower.

Rendering the grid

looking at the samples(look under ext-2.2examplesgrid-filtering) included in the extjs sdk. its not that hard to figure out how to render the grid using javascript. the extjs API reference would be helpful as well.

i ran across a bug in the GridView component when i tried to use disable ‘autoHeight’ ‘feature’ of the grid. how ever there was a fix available for it in the the extjs forums.  following is the JS code that i used in my view(in which the grid resides). i just included them in a script tag within the view. but the best way to use them would be to extract them in to some helper methods and then using the helpers.

some notes about ExtJs grids

  • a grid is ‘bound’ to a data store.
  • when rendering a grid you will have to create a data record, column modal and a data reader(JsonReader in this case).
  • column modal – how the columns are structured
  • data record – how a data record(or a person object, in the context of this example) is structured. what fields does it contain…etc
  • data reader – where to get/ how to read data
  • filters and paging is connected to the grid in a ‘pluggable’ manner

        //Sugguested Fix for the bug: "Grid autoHeight disables horizontal scrolling too"
        Ext.override(Ext.grid.GridView, {
            layout: function() {
                if (!this.mainBody) {
                    return;
                }
                var g = this.grid;
                var c = g.getGridEl(), cm = this.cm,
                expandCol = g.autoExpandColumn,
                gv = this;
                var csize = c.getSize(true);
                var vw = csize.width;
                if (vw < 20 || csize.height < 20) {
                    return;
                }
                if (g.autoHeight) {
                    csize.height = this.mainHd.getHeight() + this.mainBody.getHeight();
                    if (!this.forceFit) {
                        csize.height += this.scrollOffset;
                    }
                }
                this.el.setSize(csize.width, csize.height);
                var hdHeight = this.mainHd.getHeight();
                var vh = csize.height - (hdHeight);
                this.scroller.setSize(vw, vh);
                if (this.innerHd) {
                    this.innerHd.style.width = (vw) + 'px';
                }
                if (this.forceFit) {
                    if (this.lastViewWidth != vw) {
                        this.fitColumns(false, false);
                        this.lastViewWidth = vw;
                    }
                } else {
                    this.autoExpand();
                }
                this.onLayout(vw, vh);
            }
        });
        //end bug fix here
        //////////////////

        Ext.onReady(function() {
            Ext.QuickTips.init(); //for enabling tool tips

            Ext.menu.RangeMenu.prototype.icons = {
                gt: '/Scripts/extjs/plugins/grid-filter/img/greater_then.png',
                lt: '/Scripts/extjs/plugins/grid-filter/img/less_then.png',
                eq: '/Scripts/extjs/plugins/grid-filter/img/equals.png'
            };
            Ext.grid.filter.StringFilter.prototype.icon = '/Scripts/extjs/plugins/grid-filter/img/find.png';

            createAndShowGrid();
        });

        function createAndShowGrid() {

            order = Ext.data.Record.create([
	        { name: 'Id' },
	        { name: 'FirstName' },
	        { name: 'Email' }
        ]);

            orderReader = new Ext.data.JsonReader({
                root: 'dataitems',
                totalProperty: 'totalItems' //number of total records
            },
		    order
	    );
            //data store creation
            ds = new Ext.data.Store({
                proxy: new Ext.data.HttpProxy({
                    url: '/Person/ListJson'
                }),
                reader: orderReader,
                sortInfo: { field: 'Id', direction: "ASC" },
                remoteSort: true
            });

            columnModel = new Ext.grid.ColumnModel([{
                header: 'Person Id',
                dataIndex: 'Id'
            }, {
                header: 'Person Name',
                dataIndex: 'FirstName'
            }, {
                header: 'Person Email',
                dataIndex: 'Email'
            }
	    ]);

            columnModel.defaultSortable = true;

            var filters = new Ext.grid.GridFilters({
                filters: [
	                { type: 'string', dataIndex: 'Id' },
	                { type: 'string', dataIndex: 'FirstName' },
	                { type: 'string', dataIndex: 'Email' }
	            ]
            });

            var pagingBar = new Ext.PagingToolbar({
                pageSize: 10,
                store: ds,
                displayInfo: true,
                displayMsg: 'Displaying Persons {0} - {1} of {2}',
                emptyMsg: "No Persons to display",
                plugins: filters
            });

            grid = new Ext.grid.GridPanel({
                store: ds,
                cm: columnModel,
                width: 500,
                autoHeight: true,
                title: 'Persons List',
                frame: true,
                loadMask: true,
                stripeRows: true,
                plugins: filters,
                bbar: pagingBar // paging bar on the bottom
            });

            grid.render('personGrid');
            grid.store.load({ params: { start: 0, limit: 10} });
        }
<div id="personGrid"

Controller actions to support the grid’s actions(get data/sort/filter data…etc)

A ExtJS grid can be fed data in various ways. i am going use a controller action to give the grid the data it needs to render it self. the data would be in json format. we would have a controller action like the following, that returns a JsonResult. we can use the Json method in the mvc framework to serialize the data to json with ease. i am doing it by constructing a anonymous type that hosts the data and then serializing it to json. please note that the following example uses the Entity Framework to deal with the back-end data. although this is example uses the EF. you can easily port it to use any other ORM technology(Linq…etc).

source of /Person/ListJson action

public JsonResult ListJson()
        {
            int totalOjectCount;
            var personsList = ExtJSGridFilterHelper.GetResults(Request, new ObjectEntities().Person,out totalOjectCount);
            var list = new ArrayList();
            foreach (var person in personsList) //populate data containers with read data
            {
                list.Add(new
                             {
                                 Id = person.ID,
                                 FirstName = person.FirstName,
                                 person.Email
                             });
            }
            return Json(new { dataitems = list.ToArray(), totalItems = totalOjectCount });
        }

note that the json result that is returned, basically contains a array of items and a another property called totalItems. this total items property tell the grid, the number of(obviously ) total items that it will have to display. this is helpfule for the grid when pagination is enabled. and you should make sure that this property is specified in your json output if you want the pagination to work correctly.

example json output that is produced by the above action

{"dataitems":[{"Id":"5","FirstName":"jhon Lalic","Email":"jhon@gmail.com"},{"Id":"6","FirstName":"Maureen Joesph","Email"
:"maureen@gmail.com"}],"totalItems":2}

this contains two records. and the totalItems property.

ExtJSGridFilterHelper is a helper class that harbours most of the logic related to implementing sorting/paging and filtering of data. i am not going to explain what the source code is doing, step by step. but you can always read the source ;)

parsing of parameters sent by the grid…(for the full source code please look at the source of ExtJSGridFilterHelper)

parameters related to paging

  • start
  • limit

parameters related to the sorting

  • sort => the name of the property to be sorted(as given in the client side(aka field name))
  • dir => direction
if (sort != null) //do sorting
            {
                objectsList = query.OrderBy("it." + sort).ToList();
                if (dir == "DESC") objectsList.Reverse();
            }
            else
            {
                objectsList = query.ToList();
            }

parameters related to the filters

public Filter(int id, HttpRequestBase request)
            {
                Id = id;
                Field = request.Params[string.Format("filter[{0}][field]", id)];
                DataType = request.Params[string.Format("filter[{0}][data][type]", id)];
                DataValue = request.Params[string.Format("filter[{0}][data][value]", id)];
                Datacomparison = request.Params[string.Format("filter[{0}][data][comparison]", id)];
            }

how filters are processed, extract from the getExpression() method

</pre>
<pre>public FilterExpressionResult getExpression()
            {
                string expressionString = null;
                var expressionParams = new List(); //paramerters collection
                switch (DataType)
                {
                    case "string":
                        expressionString = string.Format("(it.{0} like '{1}%')", Field, DataValue);
                        break;
                    case "boolean":
                        expressionString = string.Format("(it.{0} = {1})", Field, (DataValue == "true") ? 1 : 0);
                        break;
                    case "numeric":
                        switch (Datacomparison)
                        {
                            case "gt":
                                Datacomparison = ">";
                                break;
                            case "lt":
                                Datacomparison = "";
                                break;
                            case "lt":
                                Datacomparison = "<";
                                break;
                            default:
                                Datacomparison = "=";
                                break;
                        }

                        expressionParams.Add(new ObjectParameter("Param" + Id, DateTime.Parse(DataValue)));
                        expressionString = string.Format("(it.{0} {2} {1})", Field, "@" + "Param" + Id, Datacomparison);
                        break;
                    case "list":
                        var split = DataValue.Split(new[] { ',' });
                        var r = new string[split.Length];
                        for (var i = 0; i < split.Length; i++)
                        {
                            r[i] = string.Format("(it.{0} = '{1}')", Field, split[i]);
                        }
                        expressionString = string.Format("({0})", string.Join("OR", r));
                        break;
                }
<pre>

source of ExtJSGridFilterHelper

public class ExtJSGridFilterHelper
    {
        public static List GetResults(HttpRequestBase request, ObjectQuery query, out int totalOjectCount)
        {
            var sort = request.Params["sort"];
            var dir = request.Params["dir"];

            //get non null values
            int intStart, intLimit;
            if (!int.TryParse(request.Params["start"], out intStart)) intStart = 0;
            if (!int.TryParse(request.Params["limit"], out intLimit)) intLimit = 10;

            List objectsList;

            totalOjectCount = query.Count();
            var i = 0;
            var exspressions = new List();
            var parameters = new List();
            while (true) //check for filters starting from 0, if one exists move to the next one(0 -> 1...), .
            {
                if (!Filter.checkExistence(i, request.Params)) break; //...if not stop looking further
                var expression = new Filter(i, request).getExpression();
                exspressions.Add(expression.Expression);
                parameters.AddRange(expression.Parameters);
                i++; //keep track of index
            }
            var exspression = string.Format("({0})", string.Join("AND", exspressions.ToArray()));
            //build the final expression
            if (exspression != "()") query = query.Where(exspression, parameters.ToArray()); //filter collection on the expression

            if (sort != null) //do sorting
            {
                objectsList = query.OrderBy("it." + sort).ToList();
                if (dir == "DESC") objectsList.Reverse();
            }
            else
            {
                objectsList = query.ToList();
            }

            if (intStart + intLimit > objectsList.Count)
                intLimit = objectsList.Count - intStart; //make sure the range we select is valid
            objectsList = objectsList.GetRange(intStart, intLimit);
            return objectsList;
        }

        #region Nested type: Filter

        public class Filter
        {
            public string Datacomparison { get; set; }
            public string DataType { get; set; }
            public string DataValue { get; set; }
            public string Field { get; set; }

            public int Id { get; set; }

            public static bool checkExistence(int filterIndex, NameValueCollection @params)
            {
                return (@params[string.Format("filter[{0}][field]", filterIndex)] != null);
            }

            public Filter(int id, HttpRequestBase request)
            {
                Id = id;
                Field = request.Params[string.Format("filter[{0}][field]", id)];
                DataType = request.Params[string.Format("filter[{0}][data][type]", id)];
                DataValue = request.Params[string.Format("filter[{0}][data][value]", id)];
                Datacomparison = request.Params[string.Format("filter[{0}][data][comparison]", id)];
            }

            public FilterExpressionResult getExpression()
            {
                string expressionString = null;
                var expressionParams = new List(); //paramerters collection
                switch (DataType)
                {
                    case "string":
                        expressionString = string.Format("(it.{0} like '{1}%')", Field, DataValue);
                        break;
                    case "boolean":
                        expressionString = string.Format("(it.{0} = {1})", Field, (DataValue == "true") ? 1 : 0);
                        break;
                    case "numeric":
                        switch (Datacomparison)
                        {
                            case "gt":
                                Datacomparison = ">";
                                break;
                            case "lt":
                                Datacomparison = "";
                                break;
                            case "lt":
                                Datacomparison = "<";
                                break;
                            default:
                                Datacomparison = "=";
                                break;
                        }

                        expressionParams.Add(new ObjectParameter("Param" + Id, DateTime.Parse(DataValue)));
                        expressionString = string.Format("(it.{0} {2} {1})", Field, "@" + "Param" + Id, Datacomparison);
                        break;
                    case "list":
                        var split = DataValue.Split(new[] { ',' });
                        var r = new string[split.Length];
                        for (var i = 0; i < split.Length; i++)
                        {
                            r[i] = string.Format("(it.{0} = '{1}')", Field, split[i]);
                        }
                        expressionString = string.Format("({0})", string.Join("OR", r));
                        break;
                }
                return expressionString != null
                           ? new FilterExpressionResult { Expression = expressionString, Parameters = expressionParams }
                           : null;
            }

            #region Nested type: FilterExpressionResult

            public class FilterExpressionResult
            {
                public string Expression { get; set; }
                public List Parameters { get; set; }
            }

            #endregion
        }

        #endregion
    }

ah! that’s it for now. i might post some helpers that can be used in your views when you want to render the grid. the advantage of using this would be, so you won’t need to repeat all that javascript code in each and every view that you want to display a grid in. hope this will be helpful to someone. cheers!

About these ads

16 Comments »

  1. Thanks for leaving a message at my blog.

    Your articles is much cleaner and self explanatory than mine.

    Comment by Osman — February 5, 2009 @ 2:24 pm

  2. I feel like a dork for not being able to find it myself (been googling for the better part of an hour), but what namespace does the “List” you’re using live under? The only List I can find requires an actual type (List), which pretty much defeats the purpose here of doing generic filtering…

    ObjectQuery is from the Entity Data Model (System.Data.Objects.ObjectQuery), yeah?

    At any rate, neat sample! Thanks for the post. :)

    Comment by superlime — February 26, 2009 @ 3:58 am

  3. Oops. My angle brackets got filtered out from my last post.. I meant to say that the only List I can find is the List(lessthan)T(greaterthan) at System.Collections.Generic.List, which requires an object type T to initialize.. Maybe your blog post actually had the same code removal problem as my comment? Are you using List(lessthan)object(greaterthan), perhaps?

    (examples found in line 4 and 14 of ExtJSGridFilterHelper)

    At any rate, thanks in advance. :)

    Comment by superlime — February 26, 2009 @ 4:06 am

    • @superlime
      i feel like a idiot! you are right. its the “List(lessthan)object(greaterthan)” i will try and correct this issue. again I’m really sorry for wasting your time. i am noob when it comes to posting source code on my blog.

      please refer to the source code over here: http://pastebin.com/f72e57c25 it should be accurate.
      cheers!

      Comment by yasi8h — February 26, 2009 @ 4:24 am

  4. where can i download the code of this tutorial ?
    thx

    Comment by ahmed — April 3, 2009 @ 7:10 am

    • @ahmed i don’t have any project with all the related source code. i’m sorry about that. but you can get the source code from the given code snippets. how you use them in a project will depend on your needs.

      Comment by yasi8h — April 3, 2009 @ 9:34 am

  5. We have integrated ASP.NET MVC and ExtJS with LinqToSQL/Entity Framework, you can download the whole VS 2008 projects at the following link:

    http://latticesoft.com/forums/files/

    Comment by li — April 6, 2009 @ 8:57 pm

  6. Hi All !

    An exciting example, but:Very, very sad, it’s not working for me. I am new to the whole stuff, but not for asp.net/IIS. I managed to change the version dependency on the [shortly] released mvc 1.0 and could compile the project. In the VS test-environment and in a live webserver, I get always “404″, “not found”, if I use the “default.aspx” page. Any help is really very welcomed!

    br–mabra

    Comment by mabra — May 1, 2009 @ 4:03 pm

  7. [...] In my case i wanted my controllers to have all the basic actions like List, Details, Create, Edit and my custom actions like Grid, GridData(take a look at A Grid with Ajax/Pagination/Sorting/Filtering on ASP.net MVC with ExtJS and Enitiy Framework). [...]

    Pingback by Using Custom T4 templates To Generate Better Controllers in ASP MVC « theKindOfMe — July 1, 2009 @ 7:52 am

  8. seems like ur ripping off the web,

    http://www.redgreenrefactor.com/post/Ext-Js-and-ASPNET-MVC.aspx

    ..!. u

    Comment by fuckerinhell — July 10, 2009 @ 9:03 pm

    • hi “fuckerinhell”! :)
      i think if you take some time and compare my article with the given article you would see that there are some significant differences between the two.

      if you are not sure you can always use some s/w(google plagiarism detection software…etc) to find the similarities between the articles/source code….

      have a nice day.

      Comment by yasi8h — July 11, 2009 @ 6:46 am

  9. hi, were you able to find any extjs grid generator, because it tires to repeat some declarations, etc..

    Comment by asp.net developer — November 25, 2009 @ 1:22 pm

    • nope. but do some googling and you might dig something up. if you don’t you can try coding some template/macro yourself. i tried something over here you might want to check that out.

      Comment by yasi8h — November 25, 2009 @ 3:26 pm

  10. Dude, this layout with 400px width is fucked up.. Reconsider this design, my brain hurts…

    Comment by Your Nightmare — December 22, 2009 @ 4:36 pm

    • true. sorry about that :P will change this to something else when i get sometime.

      Comment by yasi8h — December 22, 2009 @ 5:33 pm

  11. Can i download the example ?

    Comment by joy — May 29, 2012 @ 4:23 pm


RSS feed for comments on this post. TrackBack URI

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

The Silver is the New Black Theme Blog at WordPress.com.

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: