如何知道哪个 HTML 对象被点击了






3.27/5 (5投票s)
本文介绍了如何添加 JavaScript 和 VB 代码,通过模拟 AutoPostBack 来检测 ASP.NET 页面中哪个 HTML 元素被点击了。
引言
我的一款程序需要可视化无法通过数据库查询返回的数据。我使用了一个 Table
来显示它。后来,客户希望能够点击单元格进行标记,并将标记存储在数据库中。TableCell
没有服务器端点击事件,所以我不得不另寻他法。
我正在寻找的解决方案需要满足以下要求:
- 它必须触发一个服务器端事件,并且能够提供被点击的
TableCell
。 - 如果将来添加 AutoPostBack 控件,也必须能够正常工作,而无需修改代码。
(注意:本文提出的解决方案可以用于**任何** HTML 元素,而不仅仅是 TableCell
。)
我尝试过但未成功的方法
我的第一个尝试是使用一个表单,其中包含:
- 三个 HTML 元素,用户必须点击它们。
- 一个标签,用于显示被点击的 HTML 元素。
- 一个隐藏字段,用于传递一个值,该值指示哪个 HTML 元素被点击了,以及
- 一个提交按钮。
在三个 HTML 元素上,我添加了 JavaScript 代码,该代码会将一个值写入隐藏字段,然后模拟点击提交按钮。这样做是有效的,但有一个缺点:提交按钮必须可见。如果我将其设为不可见,就会发生 JavaScript 错误,因为 ASP.NET 没有将不可见的提交按钮包含在生成的 HTML 中,因此 JavaScript 代码找不到它。
然后我阅读了 Dave Hurt 的文章 DataGrid 中的可点击行。他使用了 ASP.NET 生成的 __doPostBack
函数来检测哪个 DataGrid
行被点击了。这个想法是好的,但它只在网页包含可见的 AutoPostBack 控件(TextBox
、CheckBox
或 DropDownList
且 AutoPostBack
属性设置为 true)时才有效。我的页面上没有,也不想要可见的 AutoPostBack 控件。
Postback 工作原理
在我展示我尝试过且有效的方法之前,了解 AutoPostBack 的工作原理非常重要。看看当存在 AutoPostBack 控件时生成的 HTML 代码:
<form name="Form1" method="post" action="WebForm1.aspx" id="Form1">
<input type="hidden" name="__EVENTTARGET" value="" />
<input type="hidden" name="__EVENTARGUMENT" value="" />
<input type="hidden" name="__VIEWSTATE" value="dDwtODM1(...)+sg==" />
<script language="javascript" type="text/javascript">
<!--
function __doPostBack(eventTarget, eventArgument) {
var theform;
if (window.navigator.appName.toLowerCase().indexOf("microsoft") > -1) {
theform = document.Form1;
}
else {
theform = document.forms["Form1"];
}
theform.__EVENTTARGET.value = eventTarget.split("$").join(":");
theform.__EVENTARGUMENT.value = eventArgument;
theform.submit();
}
// -->
</script>
(...)
<input name="TextBox1" type="text"
onchange="__doPostBack('TextBox1','')"
language="javascript" id="TextBox1" /></P>
</form>
有几点需要注意:
- ASP.NET 添加了两个隐藏的
input
控件:__EVENTTARGET
和__EVENTARGUMENT
。 - 它添加了一个 JavaScript 函数
__doPostBack
。 - 它添加了 JavaScript 来处理客户端的
onChange
事件,该事件会调用__doPostBack
。
基本思路
我们问题的答案必须同时模拟这种行为,并**使其能够**模拟这种行为。
Postback 使用 __doPostBack
函数以及隐藏的 INPUT
控件 __EVENTTARGET
和 __EVENTARGUMENTS
。这些控件只有在 WebForm 包含 AutoPostBack 控件时才会生成。如果没有,则必须创建 __doPostBack
方法和两个隐藏的 INPUT
控件。
为了知道元素是否被点击,WebForm 必须包含一个按钮。可点击的 HTML 元素必须像点击该按钮一样进行 postback。然后我们可以在服务器端处理该按钮的 Click
事件。为了知道**哪个** HTML 元素被点击了,我们在 WebForm 中添加一个服务器端的隐藏 INPUT
(runat="server"
)。当 HTML 元素被点击时,它可以将唯一标识它的值写入这个隐藏 INPUT
。当我们在服务器端处理该按钮的 Click
事件时,隐藏 INPUT
的值将标识被点击的 HTML 元素。
解决方案
WebForm 必须包含:
- 一个 JavaScript 函数
elementClick(value)
,其中value
是一个唯一标识被点击 HTML 元素的字符串。此函数会将value
写入myHidden
(见下文),并模拟点击btnSubmit
(见下文)。 - 三个 HTML 元素,我们想知道它们是否被点击。每个元素都必须在被点击时(客户端)调用
elementClick
并传递一个唯一的值。在我的示例中,HTML 元素是带有文本“One”、“Two”和“Three”的TableCell
。它们将调用elementClick
并传递值“1”、“2”和“3”。 - 一个
ASP:Label
,名为“lblResult
”,用于显示被点击的 HTML 元素。 - 一个服务器端的隐藏
INPUT
(runat="server"
),名为“myHidden
”,我们将用它将值传递到服务器。 - 一个隐藏的
ASP:Button
,名为“btnSubmit
”,用于捕获服务器端事件。
VB.NET: HasPostBacks(Form)
如前所述,如果 WebForm 中没有 AutoPostBack 控件,则必须创建隐藏的 INPUT
控件 __EVENTTARGET
和 __EVENTARGUMENT
。HasPostBacks
会检查 WebForm,看它是否能找到任何 AutoPostBack
属性设置为 True 的控件。它会遍历表单中的所有控件,如果找到一个自动 postback 的控件就会停止。
框架中包含三个具有 AutoPostBack
属性的控件:CheckBox
、TextBox
、DropDownList
。但是,您也可以创建自己的自动 postback 控件,它不必是 CheckBox
、TextBox
或 DropDownList
。因此,检查控件是否为 CheckBox
、TextBox
或 DropDownList
,然后检查 AutoPostBack
属性是否设置,并不是一种稳健的检查方法。当我的解决方案不起作用时,我只想到了一种情况:当一个用户控件自动 postback,但没有暴露 AutoPostBack
属性时。为了捕获所有具有 AutoPostBack
属性的控件,我只是查询该属性。如果由于该控件没有该属性而生成错误,显然它不会自动 postback。
Protected Function HasPostBacks(ByVal Frm As HtmlForm) As Boolean
Dim Ctrls As ControlCollection = Frm.Controls
Dim i As Integer = 0
Dim Found As Boolean = False
While i < Ctrls.Count And Not Found
Dim Ctrl As Object = Ctrls(i)
Try
Found = Ctrl.AutoPostBack
Catch Ex As Exception
Found = False
End Try
i += 1
End While
Return Found
End Function
VB.NET: EnsurePostBack()
Private Sub Page_Load(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles MyBase.Load
'Put user code to initialize the page here
EnsurePostBack()
If Not Me.IsPostBack Then
Table1.Rows(0).Cells(0).Attributes.Add("onClick", "elementClick('1')")
Table1.Rows(0).Cells(1).Attributes.Add("onClick", "elementClick('2')")
Table1.Rows(0).Cells(2).Attributes.Add("onClick", "elementClick('3')")
TextBox1.Text = IIf(TextBox1.AutoPostBack, "PostBack = True", "PostBack = False")
End If
End Sub
如果不存在 AutoPostBack 控件,EnsurePostBack
将添加隐藏的 INPUT
控件 __EVENTTARGET
和 __EVENTARGUMENTS
。首先,它会尝试找到 Form
对象。然后,它使用 HasPostBacks
来确定是否必须添加隐藏的 INPUT
控件。如果是,它会创建两个 HtmlInputHidden
对象,设置它们的属性,并将它们添加到 Form
中。
Protected Sub EnsurePostBack()
Dim Frm As Control = Me.FindControl("Form1")
Dim HPB As Boolean = HasPostBacks(Frm)
If Not HPB Then
Dim EventTarget As HtmlInputHidden = New HtmlInputHidden()
Dim EventArguments As HtmlInputHidden = New HtmlInputHidden()
EventTarget.ID = "__EVENTTARGET"
EventTarget.Name = "__EVENTTARGET"
EventArguments.ID = "__EVENTARGUMENT"
EventArguments.Name = "__EVENTARGUMENT"
Frm.Controls.Add(EventTarget)
Frm.Controls.Add(EventArguments)
End If
End Sub
VB.NET: Page_Load
每次页面加载时,都必须调用 EnsurePostBack
。因为这些控件不属于 WebForm 的一部分,它们不会像 WebForm 中的控件那样自动重新创建。
在我的例子中,我有一个 Table
,我想知道其中的哪些单元格被点击了。所以在我的示例中,我有一个 Table
,其中包含一个 TableRow
和三个 TableCell
。因为设计器不允许向 TableCell
添加属性,所以我是在 Page.Load
事件中添加这些属性的。因为 TableCell
最初是 WebForm 的一部分,所以每次加载时都无需将 TableCell
添加到 WebForm。而且由于 TableCell
是持久的,所以 TableCell
的属性也是持久的。属性只需要添加一次。“If Not Me.IsPostBack Then
”确保这一点只发生一次。
属性名称是“onClick
”,因为我们想捕获客户端的 onClick
事件。参数的值是对 JavaScript 函数 elementClick
的调用,其值为“1”、“2”或“3”。
Private Sub Page_Load(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles MyBase.Load
'Put user code to initialize the page here
EnsurePostBack()
If Not Me.IsPostBack Then
Table1.Rows(0).Cells(0).Attributes.Add("onClick", "elementClick('1')")
Table1.Rows(0).Cells(1).Attributes.Add("onClick", "elementClick('2')")
Table1.Rows(0).Cells(2).Attributes.Add("onClick", "elementClick('3')")
TextBox1.Text = IIf(TextBox1.AutoPostBack, "PostBack = True", "PostBack = False")
End If
End Sub
JavaScript: myPostBack(eventTarget)
只有当存在 __doPostBack
方法时,才可能进行 postback。这仅在 WebForm 包含 AutoPostBack 控件时生成。为了仍然能够进行 postback,必须有一个函数执行与 __doPostBack
相同的功能,但名称不同。我称之为 myPostBack
。由于它永远不会 postback 事件参数,所以我修改了代码:
function myPostBack(eventTarget) {
var eventArgument;
eventArgument = '';
var theform;
if (window.navigator.appName.toLowerCase().indexOf("microsoft") > -1) {
theform = document.Form1;
}
else {
theform = document.forms["Form1"];
}
theform.__EVENTTARGET.value = eventTarget.split("$").join(":");
theform.__EVENTARGUMENT.value = eventArgument;
theform.submit();
}
JavaScript: elementClick(value)
此方法将由可点击的 HTML 元素调用。我使用微软的方式来获取表单。如果我获取到表单,我就可以访问 myHidden
控件,该控件将被赋值为传递给此方法的 `value`。(当然,也可以使用 `document.getItemByID` 方法,但我认为这样更简洁。)然后,将调用 myPostBack
,并将事件目标设置为 'btnSubmit
',以便在服务器端触发 btnSubmit.Click
事件。
function elementClick(value) {
var theform;
if (window.navigator.appName.toLowerCase().indexOf("microsoft") > -1) {
theform = document.Form1;
}
else {
theform = document.forms["Form1"];
}
var myHddn;
myHddn = theform.myHidden;
myHddn.value = value;
myPostBack('btnSubmit');
}
最后的寄语
我想感谢 Dave Hurt 的文章 DataGrid 中的可点击行。它帮助我找到了解决问题的方案。希望我的例子也能帮助您找到解决方案。
(如果您想尝试该示例,请启动 Visual Studio 并创建一个名为“HTMLElementClick”的新项目。下载源代码并将其解压到项目目录中,覆盖 Visual Studio 创建的文件。)