Click here to Skip to main content
16,015,097 members
Articles / Web Development / HTML

EditableGridView

Rate me:
Please Sign up or sign in to vote.
2.36/5 (4 votes)
2 Oct 2007CPOL4 min read 52.8K   635   42   4
An article on a GridView extender which enables in-place editing of the records
Screenshot - EditableGridView.gif

Introduction

As I'm keen to learn new things, I thought I'd have a go at control extenders. For this extender, my inspiration was a GridView control which holds records that have to be edited.

In the classic ASP.NET way, we would have a postback of the entire page, get the record returned, but this time entered in textfields to edit the data, post the entire page again to apply the changes and then receive the grid once more to have a consolidated set of data and no more textboxes. This scenario gives us the same data travelling over the wire twice, plus the form to edit the data in goes over the wire twice as well.

The objective of this extender is to let the user edit the data directly in the grid and only send the affected record to the server, thus saving an awful lot of bandwidth.

Building Blocks

The ASP.NET AJAX Framework is set up in a way which allows us to write extenders for any control. Because the framework is extensible, we can create our own JavaScript objects to use when in edit mode.

All custom objects used in this extender are implemented in JavaScript only. The editable cells are a custom object (BM.Extenders.TableCell) which injects HTML into the DOM object it is attached to.

Without further ado, I present you the code.

BM.Extenders.GridView Highlights

The GridView extender hooks up to a GridView and injects a button column at initialization. The buttons are hidden until their row switches to edit mode.

Also during initialization, an eventhandler is attached to the initializeRequest of the PageRequestManager. This eventhandler prevents updates (timer ticks, button clicks, ...) from occurring while a record is in editing mode.

Furthermore, we can define the indexes of the columns which should be editable and as such prevent the user from altering data he should not touch. We can also choose to hide some columns which hold values that are required for the update, but not useful for the user (the ID, fields used in the where clause of our query, ...).

All that needs to be done is set some properties for alerts if needed (save successful, save failed, item changed, ...), the page method we use for the actual update in the database and – last but not least – initialize our TableCell objects (see below).

The extender's initialization function (property accessors were removed for brevity):

C#
BM.Extenders.GridViewBehavior.prototype = {
    initialize : function() {
        BM.Extenders.GridViewBehavior.callBaseMethod(this, 'initialize');
        //hook up to the initialization of the request
        Sys.WebForms.PageRequestManager.getInstance().add_initializeRequest(onPostBack); 
        var element = this.get_element();
        gridName = element.id;
        initialise();
    },

    dispose : function() {
        //remove the event handler
        Sys.WebForms.PageRequestManager.getInstance().remove_initializeRequest
								(onPostBack);
        BM.Extenders.GridViewBehavior.callBaseMethod(this, 'dispose');
    },
    ...

The initializeRequest eventHandler:

C#
function onPostBack(sender, args) 
{ 
    if(editing) 
    { 
        args.set_cancel(true); 
    } 
}

We simply cancel the request when in editing mode.

BM.Extenders.TableCell Highlights

We need to have a way to know which cell we are editing. A cell can be uniquely identified by the row and column it is in. The TableCell object has two properties: "row" and "col". During its initialization, an eventhandler is attached to the click event and the properties are populated.

C#
cell = $create(BM.Extenders.TableCell, {row: i, col: editableColumns[j]}, 
            {click: edit}, null, rows[i].cells[editableColumns[j]]);

The eventhandler is quite simple. It gets passed a reference to the eventElement, by which we can get hold of the properties. The first thing to do is check whether this is the first cell we're going to edit, a cell in the same row as the previous one or a cell in an entirely different row. For internal use, the row, column and old value are stored in an array. After this, the textbox is added and buttons are shown.

C#
function edit(eventElement)
{
    var row = eventElement.get_row();
    var col = eventElement.get_col();
    //first cell to edit
    if(!editing)
    {
        var cell = getCell(row, col);
        setEditing(row, col);
        makeEditable(cell);
    }else{
        //determine which cell was previously being edited
        if(evalEditingRow(row, col))
        {
            //another cell in the same row
            //remove textbox from previously edited cell
            var cell = getCell(index[0], index[1]);
            updateCell(cell);
            //push the items back to make room
            index.Slide(1,2);
            cell = getCell(row, col);
            setEditing(row, col);
            makeEditable(cell);
        }
        if(evalDifferentRow(row))
        {
            //click raised on different row
            var cell = getCell(index[0], index[1]);
            //remove textbox
            updateCell(cell);
            var changed = evalCellChanged();
            var proceed = false;
            //notification removed for brevity
            if(!changed || proceed)
            {
                resetValues();
                index.Clear();
                var cell = getCell(row, col);
                setEditing(row, col);
                makeEditable(cell);
            }else{
                //put textbox back
            }
        }
    }
}

The variable "index" is an array to which I added a function Slide to move the items back and create space and a function Clear which clears the array.

C#
Array.prototype.Slide = function(start, places)
{
    for(i=start; i<=places; i++)
    {
        this.push(index[i]);
        this[i] = "";
    }
}

Basically, the current item is added at the end of the array (push) and emptied.

C#
Array.prototype.Clear = function()
{
    this.length = 0;
}

Other functions called in edit:

C#
function getCell(row, col)
{
    return $get(gridName).rows[row].cells[col];
}

function makeEditable(cell)
{
    index[2] = cell.innerHTML;
    cell.innerHTML = "<input type=\"text\" value=\"" + index[2] + "\"/>";
    showButtons(index[0]);
}

function updateCell(cell)
{
    var text = cell.getElementsByTagName("input")[0].value;
    cell.innerHTML = text;
}

function setEditing(row, col)
{
    index[0] = row;
    index[1] = col;
    editing = true;
}

function resetValues()
{
    var row = index[0];
    for(i=1; i<index.length; i+=2)
    {
        var cell = getCell(row, index[i]);
        cell.innerHTML = index[i+1];
    }
}

When sending the edited data, the function send is called with a reference to the row.

C#
function send(row)
{
    //remove textbox
    var cell = getCell(index[0], index[1]);
    updateCell(cell);
    hideButtons();
    removeEditing();
    //load values from grid
    var row = $get(gridName).rows(row);
    var arguments = new Array();
    for(i=0; i<buttonColumn; i++)
    {
        arguments[i] = row.cells[i].innerHTML;
    }
    //Call server side function
    var path = window.location;
    Sys.Net.WebServiceProxy.invoke(path, serviceMethod, 
	false, {args:arguments}, OnCallSaveComplete, OnCallSaveError);
}

First, the textbox is removed after which the values (cell's text) can be put in an array. At last, a call to the AJAX frameworks Sys.Net.WebServiceProxy.invoke method is made.

Methods called by send:

C#
function removeEditing()
{
    index.Clear();
    editing = false;
}

function hideButtons()
{
    var rows = $get(gridName).rows;
    for(i=1; i<rows.length; i++)
    {
        rows[i].cells<buttoncolumn />.getElementsByTagName("span")[0].style.visibility = "hidden";
    }
}

About Localization and Periodic Updates

To localize the text properties (thou shall address your users in their language), we can make use of resource files and add a meta:resourcekey element to the server tag of the extender. This has been tested and worked very fine.

Periodic updates can be triggered by a timer for instance. Since the GridView and its extender have to be in the same updatepanel (luckily), the extender is initialized whenever the updatepanel is refreshed.

Possible Improvements and Future Additions

I have already got some ideas to take this even further. I could add support for:

  • masked textboxes (a popular AJAX extender)
  • regular expressions defined per column so the input can be validated on the user side when the textbox's blur event fires

History

  • 2nd October, 2007: Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Web Developer
Europe Europe
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralThe Gridview has an EditMode already Pin
leppie3-Oct-07 2:32
leppie3-Oct-07 2:32 
GeneralRe: The Gridview has an EditMode already Pin
Bart Meirens6-Oct-07 2:07
Bart Meirens6-Oct-07 2:07 
GeneralRe: The Gridview has an EditMode already Pin
rodrigo diniz9-Oct-07 0:46
rodrigo diniz9-Oct-07 0:46 
GeneralRe: The Gridview has an EditMode already Pin
Bart Meirens19-Oct-07 22:24
Bart Meirens19-Oct-07 22:24 
Hi Rodrigo,

I have finally been able to free some time to check this out.

The result of putting the GridView in an updatepanel and wiring up the edit, update and cancel events differs somewhat from my implementation.

First of all, a POST is performed for each action. Let me run you through it step by step.

1. The user clicks the edit button, a POST is sent to the server containing the command argument and the RowEditing event is fired. The response to the POST is the entire gridview with the textboxes added.

2. The user clicks the update button, a POST is sent to the server containing the command argument and all values from the textbox. The RowUpdating event is fired and the response is once more the entire gridview.

3. Similar things happen for the cancel event.

Secondly, a cell containing no value gets no textbox rendered by default. One would have to go through some custom code to get one there. In my implementation this does not matter, a textbox is rendered in any case.

Thirdly, data formatting is lost when switching to edit mode. If you have a field which displays the time part of a date, you get a textbox containing the date and the time.

As measuring is knowing, I compared the generated traffic for both implementations with 6 rows (load, edit, update).

GridView in an UpdatePanel:
1976 bytes in for the initial request; 57447 out.
1652 bytes in for the edit click; 3342 out.
1822 bytes in for the update click; 3003 out.
Total is 5450 bytes in; 63792 out

In my implementation, no POST is required until the user clicks save.

GridViewExtender:
3201 bytes in for the initial request; 66006 out
0 bytes in for the edit click; 0 bytes out.
576 bytes in for the update click; 375 bytes out.
Total is 3777 bytes in; 66381 out.

A note here is that the initial request requires more data as the extender script, as well as the Microsoft script with the extender framework has to be loaded. As the number of rows in the grid increases, the amount of bandwidth saved by my implementation will increase as the entire grid is not going back and forth with each click the user performs.

I think both implementations have their merit and putting an editable gridview in an updatepanel requires significantly less javascript but is less interactive.

Kind regards,

Bart

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.