日历归档 |
|
<< < 2024 - 11 > >> | Su | Mo | Tu | We | Th | Fr | Sa | | | | | | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 |
|
|
|
About Me |
|
|
ZhangSichu |
|
Male |
|
32 |
|
ZhangSichu@gmail.com |
|
ZhangSichu@hotmail.com |
|
ZhangSichu.com |
|
weibo.com/zhangsichu |
|
|
|
个人推荐 |
|
|
|
|
分类归档 |
|
|
|
|
My Friends |
|
|
|
|
实现一个多变的Link的全过程
|
在一个页面中有一个Link需要有不同表现形式和处理方式,这给Web开发带来一些不好解决的问题。在上一个JobXxx项目中就有一个类似的需求,在这里小结写出来。
描述问题: 要求从一个数据表中读取一组数据然后绘制一个表格,表格中的一个特定列根据取得的数据不同而不同。取得的数据表有多个列,其中的Column4,是Int型可能取值有0 1 2 3 。根据这个列内容的不同,需要显示的结果也不同。当值为0时,显示一行不可以点击的普通文字如: 已经处理完成。当为1时显示一个调用Javascript的Link如:你没有权限。点击后弹出一个设定的ModuleDialog,要用户输入信息,这个ModuleDialog关闭后可能引发Postback,也可能不引发PostBack。当值为2时显示一个PostBack的Link如:删除,需要执行类似删除本行的操作。当值为3时显示两个Postback的Link如修改 和提交,修改操作,需要UI上弹出一个ModuleDialog显示修改对话框,并修改数据表中本行的一个列中的值,表明本行在修改状态。提交操作,执行类似修改本表和其他表相关信息的操作。如下图:
真实的数据内容:
实际要显示的样子:
分析问题: 根据问题的描述,需要实现4种不同行为的Link。1.简单文字,不响应用户的点击。2.Javascript Link调用本地Javascript弹出权限内容的ModuleDialog。3.PostBack的Link并需要带回两个参数(1)用户的操作类型,确定是删除还是提交,(2)当前行的ID,确定用户操作的数据内容。4.Ajax+Javascript Link 用Javascript调用本地Javascript 弹出数据修改的ModuleDialog,让用户修改相关数据,Ajax Send回来修改本行的状态为正在修改。 问题 1只需要Render一行静态文字,问题2 和4都是Render固定的Javascript。这里最难实现的可能就是问题3动态Render出不同的PostBack Link,并且PostBack时要带回两个参数的问题。需要带回两个参数,就需要两个HiddenField,在PostBack之前先调用Javascript填写两个参数,然后PostBack。 求解问题:
第一回尝试——DataView 看到这个问题描述,我第一个想到的是使用DataView控件来做,因为需要实现的大部分内容和数据绑定有关。可以利用DataView的数据绑定来实现。托一个DataView上来就开始了Coding。当数据绑定完成,数据基本上显示出来了。开始写动态RenderLink部分的代码时我发现无从下手了,找不到一个合适的机会去修改DataView数据绑定后的数据内容。挂上DataView的RowCreated事件和RowDataBound事件虽然都可以拿到e.Row.DataItem当前行的所有信息,但数据内容都是Readonly的。因为已经DataBinding了,所以Readonly不能改了。看来只有在DataBinding之前下手,在数据绑定前先修改数据源然后再绑定。在取得数据后,再遍历修改一次数据修改成想要显示的内容。可是Column4的数据类型是Int的不能修改为一串Javascript或html。只好先把数据表中除Column4的其它的内容Copy到一个新的DataTable然后重填Column4的列类型和数据。然后数据绑定。这里把Html和Javascript当成数据写入Column4,对页面的HtmlDecode有一定影响,不过可以解决。但是从头到尾看这个方法都是很别扭。 这个方法的好处:利用了DataView的DataBinding,可以直接用DataView的AutoGenerateEditColumn PageSize等属性,实现Row的强大功能。 这个方法的坏处:这是个比较别扭的做法。首先,重新Copy并修改DataTable是一个非常低效的做法。当大数据时可能会出现超时的问题。可以把这种重新填数据的工作放在DA层直接写在SQL中可能会提高效率,不过这种做法,严重违反了分层的原则,把UI控制和业务逻辑直接写入了DA层。其次,Javascript Html要写入到行的内容中。非常不易维护。(本文事例代码中BuildTestData. BuildData()方法模拟BR层向UI层提供数据的方法。)
第二回尝试——DataList 由于DataView不能在DataBinding后修改数据内容,打算用DataList试试看看是否有办法解决问题。拖上一个DataList,并为这个DataList设计一个ItemTemplate,配置一下DataBinding的DataSource,这个DataList基本上就可以工作了,DataList显示了所有的数据信息和刚才的DataView的显示结果差不多。修改Column4的显示方法,自己动态Render可以实现了需要的显示结果。 <asp:DataList ID="DataList1" runat="server" CellPadding="4" DataSourceID="ObjectDataSource1" ForeColor="#333333" BorderColor="PaleVioletRed" BorderWidth="1px"> <FooterStyle BackColor="#1C5E55" Font-Bold="True" ForeColor="White" /> <SelectedItemStyle BackColor="#C5BBAF" Font-Bold="True" ForeColor="#333333" /> <AlternatingItemStyle BackColor="White" /> <ItemStyle BackColor="#E3EAEB" /> <HeaderStyle BackColor="#1C5E55" Font-Bold="True" ForeColor="White" /> <ItemTemplate> <table> <tr> <td width="150px"> <%# DataBinder.Eval(Container.DataItem, "Column0")%> </td> <td width="150px"> <%# DataBinder.Eval(Container.DataItem, "Column1")%> </td> <td width="150px"> <%# DataBinder.Eval(Container.DataItem, "Column2")%> </td> <td width="150px"> <%# DataBinder.Eval(Container.DataItem, "Column3") %> </td> <td width="100px"> <%# DrawFourRow(Container.DataItem)%> </td> <td width="150px"> <%# DataBinder.Eval(Container.DataItem, "Column5")%> </td> </tr> </table> </ItemTemplate> </asp:DataList> 是这个DrawFourRow给出了一个重新定义Render的口子。DrawFour: protected string DrawFourRow(object dataItem) { DataRowView row = dataItem as DataRowView; if (null == row) { return string.Empty; } int? i = row[4] as int?; if(!i.HasValue) { return string.Empty; } switch (i.Value) { case 0: return "已经处理完成"; case 1: return "<a href=\"#\" onclick=\"" + string.Format(JavascriptNoAuthorizationFunction,row[0].ToString()) + JavascriptReturnFalse + "\">你没有权限</a>"; case 2: return "<a href=\"#\" onclick=\"" + string.Format(JavascriptAssignParameterFunction, ViewDeleteLinkMark, row[0].ToString()) + JavascriptSubmitFunction + "\">删除</a>"; case 3: return "<a href=\"#\" onclick=\"" + string.Format(JavascriptModifyFunction, row[0].ToString()) + JavascriptReturnFalse + "\">修改</a> <a href=\"#\" onclick=\"" + string.Format(JavascriptAssignParameterFunction, ViewApplyLinkMark, row[0].ToString()) + JavascriptSubmitFunction + "\">提交</a>"; default: return string.Empty; } } 为了配合DrawFour 需要一些Javascript,和两个HiddenFiled来记录操作类型和当前行信息。(详细内容在事例代码中) 这个方法的好处:从性能上比用DataView要好得多,一次Render解决问题。给自己定义Render留出了很大的空间。 这个方法的坏处:需要为每一个可以PostBack的行为,在PostBack之前调用Javascript设置一个与自己操作类型独一无二的 ProcessType,以保证PostBack后可以找到指定的处理方法。所以当界面上可以PostBack的控件多起来的时候,这不是一个好做法。同时需要在客户端写一个Javascript PostBack的方法,调用Forms[0].submit();这种做法有点野蛮,不够美观。
第三回尝试—— IPostBackEventHandler (去掉那两个HiddenField) 在当前的Page会提供this.Page.ClientScript.GetPostBackClientHyperlink这样一个方法。 ClientScriptManager.GetPostBackClientHyperlink (Control, String) ClientScriptManager.GetPostBackClientHyperlink (Control, String, Boolean) GetPostBackClientHyperlink会产生javascript:__doPostBack(para1,para2)这样一个客户端的Javascript 来PostBack,当客户端没有PostBack对应的__doPostBack方法时会添加一个客户端PostBack的Javascript方法。 <script type="text/javascript"> <!-- var theForm = document.forms[form1]; if (!theForm) { theForm = document.form1; } function __doPostBack(eventTarget, eventArgument) { if (!theForm.onsubmit || (theForm.onsubmit() != false)) { theForm.__EVENTTARGET.value = eventTarget; theForm.__EVENTARGUMENT.value = eventArgument; theForm.submit(); } } // --> </script> 观察这个方法,发现服务器端会通过__EVENTTARGET和__EVENTARGUMENT这两个Asp.Net引擎自己的HiddenField来传递PostBack的参数,__EVENTTARGET来确定Raise的Event和回调的方法,__EVENTARGUMENT来确定argument。是否可以利用这两个东东来代替原来自己的添加的HiddenField并完成任务。 经过测试和查看MSDN发现,GetPostBackClientHyperlink送入的两个参数一个是Control,一个是String参数。被Render成客户端的Javascript后, __doPostBack的前一个参数是Control的ClientID,后一个是自己送的String参数。经过几次尝试用一个LinkButton,并重写Page的RaisePostBackEvent方法实现了去掉那两个HiddenField的想法。 (1) 先加一个可以触发PostBack的Asp Server端的Control,设置这个Control的Visible为False,这个Control没有真正的界面用处,只是告诉Asp引擎有一个PostBack的Control: <asp:LinkButton ID="helpButton" runat="server" Visible="false" OnClick="helpButton_Click">LinkButton</asp:LinkButton> (2) 在要Post的Link处调用GetPostBackClientHyperlink方法写入调用Postback要Render的客户端代码: HyperLink link = container.FindControl("hlnkFourColumn") as HyperLink;if (link != null){ link.Text = "删除"; link.NavigateUrl = this.Page.ClientScript.GetPostBackClientHyperlink(helpButton, ViewDeleteLinkMark + ParameterSeparator + row[0].ToString()); ; link.Visible = true; } 第二个参数写想要传回来的参数。 (3) Override Page的RaisePostBackEvent 方法 base.RaisePostBackEvent(sourceControl, eventArgument); //Now the eventArgument will be the director. WebControl control = sourceControl as WebControl; if (control != null && control == this.helpButton) { if (!string.IsNullOrEmpty(eventArgument)) { string[] para = eventArgument.Split(new string[] { ParameterSeparator }, StringSplitOptions.RemoveEmptyEntries); if (para[0] == ViewDeleteLinkMark) { ViewDelete_Click(para[1]); } if (para[0] == ViewApplyLinkMark) { ViewApply_Click(para[1]); } } } 拆解参数,指定调用方法。
这个方法的好处:去掉了自己添加的HiddenField,不用维护HiddenField,也不用担心界面上有再多的PostBack控件。 这个方法的坏处:需要添加一个占位子的不显示的可以PostBack的Server端控件。
第四回尝试——Page + IPostBackEventHandler (去掉那个占位子的Server端控件) 用HttpLook观察上一种方法的Request的内容,在名值串中__EVENTTARGET,__EVENTARGUMENT中都有值,所以可以直接在PageLoad中使用this.Request["__EVENTTARGET"],拿到值。所以想到了让page直接来当这个占位子的Server端Control,所以代码变为了 link.NavigateUrl = this.Page.ClientScript.GetPostBackClientHyperlink(this, ViewDeleteLinkMark + ParameterSeparator + row[0].ToString()); 同时Page 继承IPostBackEventHandler这个接口,实现public void RaisePostBackEvent(string eventArgument)方法: public void RaisePostBackEvent(string eventArgument) { if (!string.IsNullOrEmpty(eventArgument)) { string[] para = eventArgument.Split(new string[] { ParameterSeparator }, StringSplitOptions.RemoveEmptyEntries); if (para[0] == ViewDeleteLinkMark) { ViewDelete_Click(para[1]); } if (para[0] == ViewApplyLinkMark) { ViewApply_Click(para[1]); } } } 最终实现要求。通过上面的一次次变化,可以抽出一个种Link可以带回一个参数并且不用HiddenField: public delegate void BackLinkWithParameterEventHandler(object sender, BackLinkWithParameterEventArgs e); public class BackLinkWithParameterEventArgs : EventArgs { public string PostBackParameter; public BackLinkWithParameterEventArgs(string eventArgument) { this.PostBackParameter = eventArgument; } }
/// <summary> /// Summary description for T5BackLinkWithParameter /// </summary> public class T5BackLinkWithParameter : HtmlAnchor { public bool IsNeedPostBack; public event BackLinkWithParameterEventHandler ClickPostBack; public string PostBackParameter;
public T5BackLinkWithParameter() {
}
protected override void RenderAttributes(HtmlTextWriter writer) { if (IsNeedPostBack) { this.HRef = this.Page.ClientScript.GetPostBackClientHyperlink(this, this.PostBackParameter); } base.RenderAttributes(writer); }
protected override void RaisePostBackEvent(string eventArgument) { base.RaisePostBackEvent(eventArgument); this.OnClickPostBack(eventArgument); }
private void OnClickPostBack(string eventArgument) { if (this.ClickPostBack != null) ClickPostBack(this, new BackLinkWithParameterEventArgs(eventArgument)); } } 使用这个Control的代码: protected void Page_Load(object sender, EventArgs e) { T5BackLinkWithParameter link = new T5BackLinkWithParameter(); link.IsNeedPostBack = true; link.PostBackParameter = "Test"; link.ClickPostBack += new BackLinkWithParameterEventHandler(link_ClickPostBack); link.InnerText = "Test"; this.pHolder.Controls.Add(link); }
void link_ClickPostBack(object sender, BackLinkWithParameterEventArgs e) { this.Title = e.PostBackParameter; }
详细内容在示例代码中。 File: Click to Download
|
|
|
|
|
|