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

使用 ASP.NET Repeater 进行就地编辑

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.97/5 (24投票s)

2011年8月29日

CPOL

6分钟阅读

viewsIcon

133071

downloadIcon

4486

演示如何使用 ASP.NET Repeater 控件进行就地编辑和添加项目

引言

最近,有人在论坛中询问如何使用 ASP.NET Repeater 控件向其绑定的数据源添加新项目。由于文字有时不足以传达足够的理解,我编写了这个示例,演示如何在这种情况下将 Repeater 控件与就地编辑结合使用。

此示例包含两个版本;一个面向服务器的方法,以及一个使用 AJAX 的面向客户端的方法。

Repeater vs. Gridview

在提供就地编辑和添加行时,首先想到的通常是使用 ASP.NET GridView 控件。本文不讨论这两个控件之间的区别,也不讨论何时/如何选择一个而不是另一个,但快速查看差异会显示以下内容:

GridView Repeater
默认的表格布局 使用模板
具有选择/编辑/删除命令 必须手动添加
内置分页器支持 必须手动添加
列排序 必须手动添加

尽管这不是一个详尽的列表,但 GridView 似乎通过提供更多的内置功能而拥有明显的优势。然而,所有这些功能都是有代价的。GridView 是一个非常“重”的控件,它依赖于大量使用 ViewState 才能正常工作,在某些情况下会给页面增加过多的开销。这时 ASP.NET Repeater 控件就可以发挥优势。

服务器端方法

在第一个方法中,我将依靠传统的服务器端编码,使用 PostBacks 和数据绑定事件来提供功能。我将 Repeater 控件包装在 ASP.NET UpdatePanel 中,以减少页面刷新,但除此之外,只涉及几行客户端代码。

<asp:UpdatePanel runat="server">
    <ContentTemplate>
        <asp:Repeater runat="server" ID="Repeater1" 
        OnItemCommand="OnItemCommand" OnItemDataBound="OnItemDataBound">
            <HeaderTemplate>
                <table border="0" cellpadding="0" cellspacing="0">
                    <tr>
                        <th></th>
                        <th>First Name</th>
                        <th>Last Name</th>
                    </tr>
            </HeaderTemplate>
            <ItemTemplate>
                <tr>
                    <td>
                        <asp:ImageButton ID="Edit" 
                        ImageUrl="~/Images/EditDocument.png" 
                        runat="server" CommandName="edit" />
                        <asp:ImageButton ID="Delete" 
                        ImageUrl="~/Images/Delete_black_32x32.png" runat="server"
                            CommandName="delete" />
                    </td>
                    <td>
                        <asp:Label runat="server" 
                        ID="firstName"><%# Eval
                        ("FirstName") %></asp:Label>
                        <asp:PlaceHolder runat="server" 
                        ID="firstNameEditPlaceholder" />
                        <input type="hidden" runat="
                        server" id="firstNameHidden" />
                    </td>
                    <td>
                        <asp:Label runat="server" ID="
                        lastName"><%# Eval("LastName") %></asp:Label>
                        <asp:PlaceHolder runat="server" 
                        ID="lastNameEditPlaceholder" />
                        <input type="hidden" runat="
                        server" id="lastNameHidden" />
                    </td>
                </tr>
            </ItemTemplate>
            <FooterTemplate>
                <tr>
                    <td>
                        <asp:ImageButton ID="Delete" 
                        ImageUrl="~/Images/112_Plus_Blue_32x32_72.png" runat="server"
                            OnClick="OnAddRecord" />
                    </td>
                    <td><asp:TextBox runat="server" 
                    ID="NewFirstName" /></td>
                    <td><asp:TextBox runat="server" 
                    ID="NewLastName" /></td>
                </tr>
                </table>
            </FooterTemplate>
        </asp:Repeater>
    </ContentTemplate>
</asp:UpdatePanel>    

可以看出,这是一个非常简单的示例,足以演示这些技术,实际用法当然会有所不同。为了简单起见,使用了表格布局,但可以使用模板和 CSS 生成任何类型的布局。

这里的两个关键事件是 OnItemCommandOnItemDataBound,稍后将介绍。

数据源

由于控件需要一个数据源才能绑定,我创建了一个非常简单的实体...

 public class Contact
{
    public int ID { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

...并为这个示例创建了一个非常简单的数据访问层。

public class Data
{
    public Data()
    {
    }

    public int NextId
    {
        get
        {
            int id = 0;
            if(Contacts.Count != 0)
            {
                id = Contacts.Max(c => c.ID) + 1;
            }
            return id;
        }
    }

    public List<contact /> Contacts
    {
        get
        {
            if(HttpContext.Current.Session["contacts"] == null)
            {
                HttpContext.Current.Session["contacts"] = new List<contact />();
            }
            return HttpContext.Current.Session["contacts"] as List<contact />;
        }
    }
}

当然,在生产系统中,这会更复杂,并且很可能使用数据库作为数据存储。然而,出于演示目的,这将是足够的。

服务器端事件

如果控件没有可绑定的数据,则需要一种向数据源添加项目的方法。上面显示的代码包含一个 FooterTemplate,其中包含一个表格行,其中包含允许用户输入数据的控件和一个 LinkButton,用于触发 OnAddRecored 事件处理程序。

protected void OnAddRecord(object sender, EventArgs e)
{
    // Get the textboxes using the button as the starting point
    TextBox firstName = ((Control)sender).Parent.FindControl("NewFirstName") as TextBox;
    TextBox lastName = ((Control)sender).Parent.FindControl("NewLastName") as TextBox;

    //  No point in adding anything if empty
    if(!string.IsNullOrWhiteSpace(firstName.Text) || 
			!string.IsNullOrWhiteSpace(lastName.Text))
    {
        // Add a new Contact and rebind the repeater
        Data.Contacts.Add(new Contact() 
	{ ID = Data.NextId, FirstName = firstName.Text, LastName = lastName.Text });

        Repeater1.DataSource = Data.Contacts;
        Repeater1.DataBind();
    }
}

这里没什么太复杂的。事件由 LinkButton 触发,这是此事件处理程序中的发送者对象。从中,您可以获取作为 LinkButtonParent 对象的 RepeaterItem 并找到 TextBox 控件。然后,只需将其添加到数据源并重新绑定 Repeater 控件即可。在处理就地编辑时会稍微复杂一些。

OnItemCommand 事件处理 LinkButtons 的命令操作:

protected void OnItemCommand(object source, RepeaterCommandEventArgs e)
{
    if(e.CommandName == "delete")
    {
        Data.Contacts.RemoveAt(e.Item.ItemIndex);
    }
    else if(e.CommandName == "edit")
    {
        EditIndex = e.Item.ItemIndex;
    }
    else if(e.CommandName == "save")
    {
        HtmlInputHidden t = e.Item.FindControl("firstNameHidden") as HtmlInputHidden;
        Data.Contacts[e.Item.ItemIndex].FirstName = t.Value;
        t = e.Item.FindControl("lastNameHidden") as HtmlInputHidden;
        Data.Contacts[e.Item.ItemIndex].LastName = t.Value;
        EditIndex = -1;
    }

    Repeater1.DataSource = Data.Contacts;
    Repeater1.DataBind();
}

删除操作非常简单。只需根据索引从数据源中删除项目即可。对于编辑,我将一个由 ViewState 支持的属性 EditIndex 设置为要编辑的项目的索引,该索引将在控件数据绑定时使用。

尽管 TextBox 控件可以添加到 ItemTemplate 的每一行中,但使用可见性属性会产生过多的开销。编辑一次只对一个项目进行,因此渲染许多 TextBoxes,即使它们是不可见的并且可能永远不会被使用,也是一种过于繁重的方法。相反,控件将使用 PlaceHolder 控件动态添加到正在编辑的行中。

protected void OnItemDataBound(object sender, RepeaterItemEventArgs e)
{
    if(e.Item.ItemType == ListItemType.Item || 
		e.Item.ItemType == ListItemType.AlternatingItem)
    {
        if(e.Item.ItemIndex == EditIndex)
        {
            // Find the placeholder
            PlaceHolder p = e.Item.FindControl("firstNameEditPlaceholder") as PlaceHolder;

            // Create textBox and assign the current value of the data item
            TextBox t = new TextBox();
            t.ID = "firstNameEdit";
            t.Text = ((Contact)e.Item.DataItem).FirstName;

            // Add the textbox to the placeholder
            p.Controls.Add(t);

             // Get the existing label and hide it
            Label l = e.Item.FindControl("firstName") as Label;
            l.Visible = false;

            p = e.Item.FindControl("lastNameEditPlaceholder") as PlaceHolder;            
            t = new TextBox();
            t.ID = "lastNameEdit";
            t.Text = ((Contact)e.Item.DataItem).LastName;
            p.Controls.Add(t);

            l = e.Item.FindControl("lastName") as Label;
            l.Visible = false;

            // Make hidden fields visible
            HtmlInputHidden h = e.Item.FindControl("firstNameHidden") as HtmlInputHidden;
            h.Visible = true;
            h = e.Item.FindControl("lastNameHidden") as HtmlInputHidden;
            h.Visible = true;

            // Remove the edit button from display
            ImageButton b = e.Item.FindControl("Edit") as ImageButton;
            b.Visible = false;

            // Re use the delete button
            b = e.Item.FindControl("Delete") as ImageButton;
            b.CommandName = "save";
            b.OnClientClick = "OnSave(this)";
            b.ImageUrl = "~/Images/base_floppydisk_32.png";                    
        }
    }
} 

PlaceHolder 控件对于动态添加 TextBox 控件是必需的,因为虽然您可以检索 Label 控件并找到其父控件(在本例中是表格单元格),但您无法添加 Textbox,因为 ASP.NET 将该元素生成为 LiteralControl,它不支持添加子控件。添加 Textbox 并从数据源分配当前值后,通过设置 visibility=false 来隐藏 Label 控件,然后将 HtmlHiddenInput 字段设置为 visiblity=true,以便它们将被渲染并可用。最后一步是删除编辑按钮,并重新使用删除按钮来保存编辑完成后的项目。HtmlHiddenInput 的原因在下面解释。

保存编辑过的项目

使用服务器端处理保存就地编辑会稍微复杂一些。当点击保存按钮时,首先触发的事件是 ItemCommand。然而,由于编辑控件是在 ItemDataBound 事件中动态添加的,并且此事件尚未触发,因此它们不可用。同样,由于控件尚未绑定到任何数据,Label 控件是从 ViewState 重新构建的,因此它们不能用于临时存储。取而代之的是,HtmlHiddenInput 控件与基于 JQuery 的 JavaScript 一起使用,将值从编辑控件传输到隐藏字段。这是此示例中唯一的客户端代码。

function OnSave(obj)nSave(obj)
{
    // Find the row this button is in
    var tr = $(obj).closest("tr");
    // Get the value from the edit control
    var firstNameEdit = tr.find("[id*='firstNameEdit']").val();
    // assign value to hidden input
    tr.find("[id*='firstNameHidden']").val(firstNameEdit);

    var lastNameEdit = tr.find("[id*='lastNameEdit']").val();
    tr.find("[id*='lastNameHidden']").val(lastNameEdit);
}

客户端与 AJAX

在前面的示例中,ASP.NET 引擎处理了许多渲染和幕后处理,而客户端方法则必须手动实现。

首先要做的是在文档加载时挂接按钮事件。编辑和删除按钮使用了 live JQuery 方法,因此任何新添加的行都将自动实现事件。

 $(document).ready(function ()
{tion ()
{
    // Use live so when adding new records the events will
    // automatically be bounde
    $("[id*='edit']").live('click',OnEdit);
    $("[id*='delete']").live('click', OnDelete);
    $("[id*='add']").click(OnAdd);
});   

下一步是实现添加新联系人的功能。

function OnAdd()
{
    // Get the row this button is within
    var tr = $(this).closest("tr");
    // Get the first and last name controls in this row
    var firstName = tr.find("#newFirstName");
    var lastName = tr.find("#newLastName");

    // Create a new row and update the firstname and lastname elements
    // appropriately
    newRow = NewRow(tr);
    newRow.find("span[id='firstName']").text(firstName.val());
    newRow.find("span[id='lastName']").text(lastName.val());

    AddContact(firstName.val(), lastName.val())

    // Clear everything out to start again
    firstName.val("");
    lastName.val("");
}

这里,表示被点击的“添加”按钮的 this 变量用于查找其所属的表格行。从中,找到输入元素,并提取用户输入的文本。现在的诀窍是将新行插入到表格中。如果存在任何现有行,NewRow 将尝试克隆该行,否则必须创建 HTML。另一种方法是在标记中创建行但将其隐藏,然后将其用于克隆。

function NewRow(tr)NewRow(tr)
{
    // If only one sibling then create a new row
    // otherwise just clone an existing one
    if(tr.siblings().length != 1)
    {
        var clone = tr.prev().clone();
        tr.before(clone);
    }
    else
    {
        var newRow = "<tr id=''>" +
            "<td>" +
                "<image id='edit' src='Images/EditDocument.png' class='imgButton' />" +
                "<image id='delete' 
			src='Images/Delete_black_32x32.png' class='imgButton'/>" +
            "</td>" +
            "<td>" +
                "<span ID='firstName'></span>" +
            "</td>" +
            "<td>" +
                "<span ID='lastName'></span>" +
            "</td>" +
        "</tr>";
        tr.before(newRow);
    }

    return tr.prev();
}

在插入新行并更新用户输入的文本后,下一步是将新联系人添加到服务器上的数据存储。这通过 AJAX 调用页面上的 WebMethod 来实现。

function AddContact(firstName, lastName)
{
    var data = '{'
            + "\"firstName\":\"" + firstName + "\","
            + "\"lastName\":\"" + lastName + "\""
            + '}';
    $.ajax({
        type: "POST",
        url: "AjaxEdit.aspx/AddContact",
        data: data,
        contentType: "application/json",
        dataType: "json",
        error: OnAjaxError,
        success: OnAddContactSuccess
    });
}

function OnAddContactSuccess(data)
{
    var result = eval('(' + data.d + ')');
    // Assign id from newly added contact
    newRow.attr("id", result);
    newRow = null;
}

这里没有什么太复杂的,只是将数据打包并通过 AJAX 发送到方法。如果方法成功完成,则新添加联系人的 ID 将分配给新添加行的 id 属性,以便在编辑或删除操作期间可用。

编辑和保存

编辑功能类似于服务器端方法;内联插入输入控件并替换编辑和删除按钮。

function OnEdit()
{
    // Get the row this button is within
    var tr = $(this).closest("tr");
    // Get the first and last name controls in this row
    var firstName = tr.find("span[id='firstName']");
    var lastName = tr.find("span[id='lastName']");

    // Insert an input element before the labels
    // and set the value to the label text
    // Then hide the label
    firstName.before("<input id='firstNameEdit' 
	type='text' value='" + firstName.text() + "'/>").hide();
    lastName.before("<input id='lastNameEdit' 
	type='text' value='" + lastName.text() + "'/>").hide();

    // Hide the existing buttons and add a save button in there place
    tr.find("[id*='delete']").hide();
    tr.find("[id*='edit']").before("<img id='save' src='images/base_floppydisk_32.png' />")
        .hide();

    tr.find("[id*='save']").one('click', OnSave);
}

保存功能再次类似;提取值,更新标签,恢复按钮并更新服务器上的数据源。

function OnSave()
{
    // Get the row this button is within
    var tr = $(this).closest("tr");

    var firstName = tr.find("[id='firstNameEdit']");
    var lastName = tr.find("[id='lastNameEdit']");

    // Set the text of the labels from the input elements and show them
    tr.find("span[id='firstName']").text(firstName.val()).show();
    tr.find("span[id='lastName']").text(lastName.val()).show();

    // Remove the input elements
    firstName.remove();
    lastName.remove();

    // Show the buttons again and remove the save
    tr.find("[id*='delete']").show();
    tr.find("[id*='edit']").show();
    tr.find("[id*='save']").remove();

    // update the contact on the server
    UpdateContact( tr.attr("id"), firstName.val(), lastName.val())
}

结论

本示例并非旨在深入探讨 ASP.NET Repeater 控件的功能,但希望它能成功演示如何将其用于对数据源进行 CRUD 操作。

历史

  • 首次编辑:2011年8月29日
© . All rights reserved.