![]() |
Web Development »
Ajax and Atlas »
General
Intermediate
License: The Code Project Open License (CPOL)
EditableGridViewBy Bart MeirensAn article on a GridView extender which enables in-place editing of the records |
C# (C# 2.0), Javascript, XML, HTML, .NET (.NET 2.0), Ajax, Dev
|
|
Advanced Search |
|
|
|
||||||||||||||||
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.
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.
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):
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:
function onPostBack(sender, args)
{
if(editing)
{
args.set_cancel(true);
}
}
We simply cancel the request when in editing mode.
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.
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.
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.
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.
Array.prototype.Clear = function()
{
this.length = 0;
}
Other functions called in edit:
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.
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:
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";
}
}
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.
I have already got some ideas to take this even further. I could add support for:
textboxes (a popular AJAX extender) textbox's blur event fires | You must Sign In to use this message board. | |||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||
General
News
Question
Answer
Joke
Rant
Admin
|
PermaLink |
Privacy |
Terms of Use
Last Updated: 2 Oct 2007 Editor: Deeksha Shenoy |
Copyright 2007 by Bart Meirens Everything else Copyright © CodeProject, 1999-2009 Web17 | Advertise on the Code Project |