Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

MVC Basic Site: Step 4 – jqGrid Integration in MVC 4.0 using AJAX, JSON, jQuery, LINQ, and Serialization

0.00/5 (No votes)
25 Oct 2013 4  
This article presents in detail jqGrid integration in MVC 4.0 using AJAX, JSON, jQuery, LINQ, and Serialization.

MVC Basic Site

Table of Contents

Introduction

MVC Basic Site is intended to be a series of tutorial articles about the creation of a basic and extendable web site that uses ASP.NET MVC.

The first article from this series, named MVC Basic Site: Step1-Multilingual Site Skeleton, was focused mainly on the creation of a multilingual website skeleton by using ASP.NET MVC. Also the user authentication and registration created from scratch were presented there.

The second article, MVC Basic Site: Step2-Exceptions Management, presents in details the exceptions management rules and their implementation for an ASP.NET MVC website, and provides some utility base classes and source code for Logging and Exceptions Management that can be reused (with very small changes) not only in other ASP.NET sites but also generally in any .NET project.

The third article, MVC Basic Site: Step3-Dynamic Layouts and Site Admin with: AJAX, jqGrid, Controller Extensions, HTML Helpers and more, provides the implementation of dynamic layouts and website administration by using AJAX, jqGrid, Custom Action Results, Controller Extension, HTML Helpers, and other utility C# source code and JavaScript that can be extended and reused in other projects.

This article is the fourth in the MVC Basic Site series, and presents in detail jqGrid integration in MVC 4.0 by using AJAX, JSON, jQuery, LINQ, and Serialization.

MVC Basic Site is developed using an incremental and iterative methodology, this means that each new step adds more functionalities to the solution from the previous step, so the source code provided for download in this article contains all the functionalities implemented until now (from all articles).

Note that all the provided source code is very well commented and clean and there should be no problem in reading and understanding it.

The first part of the current article describes the building blocks used, and the next part continues with the presentation of the jqGrid integration steps into the MVC Basic Site solution by using AJAX, JSON, JavaScript, and all the building blocks presented before.

Software Environment

  • .NET 4.0 Framework
  • Visual Studio 2010 (or Express edition)
  • ASP.NET MVC 4.0
  • SQL Server 2008 R2 (or Express Edition version 10.50.2500.0)

jqGrid and AJAX

jqGrid is an open source AJAX-enabled JavaScript control that provides solutions for representing and manipulating tabular data on the web, and that loads the data dynamically through AJAX callbacks.

The documentation, open source package, demo, and examples about jqGrid can be found on the next site: http://www.trirand.com

The main components of the jqGrid package that must be integrated in the web application are:

  • jquery.jqGrid.min.js – contains the jqGrid JavaScript library in the minimized form;
  • jquery.jqGrid.src.js – contains the jqGrid JavaScript library in the source form;
  • ui.jqgrid.css – the CSS used by the libraries above;
  • a set of language specific files named grid.locale-XX.js, where XX is a two-letter code for the language.

In addition to these main components it is indicated to customize, download, and use one or more jQuery UI themes by using the tool http://jqueryui.com/themeroller/. In the current solution I am using a customized version of the Redmond theme.

AJAX is a well-known acronym for Asynchronous JavaScript and XML. AJAX is not a single technology, but is a group of interrelated web development techniques used to create asynchronous web applications. With AJAX, web applications can send data to, and retrieve data from, the web server asynchronously without interfering with the display and behavior of the existing page and in this way full page reloads are avoided.

In the diagram below, I present, by using UML diagrams, the Classic Web System Model and the AJAX Web System Model.

Like you can see from the diagrams above in the case of the classic model, for each user request from the User Interface component (from the Browser Client) is send an HTTP request directly to the Web Application (that runs on the Web Server), and the Web Application does some processing and sends back the response as an HTML page (HTTP + CSS) to the browser, and finally the browser renders the entire response.

In the case of AJAX based web applications, when the user requests, the User Interface component sends a JavaScript call to the AJAX Engine component of the browser. This engine is responsible for both rendering the interface the user sees and communicating with the server on the user’s behalf. The AJAX engine allows the user’s interaction with the application to happen asynchronously—independent of the communication with the server. Note that the AJAX Engine sends to the Web Application an HTTP request and receives back as response only the needed data (and not the entire HTML page) in XML format.

GridSettings

This is the main class created by me to store the jqGrid settings and for doing the pagination and sorting.

Like you can see from the class diagram above, there are four properties used to store the current grid settings.

  • PageIndex: stores the current page index (1 is the first page, 2 second one, etc.);
  • PageSize: stores the page size (the maximum number of rows loaded and shown in a grid page);
  • SortColumn: the current sort column;
  • SortOrder: the current sort order (ASC or DESC).

This class implements the Serializable interface, so it overrides ToString() to cache the current grid settings into a string, and provides a second constructor used to initialize a new instance of the GridSettings class form the serialized data.

In the source code below you can see the main method provided by this class used to load the data from the given data source for the current grid settings. Note that the data source parameter should be a query created with LINQ.

public IQueryable<T> LoadGridData<T>(IQueryable<T> dataSource, out int count)
{
    var query = dataSource;
    //
    // Sorting and Paging by using the current grid settings.
    //
    query = query.OrderBy<T>(this.SortColumn, this.SortOrder);
    count = query.Count();
    //
    if (this.PageIndex < 1)
        this.PageIndex = 1;
    //
    var data = query.Skip((this.PageIndex - 1) * this.PageSize).Take(this.PageSize);
    return data;
}

Like you can see from the code above this method uses generics, so it is a general method and could be used to load any type of data and it returns the results count as an out parameter. First the data is sorted using the parameters from the grid settings, then it computes the count, and finally loads only the requested page of results from the database.

Note that the GridSettings class is associated with the GridModelBinder class (described in the next chapter) by using the ModelBinder attribute.

GridModelBinder

ASP.NET MVC model binding simplifies controller actions by introducing an abstraction layer that automatically populates the controller action parameters, taking care of the property mapping and type conversion code typically involved in working with ASP.NET request data.

GridModelBinder is my custom model binder class used by the GridSettings class (described in the chapter above) to bind its properties with the jqGrid parameters.

Like you can see from the class diagram above, this class has only one method named BindModel and uses it to implement the IModelBinder interface.

public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
    try
    {
        var request = controllerContext.HttpContext.Request;
        return new GridSettings
        {
            PageIndex = int.Parse(request["page"] ?? "1"),
            PageSize = int.Parse(request["rows"] ?? "20"),
            SortColumn = request["sidx"] ?? "",
            SortOrder = request["sord"] ?? "asc",
        };
    }
    catch
    {
        //
        // For unexpected errors use the default settings!
        //
        return null;
    }
}

Like you can see from the source code above, the parameters from the HTTP request that comes from jqGrid via JavaScript calls are used to create and initialize a new GridSettings object that will be used as the model in the controller.

LinqExtensions

This class extends LINQ to simplify the sorting process by using the next two parameters from my GridSeetings class described above: SortColumn and SortOrder.

Like you can see from the class diagram above the class has only one static method named OrderBy and it uses generics.

public static IQueryable<T> OrderBy<T>(
       this IQueryable<T> query, string sortColumn, string direction)
{
    string methodName = string.Format("OrderBy{0}", 
      direction.ToLower() == "asc" ? "" : "descending");
    ParameterExpression parameter = Expression.Parameter(query.ElementType, "p");
    MemberExpression memberAccess = null;
    //
    foreach (var property in sortColumn.Split('.'))
    {
        memberAccess = MemberExpression.Property(memberAccess ?? (parameter as Expression), property);
    }
    //
    LambdaExpression orderByLambda = Expression.Lambda(memberAccess, parameter);
    MethodCallExpression result = Expression.Call(
                typeof(Queryable),
                methodName,
                new[] { query.ElementType, memberAccess.Type },
                query.Expression,
                Expression.Quote(orderByLambda));
    //
    // For the sortColumn=='User.Username' and  direction=="desc" ==> 
    //           "OrderByDescending(p => p.User.Username)" expression 
    // will be appended to the input query!
    //
    return query.Provider.CreateQuery<T>(result);
}

The source code above creates a lambda expression for the given sort column and direction (sort order) then appends the result to the given input query. Note that the sort column could be simple like “ID” or more complicated like “User.Username” (that uses navigation properties between data entities).

Data Entity Class and LINQ

The data entity class used in this example is named VisitorLog (located in Logic project) and it contains a set of methods used to access the data from the VissitorLogs database table.

The main method of this class used to read the visitors logs data that will be finally shown to the user in the jqGrid is the next one.

public static IQueryable<VisitorLog> SearchLogByDates(MvcBasicSiteEntities dataContext, DateTime searchStartDate, DateTime searchEndDate)
{
    if (searchStartDate.Date == DateTime.MinValue && searchEndDate.Date == DateTime.MinValue)
    {
        return dataContext.VisitorLogs.AsQueryable();
    }
    else if (searchStartDate.Date == DateTime.MinValue)
    {
        var searchResults = dataContext.VisitorLogs.Where(c => 
            EntityFunctions.TruncateTime(c.StartDate) <= EntityFunctions.TruncateTime(searchEndDate));
        //
        return searchResults.AsQueryable();
    }
    else if (searchEndDate.Date == DateTime.MinValue)
    {
        var searchResults = dataContext.VisitorLogs.Where(c => 
            EntityFunctions.TruncateTime(c.StartDate) >= EntityFunctions.TruncateTime(searchStartDate));
        //
        return searchResults.AsQueryable();
    }
    else
    {
        var searchResults = dataContext.VisitorLogs.Where(c =>
            EntityFunctions.TruncateTime(c.StartDate) >= EntityFunctions.TruncateTime(searchStartDate) &&
            EntityFunctions.TruncateTime(c.StartDate) <= EntityFunctions.TruncateTime(searchEndDate));
        //
        return searchResults.AsQueryable();
    }
}

Like you can see from the source code above this method builds LINQ expressions for the next 4 possible cases based on the given parameters:

  • Find all entries that have dates between the given dates interval, when both parameters have valid values; valid value means different than DateTime.MinValue;
  • Find all entries from the database, when no parameter has valid values;
  • Find all entries from the given start date until today, when only start date has valid value;
  • Find all entries until the given end date, when only end date has valid value.

jqGrid Integration in MVC

All building blocks presented above are used to integrate jqGrid into my MVC solution. To see the results of this integration, you have to open the “Visitors” page from the administrator area (you must login with user Administrator and password tm77dac).

Like you can see from the screenshot above, there is a grid that has five columns of different types (strings, date time, and Boolean), and all columns have sorting enabled. The last column named “Actions” uses my ImageButton custom HTML helper (described in my previous article); when the user presses on the “Delete” image button (from the action column) the associated visitor log entry will be deleted from the database.

In the grid footer there is a paging controller that shows and let the user to specify the total number of results manipulated by the grid, the current page, the navigation buttons, and the number of results that will be shown per page. By using all these navigation controls the user can navigate between the results as he/she wants.

On the grid footer there are also two action buttons: “Reload Grid” button (image button) and “Delete All” button -used to delete all visitors' log histories for the current filter.

Note that this page, as the entire MVC Basic Site solution, is implemented by using internationalization and localization for three languages: English, Romanian (Română), and German (Deutch), and can be extended for other languages; so also the messages and controls (labels, buttons, tool tips, etc.) from this page are multilingual.

The integration of the jqGrid into this MVC solution was done using the four steps presented below. You could follow similar steps to integrate jqGrid in your MVC applications.

Step 1 (Partial View)

After creating the building blocks presented above, the first step followed by me to integrate jqGrid was to create a partial view named _VisitorLogGrid.cshtml that will be the container for the grid and for its pager.

<table id="_visitorLogGrid" cellpadding="0" cellspacing="0">
</table>
<div id="_visitorLogPager" style="text-align: center;">
</div>

The two IDs defined in the HTML code above are very important, because they will be used in the next steps.

Step 2 (JavaScript)

The second step was to create a JavaScript file named VisitorLogGrid.js and place it into the Scripts folder.

function showGrid() {
    $('#_visitorLogGrid').jqGrid({
        caption: paramFromView.Caption,
        colNames: ['ID', paramFromView.VisitorName, paramFromView.StartDate, 
          paramFromView.EndDate, paramFromView.WasTimeOut, paramFromView.Actions],
        colModel: [
                    { name: 'ID', width: 1, hidden: true, key: true },
                    { name: 'VisitorName', index: 'User.Username', width: 300 },
                    { name: 'StartDate', index: 'StartDate', width: 150 },
                    { name: 'EndDate', index: 'EndDate', width: 150 },
                    { name: 'WasTimeOut', index: 'WasTimeOut', width: 120, 
                            formatter: "checkbox", align: "center" },
                    { name: 'Action', index: 'ID', width: 70, align: "center" }
                  ],
        hidegrid: false,
        pager: jQuery('#_visitorLogPager'),
        sortname: 'ID',
        rowNum: paramFromView.PageSize,
        rowList: [10, 20, 50, 100],
        sortorder: "desc",
        width: paramFromView.Width,
        height: paramFromView.Height,
        datatype: 'json',
        caption: paramFromView.Caption,
        viewrecords: true,
        mtype: 'GET',
        jsonReader: {
            root: "rows",
            page: "page",
            total: "total",
            records: "records",
            repeatitems: false,
            userdata: "userdata"
        },
        url: paramFromView.Url
    }).navGrid('#_visitorLogPager', { view: false, del: false, add: false, edit: false, search: false },
       { width: 400 }, // default settings for edit
       {}, // default settings for add
       {}, // delete instead that del:false we need this
       {}, // search options
       {} /* view parameters*/
     ).navButtonAdd('#_visitorLogPager', {
         caption: paramFromView.DeleteAllCaption, buttonimg: "", onClickButton: function () {
             if (confirm(paramFromView.DeleteAllConfirmationMessage)) {
                 document.location = paramFromView.ClearGridUrl;
             }
             else {
                 $('#_visitorLogGrid').resetSelection();
             }
         }, position: "last"
     });
};

$(document).ready(function () {
    showGrid();
});

Like you can see form the JavaScript code above, there is defined a document.ready event used to show the grid when the document is ready, and a main function named showGrid that defines the jqGrid columns, properties, pager, and navigation buttons.

Note that all labels used in the grid caption, columns definition, buttons, and messages use values that come from Resource files via the properties of the object paramFromView (see details below).

There are some important details that must be underlined:

  • _visitorLogGrid is the ID of the table defined in the partial view above and is used to create the grid;
  • _visitorLogPager is the ID of the div defined in the partial view above and is used to create the pager control and the navigation buttons (“Refresh” and “Delete All”) associated with the grid;
  • colNames property defines the names for all grid columns (including the hided columns);
  • colModel property defines the columns behavior and properties including the sorting indexes associated with data entity properties;
  • rowNum property sets up the current page size of the grid (the number of rows per page), by using the PageSize property of the paramFromView object;
  • width property sets up the width of the grid controls in pixels, by using the Width property of the paramFromView object;
  • hight property sets up the height of the grid controls in pixels, by using the Height property of the paramFromView object;
  • url property sets up the URL used by the jqGrid to get data from the server by using AJAX calls, by using the Url property of the paramFromView object;
  • Delete All navigation button uses the ClearGridUrl property of the paramFromView object to setup the URL used when the user confirms the “Delete All” action.
  • jsonReader property defines the names of the main properties used in the JSON data that will be read from the server (see details in Step 4 below).

Step 3 (Razor View)

The third step was to create the main page that will use the JavaScript file from the second step. It is about the view Index.cshtml from the next folder: Views\VisitorsLog.

@using MvcBasicSite.Models.Grid;
@{
    ViewBag.Title = Resources.Resource.VisitorLogIndexTitle;
    Layout = "~/Views/Shared/_AdminLayout.cshtml";
    //
    int pageSize = 20;
    if (Session["VisitorLogGridSettings"] != null)
    {
        //
        // Get from cache the last page zise selected by the user. 
        //
        GridSettings grid = new GridSettings((string)Session["VisitorLogGridSettings"]);
        pageSize = grid.PageSize;
    }
    //
    // Restore the last search params from cache.
    //
    string startDate = (Session["StartDate"] == null
        ? string.Empty
        : ((DateTime)Session["StartDate"]).ToShortDateString());
    string endDate = (Session["EndDate"] == null
        ? string.Empty
        : ((DateTime)Session["EndDate"]).ToShortDateString());
}
<table>
    <tr>
        <td style="text-align: right; margin-top: 0px;">
            @using (Ajax.BeginForm("Search", "VisitorLog", 
                new AjaxOptions
                {
                    HttpMethod = "GET",
                    InsertionMode = InsertionMode.Replace,
                    UpdateTargetId = "jqGrid",
                    OnSuccess = "showGrid()"
                }))
            {
                <table>
                    <tr>
                        <td>
                            <label>@Resources.Resource.VisitorLogIndexFrom</label>
                            <input type="text" id="from" name="from" 
                              data-datepicker="true" value="@startDate" />
                        </td>
                        <td>
                            <label>@Resources.Resource.VisitorLogIndexTo</label>
                            <input type="text" id="to" name="to" 
                              data-datepicker="true" value="@endDate" />
                        </td>
                        <td style="text-align: right; margin-top: 0px;">
                            <input type="submit" name="_search" 
                              value="@Resources.Resource.VisitorLogApplyFilter" 
                              class="searchButton" />
                        </td>
                    </tr>
                </table>
            }
        </td>
    </tr>
</table>
<div id="jqGrid">
    @Html.Partial("_VisitorLogGrid")
</div>
<script type="text/javascript">
    var paramFromView = {
        DeleteAllCaption: '@Resources.Resource.VisitorLogDeleteAllCaption',
        ClearGridUrl: '@Url.Content("~/VisitorLog/ClearGridData")',
        DeleteAllConfirmationMessage: '@Resources.Resource.VisitorLogDeleteAllDataConfirmation',
        Url: '@Url.Content("~/VisitorLog/GetData")',
        Width: 790,
        Height: 464,
        Caption: '@Resources.Resource.VisitorLogIndexTitle',
        VisitorName: '@Resources.Resource.VisitorLogIndexVisitorName',
        StartDate: '@Resources.Resource.VisitorLogIndexStartDate',
        EndDate: '@Resources.Resource.VisitorLogIndexEndDate',
        WasTimeOut: '@Resources.Resource.VisitorLogIndexWasTimeOut',
        Actions: '@Resources.Resource.VisitorLogIndexActions',
        PageSize: @pageSize
    }
</script>
@section scripts
{
    @Content.Script("VisitorLogGrid.js", Url)
}

In the first section of the razor view above, after I setup the page title and layout, I am getting from the HTTP session the last grid settings and search parameters (start date and end date) used in the page.

Then there is a table used for filtering the results by using the start date and end date. Note that AJAX is used to get only the needed data by invoking the Search action of the VisitiorLog controller, and for success the JavaScript function showGrid (defined in Step 2 above) is used and only a partial area of the page that contains the grid is updated.

For rendering the grid, the partial view and JavaScript code described in the steps above are used by using the Razor blocks: @Html.Partial("_VisitorLogGrid") and @Content.Script("VisitorLogGrid.js", Url).

At the end of the view here is an inline JavaScript section that creates and initializes a paramFromView object used in the JavaScript code from the second step. In this way we communicate dynamically the last grid settings (that user may change during the execution by using UI) and also the values of the labels, button texts, and messages that are read from the proper Resources files (based on the current selected language). Note that the next two URLs associated with the controller action are also setup here:

  • VisitorLog/GetData - get the data for the grid,
  • VisitorLog/ClearGridData - delete all data that match the current filter.

Step 4 (Controller)

Now it is time to go to the VisitorLogController class to implement the actions invoked from the view.

The main method invoked from the JavaScript code by using AJAX to load the data that will be used by the jqGrid by using the grid settings is the next one:

public JsonResult GetData(GridSettings grid)
{
    if (_fromIndex && Session["VisitorLogGridSettings"] != null)
    {
        //
        // Get grid settings from cache!
        //
        grid = new GridSettings((str ing)Session["VisitorLogGridSettings"]);
    }
    //
    _fromIndex = false; // Must be set on false here!
    //
    // Load the data from the database for the current grid settings.
    //
    DateTime searchStartDate = (Session["StartDate"] == null ? 
      DateTime.MinValue : (DateTime)Session["StartDate"]);
    DateTime searchEndDate = (Session["EndDate"] == null ? 
      DateTime.MinValue : (DateTime)Session["EndDate"]);
    int count;
    var query = grid.LoadGridData<VisitorLog>(
      VisitorLog.SearchLogByDates(_db, searchStartDate, searchEndDate), out count);
    var data = query.ToArray();
    //
    // Convert the results in JSON jqGrid format.
    //
    string gridSettingsString = grid.ToString(); // Used to preserve grid settings!
    Session["VisitorLogGridSettings"] = gridSettingsString;
    gridSettingsString = null;
    var result = new
    {
        total = (int)Math.Ceiling((double)count / grid.PageSize),
        page = grid.PageIndex,
        records = count,
        rows = (from host in data
                select new
                {
                    ID = host.ID,
                    VisitorName = GetVisitorNameForBinding(host),
                    StartDate = host.StartDate.ToString(),
                    EndDate = host.EndDate.ToString(),
                    WasTimeOut = (host.WasTimeOut ?? false),
                    Action = string.Format("{0}", 
                                    RenderHelpers.ImageButton(
                                            this, 
                                            Resources.Resource.DeleteTip,
                                            "~/Content/Images/Delete.gif",
                                            "Delete", 
                                            new { id = host.ID }, 
                                            new { style = "border:0px;" }))
                }).ToArray()
    };
    //
    // Convert to JsonResult before to return.
    //
    return Json(result, JsonRequestBehavior.AllowGet);
}

In the code above you can see that the GridSettings object that comes as a parameter is initialized via our custom GriModelBinder described in the chapter above and is used to send the user selections, from the user interface, used for pagination and sorting. Then this object or an object created from cached data (in the case when the user comes back from another page) is used to load data for the current filter and to create rows for the current page. Finally the results are converted into JSON format to be returned.

JSON, or JavaScript Object Notation, is a text-based open standard designed for data exchange. It is derived from the JavaScript scripting language for representing simple data structures and associative arrays, but is has a text format that is completely language independent.

Note that in the source code above there is a JSON object created named result, and it has four properties (total, page, records, and rows) with the same names as were defined in the JavaScript in Step2 above. Also the rows property contains the rows data for the grid (current page), and each row defined in the JSON above must initialize all properties defined in the jqGrid colModel property in Step2.

In the picture below you can see the Visitors (Index) page in the action when the user is specifying the start date and end date (by using the date picker control) for filtering the results. 

When the user presses the “Apply Filter” button the following action method is invoked by using AJAX, to search for visitor logs entries by using as filter the start date and/or the end date specified by the user.

public PartialViewResult Search()
{
    string startDate = Request["from"];
    string endDate = Request["to"];
    //
    // Cache the start and end dates into the session to be used by later one in the view.
    //
    Session["StartDate"] = (startDate.Length < 1 ? null : 
      (object)DateTime.Parse(startDate, Thread.CurrentThread.CurrentCulture));
    Session["EndDate"] = (endDate.Length < 1 ? null : 
      (object)DateTime.Parse(endDate, Thread.CurrentThread.CurrentCulture));
    //
    return PartialView("_VisitorLogGrid");
}

The code above gets the parameters from the request, then caches their values to be used later on in the view, and finally renders the _VisitorLogGrid partial view.

The third method is invoked at user request, indirectly from JavaScript code, to delete the visitor logs that match the current filter:

public ActionResult ClearGridData()
{
    DateTime searchStartDate = (Session["StartDate"] == null ? 
      DateTime.MinValue : (DateTime)Session["StartDate"]);
    DateTime searchEndDate = (Session["EndDate"] == null ? 
      DateTime.MinValue : (DateTime)Session["EndDate"]);
    VisitorLog.DeleteLogByDates(_db, searchStartDate, searchEndDate);
    //
    return RedirectToAction("Index");
}

The code above uses the current start date and end date parameters from the cache to delete entries from the database that match the current search filter, then redirects to the index page.

Before Running this Code

Before running this code, you should do these steps:

  1. Create a new entry into the Event Log by running the CreateEventLogEntry application as Administrator (CreateEventLogEntry application source code is provided as part of our solution);
  2. Create a database named MvcBasicSite in your SQL Server (or SQL Express), then restore the provided database MvcBasicSiteDatabase.bak on it.
  3. Modify the connection string in the Web.config file of the MvcBasicSite web application according to your settings from Step 2.

If this article and provided source code is helpful for you, you could vote for it; if you have questions I am here to help you!

References

History

  • 16thMay, 2013: Version 1.0.0.1 - Draft version.
  • 24thMay, 2013: Version 1.0.1.1 - Some new details added.
  • 1thJune, 2013: Version 1.0.2.1 - New chapter added.
  • 8thJune, 2013: Version 1.0.2.2 - Some new details added.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here