Thursday, May 7, 2009

jQuery Grid plug-in (jqGrid) with ASP.NET MVC

jqGrid is a great plug-in, making good use of jQuery. jqGrid can be downloaded here and the documentations can be found here. We will shortly see how to use this plug-in in ASP.NET MVC application. Versions used –

  • jQuery – 1.3.2
  • jqGrid – jqGrid 3.5 ALFA 3.

The source code of the demonstration can be downloaded here.

To get started, we create a new ASP.NET MVC Web Application.

When you create an ASP.NET MVC application the jQuery scripts get included automatically.

Since I am not using a database for this example I have created a code file called “Movies.cs” in the “Models” folder which will return a list of Movies. If you are using LINQ to SQL with you Database table the grid paging becomes easier, which we will discover shortly. So here are the two classes that I put in “Movies.cs”:
using System;
using System.Collections.Generic;
using System.Linq;


namespace jQueryMVC.Models {

    public class Movie {

        public int? Id { get; set; }
        public string Name { get; set; }
        public string Director { get; set; }
        public DateTime ReleaseDate { get; set; }
        public string IMDBUserRating { get; set; }
        public string Plot { get; set; }
        public string ImageURL { get; set; }
    }

    public class Movies {
        public List<Movie> GetMovies() {

            List<Movie> movies = new List<Movie>();

            movies.Add(new Movie() { Id = 1, Name = "Iron Man", Director = "Jon Favreau", ReleaseDate = new DateTime(2008, 5, 2), IMDBUserRating = "8.0/10", Plot = "When wealthy industrialist Tony Stark is forced to build an armored suit after a life-threatening incident, he ultimately decides to use its technology to fight against evil.", ImageURL = @"Images/IronMan.jpg" });
            movies.Add(new Movie() { Id = 2, Name = "Slumdog Millionaire", Director = "Danny Boyle, Loveleen Tandan ", ReleaseDate = new DateTime(2008, 1, 23), IMDBUserRating = "8.5/10", Plot = "A Mumbai teen who grew up in the slums, becomes a contestant on the Indian version of 'Who Wants To Be A Millionaire?' He is arrested under suspicion of cheating, and while being interrogated, events from his life history are shown which explain why he knows the answers.", ImageURL = @"Images/SlumdogMillionaire.jpg" });
            movies.Add(new Movie() { Id = 3, Name = "The Dark Knight", Director = "Christopher Nolan", ReleaseDate = new DateTime(2008, 7, 18), IMDBUserRating = "9.0/10", Plot = "Batman, Gordon and Harvey Dent are forced to deal with the chaos unleashed by an anarchist mastermind known only as the Joker, as it drives each of them to their limits", ImageURL = @"Images/TheDarkKnight.jpg" });
            movies.Add(new Movie() { Id = 4, Name = "The Wrestler", Director = "Darren Aronofsky", ReleaseDate = new DateTime(2008, 1, 30), IMDBUserRating = "8.4/10", Plot = "A faded professional wrestler must retire, but finds his quest for a new life outside the ring a dispiriting struggle.", ImageURL = @"Images/TheWrestler.jpg" });
            movies.Add(new Movie() { Id = 5, Name = "The Curious Case of Benjamin Button", Director = "David Fincher", ReleaseDate = new DateTime(2008, 12, 25), IMDBUserRating = "8.2/10", Plot = "Tells the story of Benjamin Button, a man who starts aging backwards with bizarre consequences.", ImageURL = @"Images/TheCuriousCase.jpg" });
            movies.Add(new Movie() { Id = 6, Name = "Frost/Nixon", Director = "Ron Howard", ReleaseDate = new DateTime(2008, 1, 23), IMDBUserRating = "8.0/10", Plot = "A dramatic retelling of the post-Watergate television interviews between British talk-show host David Frost and former president Richard Nixon.", ImageURL = @"Images/FrostNixon.jpg" });
            movies.Add(new Movie() { Id = 7, Name = "WALL-E", Director = "And9ew Stanton", ReleaseDate = new DateTime(2008, 6, 27), IMDBUserRating = "8.6/10", Plot = "In the distant future, a small waste collecting robot inadvertently embarks on a space journey that will ultimately decide the fate of mankind.", ImageURL = @"Images/WallE.jpg" });
            movies.Add(new Movie() { Id = 8, Name = "Man on Wire", Director = "James Marsh", ReleaseDate = new DateTime(2008, 8, 1), IMDBUserRating = "8.1/10", Plot = "A look at tightrope walker Philippe Petit's daring, but illegal, high-wire routine performed between New York City's World Trade Center's twin towers in 1974, what some consider, 'the artistic crime of the century.'", ImageURL = @"Images/ManOnWire.jpg" });
            movies.Add(new Movie() { Id = 9, Name = "Milk", Director = "Gus Van Sant", ReleaseDate = new DateTime(2008, 1, 8), IMDBUserRating = "8.0/10", Plot = "The story of Harvey Milk, and his struggles as an American gay activist who fought for gay rights and became California's first openly gay elected official.", ImageURL = @"Images/Milk.jpg" });
            movies.Add(new Movie() { Id = 10, Name = "Tropic Thunder", Director = "Ben Stiller", ReleaseDate = new DateTime(2008, 8, 13), IMDBUserRating = "7.3/10", Plot = "Through a series of freak occurrences, a group of actors shooting a big-budget war movie are forced to become the soldiers they are portraying.", ImageURL = @"Images/TropicThunder.jpg" });

            return movies;
        }
    }
}
I have created an “Images” folder and put a small image related to every movie in there. The way the data and the images are handled is not the best way to use it, but the purpose is to get some data and the focus is on the use of jqGrid. With that disclaimer download the jqGrid JavaScript source files (including the scripts in the “plugins” folder) with the themes and add in to the “Scripts” and “Content” folders respectively.

Please note that we have not included the jquery.js file that comes along with the plug-in as it is available with ASP.NET MVC in Visual Studio. Since we named the JavaScript source for jqGrid as “jqGridJs” to keep the plug-in source separate, we need to change the path in the file “jquery.jqGrid.js”. All we need is to change the value of the variable pathtojsfiles as shown below:

Include the jQuery script “jquery-1.3.2.js” in the master page – “Site.Master”
<script type="text/javascript" src="<%= this.ResolveClientUrl("~/Scripts/jquery-1.3.2.js") %>"></script>

We will be creating the grid in the home/Index view, so copy the following HTML tags in “Views/ Home/ Index.aspx”:
<table id="list" class="scroll"></table>
    <div id="pager" class="scroll" style="text-align:center;"></div> 
Also import the CSS file and script files in the view:
<link href="/Content/jqGridCss/ui.jqgrid.css" rel="stylesheet" type="text/css" />
    <link href="/Content/jqGridCss/redmond/jquery-ui-1.7.1.custom.css" rel="stylesheet" type="text/css" />

    <script src="<%= this.ResolveClientUrl("~/Scripts/jquery.jqGrid.js") %>" type="text/javascript"></script>
    <script src="<%= this.ResolveClientUrl("~/Scripts/jqGridJs/jqModal.js") %>" type="text/javascript"></script>
    <script src="<%= this.ResolveClientUrl("~/Scripts/jqGridJs/jqDnR.js") %>" type="text/javascript"></script>
Please note that the order in which the JavaScript files are included is important. Now copy the following code in the same view, after the above declarations. This is the function which is responsible of creating the grid on the client’s side.
<script type="text/javascript">
        jQuery(document).ready(function() {
            jQuery("#list").jqGrid({
            url: '/Home/GetMovieData/',
                datatype: 'json',
                mtype: 'GET',
                colNames: ['id', 'Movie Name', 'Directed By', 'Release Date', 'IMDB Rating', 'Plot', 'ImageURL'],
                colModel: [
                  { name: 'id', index: 'Id', width: 55, sortable: false, hidden: true },
                  { name: 'Movie Name', index: 'Name', width: 250 },
                  { name: 'Directed By', index: 'Director', width: 250, align: 'right' },
                  { name: 'Release Date', index: 'ReleaseDate', width: 100, align: 'right' },
                  { name: 'IMDB Rating', index: 'IMDBUserRating', width: 100, align: 'right' },
                  { name: 'Plot', index: 'Plot', width: 55, hidden: true },
                  { name: 'ImageURL', index: 'ImageURL', width: 55, hidden: true}],
                pager: jQuery('#pager'),
                rowNum: 5,
                rowList: [5, 10, 20],
                sortname: 'id',
                sortorder: "desc",
                height: '100%',
                width: '100%',
                viewrecords: true,
                imgpath: '/Content/jqGridCss/redmond/images',
                caption: 'Movies from 2008'
            });
        });
    </script>
Take a look at the script, the colNames show the name of the column which will be shown to the user. The colModel shows the details of the rendering of the columns. The index is the value which will be sent to the server as the parameter to sort with.
Also the “datatype” specifies that the script is expecting json data from the url “/Home/GetMovieData/”. So now, we need to create the action GetMovieData in the home controller.

This controller needs to return json data in a specific format. To read the details, please refer the documentation here. Thanks to the Json helper method in the Controller class, you need not worry about it.

Create the following action in the Home Controller:
public ActionResult GetMovieData() {

            #region JQGrid Params
            string sortColumn = (Request.Params["sidx"]).ToString();
            string sortOrder = (Request.Params["sord"]).ToString();
            int pageIndex = Convert.ToInt32(Request.Params["page"]);    //Remember this is NOT 0 based
            int rowCount = Convert.ToInt32(Request.Params["rows"]);
            #endregion

            Movies movies = new Movies();
            var movieList = movies.GetMovies();

            int totalRecords = movieList.Count();
            int totalPages = (int)Math.Ceiling((float)totalRecords / (float)rowCount);

            var jsonData = new {

                total = totalPages,
                page = pageIndex,
                records = totalRecords,
                rows = (
                    from m in movieList
                    select new {
                        i = m.Id,
                        cell = new string[] {
                        m.Id.Value.ToString(), m.Name, m.Director, m.ReleaseDate.ToShortDateString(), m.IMDBUserRating, m.Plot, m.ImageURL
                        }
                    }
                ).ToArray()
            };
            return Json(jsonData);
        }
One thing to notice here is that the name of the query string parameters are defined by the JQGrid. Now run the application and you should be able to see something like this: Our work here is almost done, well… almost, as the paging and sorting is not working yet.

Now depending on what you are using for data access, you will have to write a method to take the parameters received from the grid and sort it.

If you are using LINQ to SQL, all you need to do is add the following lines in your GetMovieData action just before selecting jsonData:
var finalList = movieList
                .OrderBy(sidx + " " + sord)
                .Skip(pageIndex * rows)
                .Take(rows);
But since the OrderBy method taking string variables is not available here, we will create a method to take care of that. Add the following Sort method in the Movies Class:
public List<Movie> Sort(List<Movie> list, string sortColumn, string sortOrder) {

            int order;

            if (sortOrder == "desc")
                order = -1;
            else
                order = 1;


            switch (sortColumn) {
                case "Name":
                    list.Sort(
                         delegate(Movie m1, Movie m2)
                         { return m1.Name.CompareTo(m2.Name) * order; } );
                    break;

                case "Director":
                    list.Sort(
                         delegate(Movie m1, Movie m2)
                         { return m1.Director.CompareTo(m2.Director) * order; });
                    break;

                case "ReleaseDate":
                    list.Sort(
                         delegate(Movie m1, Movie m2)
                         { return m1.ReleaseDate.CompareTo(m2.ReleaseDate) * order; });
                    break;

                case "IMDBUserRating":
                    list.Sort(
                         delegate(Movie m1, Movie m2)
                         { return m1.IMDBUserRating.CompareTo(m2.IMDBUserRating) * order; });
                    break;
            }

            return list;
        }
If you are using multiple grids in your application with the same typed list, it will be a good idea to create it as an extension function. No we will just make some changes in the action to make a call to this method. The final version of the action will look like this:
public ActionResult GetMovieData() {

            #region JQGrid Params
            string sortColumn = (Request.Params["sidx"]).ToString();
            string sortOrder = (Request.Params["sord"]).ToString();
            int pageIndex = Convert.ToInt32(Request.Params["page"]);
            int rowCount = Convert.ToInt32(Request.Params["rows"]);
            #endregion

            Movies movies = new Movies();
            var movieList = movies.GetMovies();

            int totalRecords = movieList.Count();
            int totalPages = (int)Math.Ceiling((float)totalRecords / (float)rowCount);

            var finalList = movies.Sort(movieList, sortColumn, sortOrder)
                                    .Skip((pageIndex - 1) * rowCount)
                                    .Take(rowCount);

            var jsonData = new {

                total = totalPages,
                page = pageIndex,
                records = totalRecords,
                rows = (
                    from m in finalList
                    select new {
                        i = m.Id,
                        cell = new string[] {
                        m.Id.Value.ToString(), m.Name, m.Director, m.ReleaseDate.ToShortDateString(), m.IMDBUserRating, m.Plot, m.ImageURL
                        }
                    }
                ).ToArray()
            };
            return Json(jsonData);
        }
Now our jqGrid is implemented. There are many more functionalites and tweaks in the plug-in like java script based CRUD operations and search operations that can be easily integrated into this code with the help of the jqGrid documentation.

At this point you might be wondering what was the point putting Images and Plot in the Movie class and why did we put it in the hidden columns in the grid. You can completely ignore that for now as that is something I’ll be using in my next blog when we will implement jQuery UI dialog box on grid’s action.

Good Luck!

12 comments:

  1. Hey man, very nice post !

    Do you have msn or something?

    ReplyDelete
  2. Thanks Patrick! If you need any help on any of this do leave your question here. I'll try to respond as soon as I can.

    ReplyDelete
  3. I followed this except with using the release version. I can see that by looking at the generated source, the grid is being generated, however it's blank on the page. I checked and all script and css files are loading (no 404). This was verified with the web dev toolbar in FF.

    ReplyDelete
  4. I removed the height and width properties from the on-page script. But it shows all rows and not 5.

    ReplyDelete
  5. hello and congratulations for your example, but to me it does not work properly and more precisely, if I place the jQuery code that creates the table and draws the url for the return json within the View / Home / action, then all I appears correctly, but if I move the folder and / or View I do not feel like
    You have an explanation for that, I might indicate a possible.
    Thank you for your help

    ReplyDelete
  6. This works great. I tried to set ImageURL hiiden to false but the grid diplayed the path itself not the actual image. How can I display an image?

    ReplyDelete
  7. Hi,

    I tried to use this in my applicaiton. But I am getting the follwoing error at line $("#list").jqGrid({.

    'Microsoft JScript runtime error: object doesn't support this property or method.'

    I have included all the references for jqGrid.

    Thanks,
    Sowjanya

    ReplyDelete
  8. @David - All that I can think of without looking at the code is to make sure you have "hidden: true" set at the column level.
    Try referring to the documentation related to the version you are using.

    ReplyDelete
  9. @Anonymous - I think you are referring to the relative URL issue.
    Try using Url.Action helper method to assign the URL for the grid.
    Something like - url: '<% =Url.Action("ActionName","controllerName") %>',

    ReplyDelete
  10. @Anonymous - To show the image in the grid, passing the path alone will not work. Passing a populated image tag as the string value might work.
    Try adding a column in the grid for the image. From the code behind, for the value of that column, send an image tag with values set.
    The image size will have to be managed.

    ReplyDelete
  11. @Sowjanya – The error you mentioned suggests that either the object accessed is wrong or the right library is missing.
    Check the jQuery and jqGrid versions.
    You might want to debug the script as see if the object exists and it has the method.

    Here’s a post on debugging java script that might help you.
    http://aspnetlive.blogspot.com/2010/02/debugging-java-script-in-visual-studio.html

    ReplyDelete
  12. Hi Mayank ,
    Please provide me some link or tutorial for JQGrid using html mvc helper.
    Thanks!!

    ReplyDelete