65.9K
CodeProject 正在变化。 阅读更多。
Home

可编辑的 GridView

starIconstarIcon
emptyStarIcon
starIcon
emptyStarIconemptyStarIcon

2.36/5 (4投票s)

2007年10月2日

CPOL

4分钟阅读

viewsIcon

53197

downloadIcon

650

一篇关于 GridView 扩展程序的文章,该扩展程序支持记录的就地编辑。

Screenshot - EditableGridView.gif

引言

由于我热衷于学习新事物,所以我想尝试一下控件扩展器。对于这个扩展器,我的灵感来自于一个GridView控件,它包含需要编辑的记录。

在经典的ASP.NET方式中,我们会将整个页面回发,获取返回的记录,但这次是在文本字段中输入数据进行编辑,再次将整个页面回发以应用更改,然后再次接收网格以获得一组集中的数据,不再有textbox。这种情况导致相同的数据在网络上传输两次,另外用于编辑的数据表单也会在网络上传输两次。

此扩展器的目标是让用户直接在网格中编辑数据,并且只将受影响的记录发送到服务器,从而节省大量带宽。

构建块

ASP.NET AJAX框架的设置方式允许我们为任何控件编写扩展器。由于框架是可扩展的,我们可以在编辑模式下创建自己的JavaScript对象。

此扩展器中使用的所有自定义对象都仅在JavaScript中实现。可编辑单元格是一个自定义对象(BM.Extenders.TableCell),它将HTML注入到其附加到的DOM对象中。

废话不多说,我将为您呈现代码。

BM.Extenders.GridView 亮点

GridView 扩展器在初始化时会挂钩到GridView 并注入一个按钮列。这些按钮在它们所在的行切换到编辑模式之前是隐藏的。

同样在初始化期间,会将一个事件处理程序附加到PageRequestManagerinitializeRequest。此事件处理程序会阻止在记录处于编辑模式时发生更新(计时器滴答、按钮点击等)。

此外,我们可以定义哪些列应该可编辑的索引,从而防止用户修改他们不应该触及的数据。我们还可以选择隐藏一些包含更新所需但对用户无用的值的列(ID、我们查询的where子句中使用的字段等)。

所有需要做的是设置一些属性,以便在需要时发出警报(保存成功、保存失败、项目已更改等),用于在数据库中实际更新的页面方法,以及——最后但同样重要的是——初始化我们的TableCell对象(见下文)。

扩展器的初始化函数(为简洁起见,属性访问器已被移除)

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');
    },
    ...

initializeRequest事件处理程序

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

当处于编辑模式时,我们仅取消请求。

BM.Extenders.TableCell 亮点

我们需要一种方法来知道我们正在编辑哪个单元格。单元格可以通过它所在的行和列来唯一标识。TableCell 对象有两个属性:“row”和“col”。在其初始化过程中,会将一个事件处理程序附加到click事件,并填充这些属性。

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

事件处理程序非常简单。它会传递一个指向eventElement的引用,通过它可以获取属性。首先要做的是检查这是我们要编辑的第一个单元格,是与上一个单元格在同一行的单元格,还是完全是另一行的单元格。供内部使用时,行、列和旧值存储在一个数组中。之后,添加textbox并显示按钮。

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
            }
        }
    }
}

变量“index”是一个数组,我向其中添加了一个Slide函数来移动项目并创建空间,以及一个Clear函数来清空数组。

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

基本上,当前项被添加到数组的末尾(push)并被清空。

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

编辑中调用的其他函数

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];
    }
}

发送编辑后的数据时,会调用send函数,并传递对该行的引用。

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);
}

首先,会移除textbox,然后可以将值(单元格文本)放入一个数组中。最后,会调用AJAX框架的Sys.Net.WebServiceProxy.invoke方法。

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";
    }
}

关于本地化和定期更新

要本地化文本属性(您应该用用户的语言与他们交流),我们可以利用资源文件并在扩展器的服务器标记中添加meta:resourcekey元素。这已经过测试,效果很好。

定期更新可以通过计时器等触发。由于GridView 及其扩展器必须位于同一个updatepanel 中(幸运的是),因此每次刷新updatepanel 时都会初始化扩展器。

可能的改进和未来添加

我已经有一些想法可以将其进一步发展。我可以添加对以下方面的支持:

  • 掩码textbox(一个流行的AJAX扩展器)
  • 为每列定义的正则表达式,以便在textbox的blur事件触发时,可以在用户端验证输入

历史

  • 2007年10月2日:初始版本
© . All rights reserved.