jQuery Grids Unleashed – Flat Grid Architecture (JavaScript)

Alexander Todorov / Monday, November 14, 2011

jQuery Grids Unleashed – Flat Grid Architecture (JavaScript)

We, at Infragistics, have worked very hard this year in order to deliver what we believe is the best JavaScript & HTML5 control toolset on the market. Following the recent 2011.2 release announcement, I would like to start a series of blog posts describing in detail what we have built, primarily focusing on architecture, design concepts, rendering, performance, and API usage.

Some of the concepts that are going to be described in the series aren’t something new – they are about the jQuery grid, which we have released in 2011.1, but I would like to do an in-depth coverage of all areas.

Overview

The main topic of this blog post is the architecture of the Infragistics flat jQuery Grid. It focuses on the HTML & JavaScript part; stay tuned for more info on the MVC Wrappers and the server-side data binding.

I would also like to point out that this is a brand new control (starting from 2011.1), which is completely server and plugin agnostic. As I have shown in one of my previous blog posts about Windows 8 (WinJS), the grid can also be used out of the box in a METRO Web & Win application without modifying any of the controls’ code. Most of our samples have pure HTML/JavaScript equivalents and can be run without any dependence on ASP.NET MVC, IIS, and the .NET Framework. We support all major browsers – Firefox, Chrome, IE6+, Opera, and Safari. You can use any server-side infrastructure in order to provide data in the form of REST Services to the grid - .NET (ASP.NET MVC), PHP, Ruby on Rails. We also have full built-in support of the OData specification, as described here:

http://www.odata.org/developers/protocols/uri-conventions

Dependencies

Our grid is built from the ground up using jQuery and jQuery UI. We support all jQuery versions 1.4.4 and above, and all jQuery UI versions starting with 1.8.3.

When we have started our initial research, we have evaluated most of the existing JavaScript frameworks and UI frameworks, and have found jQuery UI to be the closest match to our key goals and requirements. It provides:

  • Powerful widget factory which allows very clean separation of complex and feature-rich API into different stateful UI modules – as opposed to entirely relying on plain jQuery plugins
  • UI Widgets inheritance
  • Great theming framework (Themeroller)
  • Built using jQuery
  • Powerful animations and effects framework, which is improved and optimized daily by community contributions
  • Great event dispatching and namespacing framework

Key Goals

The key goals of the jQuery Grids are:

  • Performance
  • Ease of use
  • Accessible and W3C standards compliant rendering
  • Simple and yet very powerful API
  • Modularity and extensibility
  • Rich styling support
  • Server agnostic
  • Built using the latest cutting edge technologies and design concepts, yet having support for all major browsers on the market

Performance is a big and important topic, and in fact most of the key goals have a separate topic dedicated to them, but I would like to elaborate more on the Modularity part, because this is one of the great key differentiators in our grids as compared to competing products on the market.

The jQuery grids consist of several main parts – the JavaScript DataSource control – which we ship as a separate standalone module, the grid framework UI widget, and all feature UI widgets.

A widget represents a piece of UI, usually dynamically built based on some HTML placeholder, backed up by some JavaScript API, with a well-defined lifecycle. A widget can be a control such as the Grid, or a feature such as Sorting, or something as simple as a button.

The design goal behind widgets is that they usually “decorate” some existing plain HTML markup. This way, by using progressive enhancement, you can use an incremental approach to add functionality to your app. For example a rich, rounded corners button may decorate a plain input element of type button, or a button tag itself.

I am not going to go into details about how jQuery UI widgets are implemented and what their lifecycle is, this is described in detail on the jQuery UI website:

http://jqueryui.com/docs/Getting_Started

http://wiki.jqueryui.com/w/page/12138135/Widget%20factory

The Client-side data source control is the backbone of the grids. Its lifecycle (creation, configuration, destruction) is managed internally by the grid, and therefore, as a developer, you only need to setup the grid by supplying it with any of the supported data sources (such as an array of objects, or just a URL string). You don’t need to deal with creating an $.ig.DataSource by yourself, even though this control is itself part of the supported data sources for the grid.

All of the data binding, as well as paging/sorting/filtering/summaries logic are implemented there. There is a complete abstraction in terms of data binding and AJAX processing. The idea of the data source is to bind to various sources and serve JavaScript arrays to the grids. It is built as a class, following John Resig’s Simple JavaScript Inheritance pattern:

http://ejohn.org/blog/simple-javascript-inheritance/

Thus, the data source can also be easily extended by subclassing it and overwriting parts of its API, as it is demonstrated with various extensions of the $.ig.DataSource, which we already ship, such as the $.ig.RemoteDataSource, the $.ig.WebSocketsDataSource, and so on. We haven’t taken a pluggable “adaptors” approach, because we believe that it’s more work for the developer and considering all of the paging/sorting/filtering is an integral part of the data source functionality, it makes it less clean and efficient to have those work together with an adaptors approach. Using subclassing, there is virtually endless room for adding new features, as well as extensibility points.

The jQuery grid framework widget (igGrid) implements the following functionality:

  • Rendering / cell formatting / templating
  • Virtualization
  • Layout management (widths / heights / headers fixed vs. non-fixed, etc.)
  • Auto generation of columns
  • Configuring and connecting to the $.ig.DataSource

Each grid feature – such as paging, sorting, filtering, etc. is defined in a separate UI widget, which cannot be used in a standalone manner, without a grid.

The grid doesn’t require or load any of the features code, unless they are defined in the developer’s application code. This has several major advantages:

  1. Clear separation of functionality
  2. Flexibility in terms of what gets included / downloaded, and what not. It’s possible to reference only the framework, and paging, for example, you don’t need to load all features’ JavaScript.
  3. The features are aware of the grid’s API, while the grid is unaware of any of the features that exist on it. In fact, all features work by decorating their functionality on the grid, after it’s created. (Or during its creation, depending on the feature in question). Some features can “check” for each other’s existence, too.
  4. Even though feature initialization options are defined as part of the grid initialization options, a separate feature gets instantiated on the same element on which the grid is instantiated. Therefore you can use the feature API independently of the grid API, bind to feature specific events, destroy the feature UI widget independently of the grid UI widget, and so on.

 

Needless to say, this also implies clean physical separation of the code in different files. The grid framework code is defined in ig.ui.grid.framework.js, while paging, for example, is implemented in ig.ui.grid.paging.js.

Features are defined in a very simple way in the grid’s initialization code. For instance:

$("#grid1").igGrid({
    autoGenerateColumns: true,
    dataSource: gridData,
    features: [
        {
            name: 'Paging',
            // additional options 
        }
    ]
});

The grid reads the features array, and then it maps them to the respective feature widgets, by instantiating those widgets.

Figure 1: jQuery Grid architecture overview

pic1

Data Binding – Data Source control

Being a core component behind the grid, the Data Source supports many different sources of data. They include:

  1. Binding to JavaScript arrays of objects
  2. Binding to JavaScript complex objects, which contain arrays in some of their properties
  3. Binding to a string which represents valid JSON data
  4. Binding to a string which represents valid XML data
  5. Binding to a XML Document object
  6. Binding to a remote URL which returns JSON or XML
  7. Binding to a function which returns data in the above formats, after being invoked.
  8. Binding to a HTML Table DOM element which already exists on the page
  9. Binding to a HTML table by supplying its ID directly
  10. Binding to a string which represents HTML Table markup

 

When you supply any of the above, you don’t need to explicitly specify the type of the data source that you bind to. The data source control will automatically analyze its dataSource property, and will try to infer the type. Having said that, there is only one property which is used to set a data source – it’s called “dataSource”. There is no separate dataSourceUrl for remote bindings. The control’s analysis logic can infer whether it’s served an object, or a URL (by analyzing the structure of the value, etc.)

Paging, sorting, and filtering, summaries, and any combination of them are supported out of the box by enabling those features in the initialization code:

var ds = new $.ig.DataSource({ 
    type: "json", 
    dataSource: adventureWorks, 
    responseDataKey: "Records", 
    filtering: { type: "local" },
    sorting: { type: "local" },
    paging: { enabled: true, pageSize: 10, type: "local" }
            
});

We have designed the data source control in such a way that local and remote operations can be set with just a single property, called “type”. You can have local paging, with remote filtering, for example, all out of the box. If type is “remote” for any of the features, this implies that the dataSource is a remote URL (string like http://www.mysite.com/myservice/GetProducts).

After the dataSource’s dataBind() is called, there are two main properties which hold the data:

  1. data() => returns the bound data before local paging, sorting, and filtering are performed
  2. dataView() => returns the current local view of the data, after paging, sorting, and filtering are applied. In case you have both the data binding, as well as data operations (paging/sorting/filtering) working remotely, then the “data” array is basically the same as the “dataView” array.

The Data Source control also supports local (client-side) schema transformations through the localSchemaTransform (true) configuration option. Imagine you have some data coming from the server, which is all strings, but you know some of the columns are numbers, some are representing Date ticks, and some are representing “true” or “false” boolean values. If you define a data source schema, or explicit type for the grid’s columns, the data source will convert this data so that when the grid or the standalone data source is bound, the dataView/data arrays will have the correct types.

Note that when you configure the grid, the columns collection is used to generate a schema for the data source. So if you set dataType:”number” for a column, even if this data is in string format in the actual data source, it will be converted to numbers.

Date objects are a bit special, because various server-side frameworks encode them differently. For example when you are returning a JsonResult object from an ASP.NET MVC Controller action, the Dates get encoded in the following format: "/Date(1297973847733)/". The data source control can detect and convert this format to Date objects, if your column is defined with a dateType: “date”.

When features, such as paging/sorting/filtering are configured to work remotely, you have the freedom to customize the request URL parameters that are appended to the URL when a request to fetch data is created. By default, every feature has specific options which specify how its current state is going to be encoded in the URL – for instance paging has pageIndexUrlKey and pageSizeUrlKey and they are null by default. This means that OData encoding will be used in order to encode paging requests like this:

http://odata.netflix.com/Catalog/Titles?$format=json&$callback=jsonp1321046061142&%24skip=20&%24top=20&%24inlinecount=allpages

If those are set to some values of your choice (such as page and pageSize, respectively), your request will look like this:

http://samples.infragistics.com/jquery/grid/BindGridPaging?controller=GridPaging&sampleid=7d8d0c5c-a18e-431e-8672-0553efaa2b22&_=1321046146732&page=1&pageSize=12

Moreover, if you need further customizations for the URL encoding, you can handle the urlParamsEncoding and urlParamsEncoded events, in order to add any custom logic or additional keys and values in the request.

I should note that when the grid data-binds remotely, it uses GET requests by default. When it updates data, serializes updating transactions (batch update scenario), or sends any state changes in general, it uses POST requests by default.

Last, but not least, you can easily extend the $.ig.DataSource class in order to implement your custom sources of data. You would need to overwrite the _processRequest(<ajax options>) as an entry point.

Rendering and performance

Our JavaScript flat grid uses a very optimized rendering approach which is based on solid research and upfront performance measurements about various rendering techniques and algorithms. We start off with a JavaScript array of data object that we need to render. It’s not as simple as just outputting a bunch of HTML or appending a bunch of DOM notes to a table. We first need to format the data values based on some default format / formatting function. So every column definition has a format property, which also has a default value. For Dates, for example, that is dd/mm/yyyy. After formatting the data values, we iterate through all rows by concatenating the generated HTML markup string for a data row, into a resulting string which holds all data rows. Finally, we append, at once, the resulting string to the body of our data table. We optimize this process additionally, by checking if the browser supports setting innerHTML for the TBODY tag, so that we can set its contents directly to our generated string.

You can find an in-depth explanation of all performance-related options and best practices in our Performance Guide:

http://help.infragistics.com/Help/NetAdvantage/jQuery/2011.2/CLR4.0/html/igGrid_Performance_Guide.html

You can also get a sense of how we are evaluating our grid’s truly outstanding performance with real world scenarios and measurements, by checking out our jQuery Grid Performance Whitepaper:

http://download.infragistics.com/marketing/infragistics-jquery-grid-performance-whitepaper.pdf

Templating

The jQuery grid supports templating of your data records through use of the jQuery Templating plugin:

http://api.jquery.com/jquery.tmpl/

We plan to ship our own templating engine in the future, as well as add support for column templates.

Whether templating is enabled or not, is transparent of any column formats and types that are defined. For example if you have a date column, and you would like to format dates as “dd/mm”, this will work fine no matter what the jQueryTemplating option is set to (false/true).

Templating can be enabled by setting the jQueryTemplating: true, and then defining the rowTemplate option by following the syntax defined on the jQuery templating documentation page given above.

Layout

The generated markup by the grid can differ depending on whether various options are set.

Options which affect the rendering structure of the grid are – width, height, fixedHeaders, fixedFooters (true/false), as well as virtualization (true/false).

If the grid doesn’t have width or height set, then the structure is the following: div container => Table element. There is a container div wrapping the data table all the time, no matter whether the grid is instantiated on a TABLE or on a DIV element. The div wraps that at runtime. This is done in order to accommodate various other elements which can be rendered on top of the header, such as parts of the pager UI, header caption, etc.

If the grid has width or height set, and fixedHeaders is false, the rendered layout is the following:

- the caption is always rendered in a separate table as a child of the grid’s container div (you can always lookup this container by ID, by using <grid element id>_container

- the headers of the data table are rendered in the thead of the data table itself

- the data table is wrapped in a scrolling container

pic2

If the grid has width or height set, and fixedHeaders is true, then the layout becomes the following (the difference is that now the headers table is in a separate table, which is wrapped in a div; the headers div is with overflow:hidden, and scrolling the data table horizontally synchronizes the left offset of the headers table so that they are both in sync):

pic3

If you enable the accessibilityRendering option, we also output various W3C accessibility attributes so that we make sure any screen reading or other similar software can work out of the box:

clip_image007

The autoAdjustHeight option plays an important role in the grid rendering and layout. It is also described in the performance guidelines document, because it affects performance (when set to true). It is true by default. Its meaning is:

1) When set to true – if we set a height value, the grid container will always take this value, no matter how many UI elements apart from the data table exist in the grid. The data table’s scrolling container will be automatically adjusted to accommodate for the rest of the vertical UI. Let me give you an example:

haha4

2) When set to false, the height option will be always applied to the data table’s scrolling container, therefore any additional vertical UI such as pager, caption, filter row, etc. will add on top of that height, so your total grid height will be always more than the specified height in px, assuming you have enabled functionality which adds UI parts on top and at the bottom of the data table.

Theming Framework

Being built on top of jQuery UI, the grid makes full use of the Themeroller styling framework. We have full separation of layout-specific CSS (positioning, overflows, paddings, etc.), and theme-specific CSS (colors, borders. Etc). When you build your application, you need to reference 2 CSS files:

<link type="text/css" 
        href="/SamplesBrowser/Styles/mvc/jQueryThemes/min/ig/jquery.ui.custom.min.css" 
        rel="stylesheet" />

This defines the custom themeroller theme that we use by default – which is called IG Style.

<link type="text/css" 
        href="/SamplesBrowser/Styles/mvc/jQueryThemes/base/ig.ui.min.css" 
        rel="stylesheet" />
 

This should be always included in your applications, no matter what the visual theme is. The CSS contained in ig.ui.min.css (or ig.ui.css) contains layout CSS and the controls won’t render properly without it.

So, in order to change the theme, you simply need to replace the jquery.ui.custom.min.css with a jQuery UI stylesheet of your choice – for instance ui-darkness.css.

You can also make full use of the Themeroller tool itself, and build your custom themes to style the grid:

http://jqueryui.com/themeroller/

In all of the grid’s framework and features, we also make full use of the jQuery UI base CSS classes for predefined states and interactions, such as active or selected item, widget content, icons, etc. An in-depth coverage of all available framework classes can be found here:

http://jqueryui.com/docs/Theming/API

Built-in Updating and transactions API

Even though we have shipped a full blown Updating grid feature, we have developed the core transactions and updating API as part of the data source control. Also, some of the API is directly exposed through the grid.

The primaryKey option plays an important role when it comes to updating. Most updating operations, such as editing a record, or deleting a record, require the existence of unique identifiers – primary key values; therefore it is important to set the primaryKey whenever you use Updating. Whenever this is set, the grid renders a special data-id=<primary key value> attribute for every record.

The Grid / Data Source’s transaction support can be divided into several main parts:

  1. transactions for editing records and cells
  2. transactions for deleting records
  3. transactions for adding new records

 

The data source supplies the following API in order to expose this functionality:

  • findRecordByKey
  • findRecordByIndex
  • removeRecordByKey
  • removeRecordByIndex
  • setCellValue
  • updateRow
  • addRow
  • insertRow
  • deleteRow

 

Also, the data source provides API for committing and rolling back transactions – if no parameters are specified to those functions, they apply to all transactions, otherwise they can commit or rollback a single transaction:

  • commit
  • rollback

 

Those two are also exposed in the grid’s API.

It’s also very important to note that even though indices can be used for updating manipulations, it is not the recommended approach. This should only be done on your own risk, and when there aren’t features enabled such as sorting or paging, which can “disrupt” or “reorder” the indices relative to the data rows.

The way updating and transactions work in a two-way approach, depending on the value of the autoCommit option – it’s an option in both the grid and the data source. When set on the grid, it’s delegated to the data source.

1) Local. When you add rows, delete rows, update cell values, and so on – transactions are stored in a transaction list in the data source. Depending on the value of autoCommit, those changes are either immediately persisted in the underlying data source, or are kept in the transaction list, until commit() or rollback() is called. You can take the current pending transactions using the data source API: pendingTransactions(). It returns a list of objects, in the following format, depending on the transaction type:

Add row:

{type: 'newrow', tid: <generated transaction id>, row: rowObject, rowId: rowId}

Delete row:

{type: 'deleterow', tid: <generated transaction id>, rowId: rowId}

Insert row:

{type: 'insertrow', tid: <generated transaction id>, row: rowObject, rowId: rowId, rowIndex: rowIndex}

Edit row:

{type: 'row', tid: <generated transaction id>, row: rowObject, rowId: rowId}

Edit cell:

{type: 'cell', rowId: rowId, tid: <generated transaction id>, col: colId, value: val}

2) Global. Even if autoCommit is true, or you invoke commit manually, this only “persists” the changes to the local data source. In most real life scenarios, you would like to persist your changes to the server, down to the database from which you are loading data. As soon as you call commit() or autoCommit is true, the local transaction log, exposed through pendingChanges(), is cleared, but all changes get appended in a global transaction log, which can be accessed by calling allTransactions().

So if you have set the updateUrl in the grid/dataSource options, and you call saveChanges() off the grid/dataSource, what the data source will do behind the scenes is take this global transaction log, serialize it, and do a POST request on the updateURL. If the server returns a JSON data structure which contains a Success = true property, the global log will be cleared, and the UI – updated, respectively (by Updating, if it is enabled). Returning a data structure that contains Success = true implies that the server has successfully committed the changes to the underlying data store.

Please note that in many cases, localSchemaTransform will be true and the data source will be of local type, which implies that the dataSource array in the data source control, after data binding has taken place, will not point to the original data source array. Instead it will point to a copy of the “transformed” original data source. Therefore, whenever you make any changes, they will be committed locally to the data source’s copy of the original data source. in cases you don’t want this to take place, you can manually loop through the transaction log and update your original JavaScript array.

There is a utility function, called transactionsAsString, exposed in both the data source, and the grid, which returns a serialized string of the transaction log, so that you can use this to pass to your service, if you’d like to have full control of the updating and transactions. This utility function serializes the accumulated global transaction log, not the local one.

In a future post I will elaborate further on the Updating and transactions support and provide real-world examples of using the API and events.

API Usage

I’d like to give you some examples of how the grid and features API is used in applications. As already described, it closely follows the jQuery UI conventions of using and interacting with UI widgets.

Creation

Creating a basic grid with auto-generated columns

$("#grid1").igGrid({
    autoGenerateColumns: true,
    dataSource: gridData
});

Retrieving some option value, after the grid has already been created:

var optionVal = $("#grid1").igGrid("option", "width");

Setting an option after the grid has already been created:

// change the grid caption
$("#grid1").igGrid("option", "caption", "new caption");

Note that some options such as width/height cannot be changed at runtime. In order to apply those, simply destroy the grid, and then create it again.

“destroying” the grid – implies removing all created elements, unbinding event handlers, etc. basically this means bringing the UI to its state before the grid was created:

$("#grid1").igGrid("destroy");

 

Any of the API can be called in the same way as shown above.

Columns

Defining columns with widths, dataTypes, format, and formatter functions:

$("#grid1").igGrid({
    autoGenerateColumns: false,
    columns: [
        { 
            headerText: "Product ID",
            key: "ProductID", 
            dataType: "number" 
        
        },
        { 
            headerText: "Product Name",
            key: "Name", 
            dataType: "string" 
        
        },
        {
            headerText: "Product Number",
            key: "ProductNumber",
            dataType: "string", 
            formatter: function (val) { return val.toLowerCase(); } 
        },
        { 
            headerText: "Color",
            key: "Color",
            dataType: "string" 
         },
        {
            headerText: "Modified Date",
            key: "ModifiedDate",
            dataType: "date", 
            format: "mm/yy" 
        },
        { 
            headerText: "Reorder Point",
            key: "ReorderPoint", 
            dataType: "number" },
        {
            headerText: "List Price",
            key: "ListPrice",
            dataType: "number" 
         },
        {
            headerText: "Standard Cost",
            key: "StandardCost",
            dataType: "number" 
        }
    ],
    dataSource: "/SamplesBrowser/Services/AdventureWorksDataService.svc/Products",
    responseDataKey: 'd.results',
    width: "500px",
    height: "400px"
});
Automatic generation of columns

The grid has functionality to automatically generate your columns by analyzing the data source. If you are using the pure JavaScript grid outside the context of ASP.NET MVC wrappers, this can be done either immediately, if your data source is local, or after the data is bound, if your data source is remote. The prerequisite is that there is at least 1 record in the data source, otherwise the grid cannot infer anything about the column key and type. When columns are automatically generated, the column headers are set to be the same as the column keys. Complex objects/arrays/functions in your data source’s records are skipped and columns are not generated for them.

It’s very important to note that when you have autoGenerateColumns set to true (which is the default value), and you also have columns defined manually, at the same time, the way it works and renders is the following:

  1. Your manually defined columns are added first. (and hence data for them is rendered first in the grid)
  2. After they are rendered, any auto-generated columns which do not already exist, are appended to the columns collection (and hence data for them is rendered after the manually defined columns data).

Automatic generation of columns is also supported on the server-side, as part of the ASP.NET MVC Grid wrapper logic, but I am going to talk about this in another post when covering the MVC Wrappers and data binding.

Binding to events

There are two main ways to bind to grid events:

1) By using bind/live and following the jQuery UI event namespacing conventions. For example if I would like to bind to the dataBinding event of the grid, I can do this in the following way:

$("#grid1").live("iggriddatabinding", <my handler>);

Note that if the widget is called iggrid, and the event – “dataBinding”, the event name parameter to pass to “live” or “delegate” or “on” – depending on your choice – is “iggriddatabinding”. If I wanted to bind to a columnSorted event, I would call:

$("#grid1").live("iggridsortingcolumnsorted", <my handler>);
 

Because igGridSorting is the name of the widget.

2) By specifying event handlers directly in the options when initializing the widget:

$("#grid1").igGrid({
    autoGenerateColumns: true,
    dataSource: gridData,
    dataBinding: function (event, args) { // do something; } 
});
 
Grid features

Grid features such as Paging, Sorting, GroupBy, - are defined in a features array, as part of the grid initialization options:

$("#grid1").igGrid({
    autoGenerateColumns: true,
    dataSource: gridData,
    features: [
        {
            name: 'GroupBy',
            type: 'local'
        }
    ]
});

The above code enables the GroupBy feature, and sets its operation to local – meaning that all feature functionality will happen locally even if the grid is bound to a remote service.

Note how the “name” comes into play. If the widget’s name is igGridPaging, or igGridGroupBy, the name you specify in the options, when declaring your grid, is without the “igGrid” in front, so it’s just “Paging”, or “GroupBy” or “Sorting” / “Filtering”, etc.

Some features can have column settings configured. Such features are sorting, filtering, groupBy, hiding, resizing, and others. Paging for instance doesn’t have column settings. The idea of the “columnSettings” array is to enable per-column customizations. For example here is some code which enables Sorting, but has sorting disabled for the first column:

$("#grid1").igGrid({
    autoGenerateColumns: true,
    dataSource: gridData,
    features: [
        {
            name: 'Sorting',
            type: 'local',
            columnSettings: [
                { columnKey: "ProductID", allowSorting: false }
            ]
        }
    ]
});

In order to use the feature API, you refer to the widget that gets instantiated on the element on which the grid is instantiated. Even though you declare features as part of the grid options, a full blown jQuery UI feature widget (such as igGridPaging) is created and instantiated separately, on the element on which we instantiate the grid. So to get a reference to the paging widget, you can do:

var pagingFeatureObject = $("#grid1").data("igGridPaging");

To use the feature API, for example to sort a column, you can do:

$("#grid1").igGridSorting("sortColumn", "ProductID", "desc");

Note that by default all features’ type is set to “remote”. So in case you are binding to local data, you may want to change this otherwise the functionality will not work. In the first SR for 2011.2 and the next one for 2011.1, we have made this more intelligent by keeping the default type to null, and then if it’s not explicitly set, we are inferring that based on the data source type (whether it’s local or remote). To clarify, by “type” I am referring to the feature operation type – remote vs. local.

Virtualization

Our grid supports a unique and innovative approach of boosting your application performance – that’s the so called “virtualization” which we have implemented a while ago and demonstrated with the 2011 January CTP.

Virtualization is all about giving end users the ability to interact (scroll) with thousands and even millions of data rows, without having the underlying control logic render that huge DOM and keep it in memory. Having virtualization doesn’t mean requesting anything from the server, as the end user scrolls. Everything is local – you load your data from your backend, and after the grid is bound, the grid renders as if all your records are rendered. What actually happens is that you have only as many records rendered, as the grid visible viewport can show. So if you have a 400px grid, and your average record height is 20, the grid will only add 20 table rows to the TBODY DOM. As the end users scrolls, the data in those table rows (TR elements) is updated; nothing is removed or appended in any way. This not only avoids memory leaks and speeds up scrolling performance dramatically, but also creates a “gapless” visual experience. This means that your end users will not see blank white spaces or loading indicators in the grid while they are scrolling. They will never “feel” that data is fetched or rendered remotely, behind the scenes.

There are many different approaches to implement virtualization, and we have chosen the described one mainly because of efficiency and visual UX trade-offs. At the moment virtualization is not supported for “hierarchical” features such as Outlook GroupBy and in the context of hierarchical controls such as the hierarchical grid, but we plan to add this as part of 12.1.

When virtualization is enabled, it can be both row and column virtualization. The most common form of grid virtualization is row virtualization. For this to work, you should set either virtualization to true, or rowVirtualization to true. The grid must have a fixed height defined in pixels, and in case you have data which spans multiple records, you also need to set the avgRowHeight option. You can easily calculate how many rows will be shown in the grid, by dividing the height by the avgRowHeight. This is also a neat way of controlling the records height without resorting to CSS.

It’s important to note that all grid features are transparent of virtualization, and work in the same way, no matter if virtualization is enabled or not. Including features such as selection – we’ve done our best to make sure all functionality is seamless both for you, and for your customers.

Sometimes you’d want to load hundreds of columns in the grid – whenever your business scenario requires it, in that case you can and should enable column virtualization. This can be done by either setting virtualization to true (which enables both row and column), or columnVirtualization to true. Note that when you have column virtualization enabled, the same rendering & performance concepts that I have already explained apply to groups of column cells, when you are scrolling horizontally. Column virtualization requires having columns of the same width (avgColumnWidth / defaultColumnWidth), and having a fixed grid width defined which is smaller than the total sum of all columns’ width. It also requires that, for best results, the column width sum of the visible columns is equal to the grid fixed width.

Stay Tuned!

In the next posts from this series, I am going to write about the Hierarchical Grid which we have just released in 2011.2, the MVC wrappers and LINQ data binding logic for both grids, as well as some detailed walkthroughs related to using various server-side backends with the grids, such as PHP and Ruby on Rails. I am also going to cover some very important and widely used application scenarios such as building a data-driven application from scratch, implementing a master-details template in the flat grid, and implementing real-time updates and conditional formatting. So stay tuned!