Mac type grid view control for
asp.net/java web applications
For one of my project, I made a very dynamic grid which is
totally HTML based and very fast while rendering data from server.
Features:
1. No
viewstate
2. Dynamic
grid which gets data on scroll using REST based WCF service in JSon
3. No
DOM modifications which makes the rendering very fast
4. Custom
control which can be integrated in any web application – Java / Asp.Net
(However I built it for Asp.Net)
5. Support
for very large record set.
6. Designer
friendly – As the DOM doesn’t change and the basic structure remains the same.
7. Highly
extensible to support any kind of data
Concept:
The concept comes from Apple applications and IPhone/IPad
where user is provided with very rich user interface which allows him to scroll
through the records very fast. Actually what happens is that the basic
structure of the grid remains the same (the table , tr, etc.) all that gets
modified is the content of the control. For example, if we talk about a link
button, only attributes that are modified are text, url. Rest remains the same.
This is what will make the grid to function very fast as you are actually not
modifying the DOM object from client but modifying some attributes which again
would give better performance.
On UI, the grid is divided into two parts –
1. Actual
grid which will show the actual content. This would basically be the table with
TR’s inside. The table will have overflow as false so that this grid doesn’t have
a scroll bar. The content of this portion will be modified on scroll which is
explained in the second part.
2. Second
Div which will have the scroll bar. The concept here would be to insert another
Div in this which will have height = Total number of records * height of each
TR. This would generate a scrollbar for user. The only thing that needs to be
handled now is the scroll event.
Implementation
(ASP.NET)
Basically this grid was designed for an ASP.NET project but
can eventually be used in java also as it basically works on the basic html
tags of HTML. I created a custom control here that would override the render
mechanism of a WebControl.
Basic Architecture
The grid basically is a table with multiple number of TR and
TD both getting derived from the user input. TD, user would give the column
specification for grid in its definition in aspx.
<cc1:MashableGrid ID="myGrid" runat="server" ItemCss="row1" AlternatingItemCss="row2"
DataKeyColumnName="KeyId"
Width="98.5%"
Rows="10"
Height="330"
DefaultSortExpression="KeyId"
ServiceName="/BusinessService/GetData"
HeaderCss="tableHeader" RequestDelay="250"
QStringColumnName="KeyId">
<Columns>
<Items>
<cc1:Column Width="10%" CssClass="boldFont
tab_coloum_border"
AllowSorting="true"
AllowSearching="true"
SortExpression="RoleName"
DataFieldName="Name"
DisplayProperty="IsDisplay"
DataType="System.String">
<Controls>
<asp:LinkButton
ID="lnkBtn"
Text="Name"
IsResourceKeyRequired="false"
EncryptedQStringColumnName="KeyId"
ToolTip="Name"
ClientClickFunction="EditName"
/>
</Controls>
</cc1:Column>
</Items>
</Columns>
</cc1:MashableGrid>
What basically goes at the back is that a json collection is
expected to the javascript code which basically replaces the control properties
on the page with the ones mentioned in the json collection.
Let’s call the first div (big one) as DivMain. This div will
have the main table which will actual data.
The div on the right is a composite grid with another grid
into it. Lets call it DivScroller and this is having another div with name
DivDummy.
DivMain has its overflow set to hidden and would never ever
show scroller. All it will show is a table of content.
DivScroller having another div in it has overflow set to
auto. This acts just as a container to another div which is the main thing that
plays in this control. The height of this div would be set dynamically to show
scroller in the parent div (DivScroller). Calculation is as follows
Height of DivDummy = total number of records * height of
each td (DivMain)
By this you would achieve the desired scroller. Now binding
the scroll event of this grid and displaying the data according to the scroll
position is the main task left.
This would be achieved by a little calculation on the basis
of scroll position
Start record to show = scrollPosition / height of each
td(DivMain)
Now only thing left in code is how to get the data and bind
it. So on scroll, there would be a service call that would ask for data from
server (according to start count and number of records that are displayed).
Next thing and the biggest task left is to map and replace
the content according to the data we got in json. If you saw it, we specified
some properties while binding the grid (in aspx). That will play the trick. Every
html control has max 3-4 attributes that are actually dynamic and are set. And
this is what we will replace. Below is the code that does the trick.
MashableTree.prototype.FillData = function (list, mashTree) {
if (list == undefined || list == null || list.length == 0) {
$('#' + this.actualGridId
+ ' td').each(function
() {
this.style.display = 'none';
});
$('#' + this.actualGridId
+ ' th').each(function
() {
this.style.display = 'none';
});
return;
}
var mapping;
var typeArr;
var types = '';
var type = '';
var dataType = '';
var strMappings = new
Array();
var map = new
Array();
var index = 0;
var td = null;
$('#' + this.actualGridId
+ ' td').each(function
() {
td =
$(this);
mapping = $(td).attr('mapping');
dataType = $(td).attr('DataType');
if (mapping != undefined) {
types = $(td).attr('type');
index = $(td).attr('index');
typeArr = types.split(',')
for (var t = 0; t
< typeArr.length; t++) {
type = typeArr[t];
switch (type) {
case 'key':
str = mapping.split(',');
for (var i = 0; i < str.length;
i++) {
if (str[i] != '') {
map =
str[i].split(':');
if (map[0] == 'key')
{
if (index < list.length) {
var id = parseInt(mashTree.GetDataFromList(mashTree,
list, parseInt(index), map[1]));
$(td).text(id.toString());
if (mashTree.SelectedFolderId > 0) {
if (mashTree.SelectedFolderId == id) {
$(td).parent().addClass('SelectedTD');
}
}
}
}
}
}
break;
case 'lnk':
str = mapping.split(',');
for
(var i = 0; i < str.length; i++) {
if (str[i] != '') {
map =
str[i].split(':');
if (map[0] == 'td_disp')
{
if (index
< list.length) {
var dispVal = mashTree.GetDataFromList(mashTree,
list, parseInt(index), map[1]);
if (!dispVal) {
$(td).children().hide();
break;
}
else {
if (mapping.indexOf('img')
< 0) {
$(td).children().show();
}
}
}
}
else {
if (mapping.indexOf('img')
< 0) {
$(td).children().show();
}
}
if (map[0] == 'lnk_text')
{
if (index >= list.length) {
$(td).children()[0].innerHTML = '';
}
else {
$(td).children()[0].innerHTML = mashTree.GetDataFromList(mashTree, list,
parseInt(index), map[1]); // list[index][map[1]];
}
}
if (map[0] == 'lnk_url')
{
if (index >= list.length) {
$(td).children()[t].href = '';
}
else {
if (map[1] != '') {
$(td).children()[t].href = mashTree.GetDataFromList(mashTree, list,
parseInt(index), map[1]); // list[index][map[1]];
}
else {
$(td).children()[t].href = '#';
}
}
}
if (map[0] == 'lnk_function')
{
if (map[1].length > 0) {
if (index >= list.length) {
$(td).children().hide();
}
else {
var tdHtml = $(td).html();
var functionHtml = '';
var indexStart = 0;
var indexEnd = 0;
var textToReplace = '';
if (tdHtml != '') {
indexStart = tdHtml.indexOf(map[1]);
indexEnd = tdHtml.indexOf(';',
indexStart);
textToReplace = tdHtml.substring(indexStart, indexEnd + 1);
if (mashTree.QStringColumnName != '') {
tdHtml = tdHtml.replace(textToReplace, map[1] + "('"
+ list[index][mashTree.QStringColumnName] + "');");
$(td).html(tdHtml);
}
}
}
}
}
}
}
break;
case 'lbl':
str = mapping.split(',');
for
(var i = 0; i < str.length; i++) {
if (str[i] != '') {
map =
str[i].split(':');
if (map[0] == 'td_disp')
{
if (index < list.length) {
var dispVal = mashTree.GetDataFromList(mashTree,
list, parseInt(index), map[1]);
if (!dispVal) {
$(td).html('');
$(td).children().hide();
break;
}
else {
$(td).children().show();
}
}
}
else {
$(td).children().show();
}
if (map[0] == 'lbl_text')
{
if (index >= list.length) {
$(td).text('');
}
else {
if (dataType == 'System.DateTime')
{
$(td).text(mashTree.ConvertToDate(mashTree.GetDataFromList(mashTree,
list, parseInt(index), map[1])));
}
else {
$(td).text(mashTree.GetDataFromList(mashTree, list, parseInt(index),
map[1]));
}
}
}
}
}
break;
case 'img':
if (index >= list.length) {
$(td).children().hide();
break;
}
else {
$(td).children().show();
}
str = mapping.split(',');
for (var i = 0; i < str.length;
i++) {
if (str[i] != '') {
map =
str[i].split(':');
if (map[0] == 'td_disp')
{
if (index < list.length) {
var dispVal = mashTree.GetDataFromList(mashTree,
list, parseInt(index), map[1]);
if (!dispVal) {
$(td).children().hide();
break;
}
else {
$(td).children().show();
}
}
}
else {
$(td).children().show();
}
if (map[0] == 'img_url')
{
if (map[1].length > 0) {
if (index >= list.length) {
$(td).children()[t].style.display = 'none';
}
else {
$(td).children()[t].style.display = '';
$(td).children()[t].src = list[index][map[1]];
}
}
}
if (map[0] == 'img_display')
{
if (map[1].length > 0) {
if (index >= list.length) {
$(td).children()[t].style.display = 'none';
}
else {
if (list[index][map[1]]) {
$(td).children()[t].style.display = '';
}
else {
$(td).children()[t].style.display = 'none';
}
}
}
}
if (map[0] == 'img_function')
{
if (map[1].length > 0) {
if (index >= list.length) {
$(td).children().hide();
}
else {
var tdHtml = $(td).html();
var functionHtml = '';
var indexStart = 0;
var indexEnd = 0;
var textToReplace = '';
if (tdHtml != '') {
indexStart = tdHtml.indexOf(map[1]);
indexEnd = tdHtml.indexOf(';',
indexStart);
textToReplace = tdHtml.substring(indexStart, indexEnd + 1);
if (mashTree.QStringColumnName != '') {
tdHtml = tdHtml.replace(textToReplace, map[1] + "('"
+ list[index][mashTree.QStringColumnName] + "');");
$(td).html(tdHtml);
}
}
}
}
}
}
}
break;
case 'chk':
str = mapping.split(',');
for (var i = 0; i < str.length;
i++) {
if (str[i] != '') {
map =
str[i].split(':');
if (map[0] == 'td_disp')
{
if (index < list.length) {
var dispVal = mashTree.GetDataFromList(mashTree,
list, parseInt(index), map[1]);
if (!dispVal) {
$(td).children().hide();
break;
}
else {
$(td).children().show();
}
}
}
else {
$(td).children().show();
}
if (map[0] == 'chk_checked')
{
if (index >= list.length) {
$(td).children()[t].style.display = 'none';
}
else {
$(td).children()[t].style.display
= '';
$(td).children()[t].checked = mashTree.GetDataFromList(mashTree, list,
parseInt(index), map[1]);
}
}
}
}
break;
default:
{
if (index >= list.length) {
$(td).children()[t].style.display
= 'none';
}
else {
$(td).children()[t].style.display = '';
}
break;
}
}
}
}
});
}
If
you want to use this grid, I can share the code. Drop a comment on this post
along with the email id.