ExtJs Grid Dynamic Columns

I had to use ExtJs grid in my recent MVC project. The grid and all other controls are really good, both functionality and usability wise but I stuck into the problem when I needed to make the grid columns dynamic. Most of the examples I found on forums and online tutorials were about using it with fix definition of columns. After searching forums and blogs on Internet, I was able to fix the issue but I realized that there is no thorough tutorial or help to address this issue for ASP.Net MVC. In this blog, I will be giving a complete end to end solution to use dynamic grid columns from ASP.Net. It can be further improved and make more dynamic but it will fulfill most of the requirements.

First of all, I found this ExtJs extension method on ExtJs forums. I didn’t save the link otherwise I would have added the link to that post here. This script needs to be just saved in .js file and referenced from the code. It will take care of most of the heavy lifting on the client side.
Although most of the code is same as I copied it from the forums but I modified the “DynamicColumnModel” function to add my own code for handling the “Column” metadata sent from server. I have commented those line in the code so it can be modified if JSon result returning from server is not in the same format as mine.

//*****************************************
//ExtJS method for dynamic columns
//*****************************************
Ext.data.DynamicJsonReader = function(config)
{
    Ext.data.DynamicJsonReader.superclass.constructor.call(this, config, []);
};

Ext.extend(Ext.data.DynamicJsonReader, Ext.data.JsonReader, {
    getRecordType: function(data)
    {
        var i = 0, arr = [];
        for (var name in data[0]) { arr[i++] = name; } // is there a built-in to do this?

        this.recordType = Ext.data.Record.create(arr);
        return this.recordType;
    },

    readRecords: function(o)
    { // this is just the same as base class, with call to getRecordType injected
        this.jsonData = o;
        var s = this.meta;
        var sid = s.id;

        var totalRecords = 0;
        if (s.totalProperty)
        {
            var v = parseInt(eval("o." + s.totalProperty), 10);
            if (!isNaN(v))
            {
                totalRecords = v;
            }
        }
        var root = s.root ? eval("o." + s.root) : o;

        var recordType = this.getRecordType(root);
        var fields = recordType.prototype.fields;

        var records = [];
        for (var i = 0; i < root.length; i++)
        {
            var n = root[i];
            var values = {};
            var id = (n[sid] !== undefined && n[sid] !== "" ? n[sid] : null);
            for (var j = 0, jlen = fields.length; j < jlen; j++)
            {
                var f = fields.items[j];
                var map = f.mapping || f.name;
                var v = n[map] !== undefined ? n[map] : f.defaultValue;
                v = f.convert(v);
                values[f.name] = v;
            }
            var record = new recordType(values, id);
            record.json = n;
            records[records.length] = record;
        }
        return {
            records: records,
            totalRecords: totalRecords || records.length,
            totalProperty: 'totalRecords'
        };
    }
});

Ext.grid.DynamicColumnModel = function(store)
{
    var cols = [];
    var recordType = store.recordType;
    var fields = recordType.prototype.fields;

    //for dynamic columns we need to return the columnInfo from server so we can build the columns here.
    //in this example, the ResultData is a JSON object, returned from the server which contains a ColumnInfo
    //object with "fields" collection. Each Field in Fields Collection holds the information column
    //we are using the "renderer" here as well to show one important feature of displaying the MVC JSon Date
    $.each(store.reader.jsonData.ResultData.columnInfo.fields, function(index, metaValue)
    {
        cols[index] = { header: metaValue.header, dataIndex: metaValue.dataIndex, width: metaValue.width,
            sortable: metaValue.sortable, hidden: metaValue.hidden,
            renderer: function(dtData) { if (metaValue.renderer) { return eval(metaValue.renderer + "('" + dtData + "')"); } else return dtData; }
        };
    });

    Ext.grid.DynamicColumnModel.superclass.constructor.call(this, cols);
};
Ext.extend(Ext.grid.DynamicColumnModel, Ext.grid.ColumnModel, {});
//*****************************************
//End of dynamic columns
//*****************************************

One of the issues which I had faced when using the ExtJs Grid with ASP.Net MVC was the date format of Json serializer. It was returning the date as a special value of “/Date(xxxxx)” format and it needed to be converted before displaying it on the Grid. This is where the “renderer” property of columns comes into play. I had passed the name of the renderer from ASP.net code so there are no long list of checks in the javascript code. It will just call the “eval” and execute whatever server render method is specified. In case of dates, I am using the following javascript code to parse and convert the date into short date time format.

//this method is used to convert the MS JSON date format to the ExtJS Grid Date Column Value
function dateFormatter(dt)
{
    /// <summary>this method is used to convert the MS JSON date format to the ExtJS Grid Date Column Value</summary>
    /// <param name="dt">Actual JSON Date Value</param>
    try
    {
        //microsoft JSON date format needs to convert into Javascript date
        var newdata = dt.replace(/\/Date\((-?[0-9]+)([+-][0-9]+)?\)\//g, "new Date($1)");
        newdata = eval('(' + newdata + ')');
        return newdata.format('m/d/Y');
    }
    catch (e)
    {
        return dt;
    }
}

Enough of ExtJs Code :). Now let’s look at some server side code which will help us build the dynamic columns and the meta data for ExtJs. First thing which I needed was to convert the datatable into an object which can be serialized by Json. If you try to serialize a datatable with ASP Json serializer then it will through the “circular reference” error so this utility method will take the datatable and convert it into a list which can be easily serialized.

/// <summary>
/// Normalizes the datatable into simple collection of row objects, that later on can be used to create the JSON object.
/// </summary>
/// <remarks>
/// whenever we need to return the datatable from view as a JSON object, then we need to first convert that datatable into a collection of rows so that it can be properly serialized.
/// </remarks>
/// <param name="dt">Source datatable that will be used to create the rows collection.</param>
/// <returns></returns>
protected List<Dictionary<string, object>> GetNormalizedRows( DataTable dt )
{
    List<Dictionary<string, object>> rows = new List<Dictionary<string, object>>();
    Array.ForEach(
        dt.Select(),
        row =>
        {
            Dictionary<string, object> Dictionary = new Dictionary<string, object>();
            foreach ( DataColumn col in dt.Columns )
                Dictionary.Add( col.ColumnName, row[col] );
            rows.Add( Dictionary );
        }
    );
    return rows;
}

Once we have the helper method ready to convert our datatable, we can write a “Controller” action, which will be called from the UI to access the data for the grid.

/// <summary>
/// This method will execute the SQL Query to create the Search results.
/// </summary>
/// <returns></returns>
[HttpPost]
public ActionResult GetData( string anyServerData )
{
        //Execute the query and return the data.
        Datatable resultTable = <get your data from server>;

        //datatables can't be serialized as JSON object, so we need to normalize them and also we need to pass the column meta
        //data information to only show specific grid columns.
        return JSon( new ResultData {
	 new
        {
            GridData = GetNormalizedRows( resultTable ),
            columnInfo = GetColumnMetaData( resultTable )
        } } 
	);
}

That method was really simple and if you have been following it then there was another helper method used in it to get the column meta data, “GetColumnMetaData”. This method will simply loop through the table, create the meta data objects for the ExtJs. It will set the required fields which we need in ExtJs to create the columns.

/// <summary>
/// Extracts the column meta data for ExtJS grid. 
/// </summary>
/// <param name="searchResult"></param>
/// <returns></returns>
private Dictionary<string, object> GetColumnMetaData( DataTable searchResult )
{
    Dictionary<string, object> metaData = new Dictionary<string, object>();
    List<Dictionary<string, object>> colMeta = new List<Dictionary<string, object>>();
    Dictionary<string, object> colDefinition = new Dictionary<string, object>();

    //loop through each datatable column. Get the column name type and other meta specific information and store them in this specific 
    //format so that it can be used by the ExtJS grid on client side.
    foreach ( DataColumn col in searchResult.Columns )
    {
        colDefinition = new Dictionary<string, object>();
        colDefinition.Add( "header", col.ColumnName );
        colDefinition.Add( "dataIndex", col.ColumnName );
        colDefinition.Add( "width", 100 );
        colDefinition.Add( "sortable", true );
        colDefinition.Add( "hidden", false );

	//for datetime columns, we need to render them specially as the date returned from MVC Json is not in format to display to the user
        if ( col.DataType == typeof( DateTime ) )
            colDefinition.Add( "renderer", "dateFormatter" );

        colMeta.Add( colDefinition );
    }

    metaData.Add( "fields", colMeta );
    metaData.Add( "root", "ResultData" );
    metaData.Add( "totalProperty", "results" );
    metaData.Add( "id", "id" );

    return metaData;
}

Finally, to make these all pieces to work, we need to make an AJAX call from client to this Controller action. Any button or link on client can be used to attach a click event handler and in that event handler, we can create the ExtJs store and grid.

//attach a function to the click of HTML Element
$("#btnGetData").click(LoadGridData);

//This will make an AJAX call to the server, passing all the form data and get the result of the search.
function LoadGridData(e)
{
    /// <summary>This will make an AJAX call to the server, passing all the form data and get the result of the search.</summary>
    /// <param name="e">Event object for the clicked Element.</param>

    e.preventDefault();

    //show the AJAX loader... the code is in my previous posts. it is not required so it can be removed if not needed
    objUIHelper.ShowAJAXLoader(true);

    //create the data store to load the data from AJAX web call
    jstore = new Ext.data.Store({
        proxy: new Ext.data.HttpProxy({ url: 'MyController/GetData', method: 'POST' }),
        reader: new Ext.data.DynamicJsonReader({ root: 'ResultData.GridData' }),
        remoteSort: false
    });

    //in case of any AJAX call exception, hide the uiBlocker and show the error message	
    jstore.on('loadexception', function(event, options, response, error)
    {
        //unblock the UI, another helper method to remove the ajax loader
        objUIHelper.HideAJAXLoader();
        
        //show the server error... 
        alert( response.responseText );
    });

    //we need to set the extra params here so it can be used on the grid refresh click
    jstore.on('beforeload', function()
    {
        jstore.baseParams = {
            anyServerData: $("#myHTMLForm").serialize()
        };
    });

    //after the data load in store, create the grid and display the data
    jstore.on('load', function(gridStore)
    {
        //remove any exisitng items from grid div. Grid div is a placeholder to show the extJsGrid
        $("#grid").empty();

        // Reset the Store's recordType
        gridStore.recordType = gridStore.reader.recordType;
        gridStore.fields = gridStore.recordType.prototype.fields;

        //create the paging toolbar
        var bar = new Ext.PagingToolbar({
            store: gridStore,
            pageSize: resultPageSize,
            displayInfo: true,
            displayMsg: 'Displaying record {0} - {1} of {2}',
            emptyMsg: 'No rows to show'
        });

        // Create the grid and bind it with the data store.
        grid = new Ext.grid.GridPanel({
            store: gridStore,
            cm: new Ext.grid.DynamicColumnModel(gridStore),
            selModel: new Ext.grid.RowSelectionModel({ singleSelect: true }),
            enableColLock: true,
            renderTo: 'grid',
            width: 940,
            autoHeight: true,
            title: 'Results',
            bbar: bar,
            pageSize: resultPageSize
        });

        // render the grid on UI
        grid.render();

        //unblock the UI
        objUIHelper.HideAJAXLoader();
    });

    //store's load will call the web URL and load the data
    jstore.load({
        params: {
            start: 0,
            limit: resultPageSize
        }
    });
}
About these ads

Tags: , , ,

About prres

I have been working with Microsoft Tools and Technologies for 9 years. Most of my work include architecting and developing with C#, ASP.Net, Silverlight and SQL Server.

27 responses to “ExtJs Grid Dynamic Columns”

  1. TG says :

    Hello! Great article, but… imho, a lot of “eval” is very ugly!
    for example: var root = s.root ? o[s.root] : o;

    • prres says :

      Thanks TG for your comment and I agree with you. Even in some cases eval can generate some unexpected results too but I have copied the line of code, which you have mentioned, from an ExtJS forum and I really didn’t dig into that code :). It was working fine so I was really relieved after hunting down this dynamic column problem. I will certainly replace the code with your proposed solution and test it in my project.

  2. GD says :

    Hy, can you provide a sample of your json data???
    Thanks..

    • prres says :

      Hi,

      Here is a sample of Server response, you can extract JSON from it:

      {“ResultData”:{“GridData”:[{"Record Num":1},{"Record Num":2}],”columnInfo”:{“fields”:[{"header":"Record Number","dataIndex":"RecordNum","width":100,"sortable":true,"hidden":false}],”root”:”ResultData”,”totalProperty”:7,”id”:”id”}}}

      So Result Data is the object and it has “GridData” and “columnInfo” properties. GridData contains the actual data to display in grid. ColumnInfo is the meta data for column headers, index, width and other properties.

      Let me know if it helped.

      Thanks

      • kiran says :

        As per your given response above : dataIndex attribute’s value ‘RecordNum’ is not matching with GridData’s record attribute ‘Record Num’ it should be ‘RecordNum’ instead without space. Hope this will save others time. Thanks for your efforts.

  3. chennupatik says :

    iam getting error o.ResultData is undefined

    • prres says :

      Can you try to debug in FireBug to check what is the value in “o”? It seems that you are not returning the correct JSON object from server.

  4. Karthikeyan Rajmohan says :

    Hellow! its a great article. Can you provide me the copy of the source code?

  5. ibrahim says :

    hi. i really need to dı what you showed here.

    first i wanted to see how it works but i couldt run it.

    i changed url to => url: ‘JSPFile.jsp’

    in my jsp file I do println the json you provided above.

    and i created a button handlers : grid.show();

    firebug just says “grid undefined”

    could help me through it?

    • prres says :

      Where do you get the “grid undefined” error? If you are getting this error in the button event handler then make sure that grid object that stores the reference to JS Grid is accessible and initialized.

  6. Bob Weber says :

    Thanks!
    I was consuming JSON from a partner’s WCF site and was suffering over the dates.
    Your renderer saved my RC!

    Thanks again,
    Bob

  7. Prashanthi says :

    Hi,

    This is a wonderful article and useful too.

    Is there a way to do Column freeze on Dynamic ColumnModel.

    Can you please let us know, what is the approach?

    Thank you,
    Prashanthi.

    • prres says :

      From the server side code, you can return any attributes and use javascript in column model to apply the attribute. I will try if I can create a sample.

  8. raj says :

    hi i’m not able to load the data it throws an error o.ResultData.Gridata is empty

  9. raj says :

    I’m trying to validate a exel file and once the validation is done i will load this dynamic grid with the result set and getting the error o.ResultData.Gridata is empty

  10. Irwin says :

    ResultData, GridData and columnInfo objects can not be resolved…. Anyone else get this to build ?

  11. ivan utama says :

    hi,
    I see your server side code using “data table”,
    can I change the “data table” with IList or List ?

  12. kiran says :

    When creating “DynamicJsonReader” ExtJS “each” syntax can be written as:

    Ext.each(store.reader.jsonData.ResultData.columnInfo.fields, function(metaValue,index)

    instead of

    $.each(store.reader.jsonData.ResultData.columnInfo.fields, function(index, metaValue)

    • prres says :

      Yes it can be but I was using the JQuery. In my project I used ExtJs grid only at one place for data display. Thanks for the valuable feedback :).

  13. Acrorncon says :

    I was very happy to find this web-site.I desired to many thanks for your energy because of this wonderful read!! I definitely enjoying each little bit of it and I’ve you bookmarked to check out new stuff you blog post.

    Can I just say what a relief to locate a person who in fact knows what theyre speaking about online.

    NFL Jerseys
    Personalized NFL Jerseys

  14. analisa usaha says :

    Hi, I do believe this is an excellent blog. I stumbledupon it ;) I will return once again since i have bookmarked it. Money and freedom is the best way to change, may you be rich and continue to guide other people.

  15. equiman says :

    This code is awesome… works like a charm. I’m trying to understand it better… but i’m lost in this point:

    jstore.on(‘load’, function(gridStore)

    where is created this gridStore?

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

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: