使用 ASP.NET Repeater 进行就地编辑
演示如何使用 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 生成任何类型的布局。
这里的两个关键事件是 OnItemCommand
和 OnItemDataBound
,稍后将介绍。
数据源
由于控件需要一个数据源才能绑定,我创建了一个非常简单的实体...
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
触发,这是此事件处理程序中的发送者对象。从中,您可以获取作为 LinkButton
的 Parent
对象的 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日