深入了解 CascadingDropDown






4.08/5 (6投票s)
本文档将帮助您了解 CascadingDropDown 的工作原理以及如何使用它。
引言
CascadingDropDownExtender
是 AjaxControlToolkit 中最受欢迎的扩展程序之一。但是,关于该控件的服务器端部分已经有了一个帮助文档,而关于(真正使其工作的)客户端部分的内容却几乎没有,这让开发者在需要进行一些高级的事件处理或自定义时感到困惑。
我认为,如果您了解它的工作原理,那么实现自己的功能会更容易。因此,在本文中,我将描述 CascadingDropDown
的工作原理,然后尝试回答一些常见问题。
简述服务器端
简而言之,对于不了解的人来说,CascadingDropDown
是一个扩展程序控件,用于根据在一个 DropDownList
中选择的值来填充另一个 DropDownList
中的数据。它通过 AJAX 使用 Web 方法获取所有必要的数据。现在,让我们更详细地考虑它的组织。
因此,CascadingDropDown
是 DropDownList
的一个扩展程序。作为目标控件,我们设置“依赖的”(或子)DropDownList
(即将被填充的那个)。要设置父控件的 ID,我们使用 ParentControlID
属性,顺便说一句,该属性不是必需的(即可以没有父 DropDownList
)。除了(TargetControlID
)之外,其他必需的属性是 Category
和 ServiceMethod
。ServiceMethod
包含 CascadingDropDown
使用的 Web 方法的名称,而 Category
则充当筛选器名称,并作为参数之一传递给 Web 方法。可选属性有:
PromptText
(在用户从DropDownList
中选择值之前显示的文本),PromptValue
(显示PromptText
时设置的值),EmptyText
(当DropDownList
没有数据可显示时显示的文本),EmptyValue
(显示EmptyText
时设置的值),LoadingText
(加载期间显示的文本),ServicePath
(Web 服务的路径;如果不声明,则使用PageMethod
),ContextKey
(用于存储任何附加信息的空间),UseContextKey
(指示是否将ContextKey
作为属性之一传递给 Web 方法),SelectedValue
(子DropDownList
的默认值)。
除了上面描述的属性之外,CascadingDropDown
还有几个辅助的静态方法可以在 Web 方法中使用,但稍后将讨论。CascadingDropDown
在服务器端所做的所有事情就是将上述属性的值收集到 脚本描述符中,并将其“发送”到派生自 Sys.UI.Behavior
(准确地说,是派生自 AjaxControlToolkit.BehaviorBase
)的相应客户端类。然后,所有主要过程都在客户端运行。
强大的客户端
在 initialize
方法中,会覆盖订阅到子 select
和父(如果定义了) select
DOM 元素的 change
事件的处理程序。此外,如果指定了父 select
,则父子 select
之间会建立一种联系:子 select
会接收一个指向父 select
的 CascadingDropDownParentControlID
字段,而父 select
会(如果还没有)获得一个名为 childDropDown
的数组字段,其中添加了指向子 select
的引用。除了所有这些之外,在初始化阶段,会从子 select
DOM 元素中移除所有选项,并向其添加 CascadingDropDownCategory
字段(该字段将包含 Category
属性的值),在父 select
中插入 PromptText
或 LoadingText
属性中的文本,并执行一些其他小的操作。此外,如果子 select
没有父,或者指定了父,并且在调用 initialize
时,其选定值不等于 PromptValue
或 EmptyValue
,那么子 select
将会被数据填充(也就是说,会执行与用户更改父 select
值相同的操作)。所以,现在是时候看看数据是如何填充的了。
一切都始于 _onParentChange
方法(注意下划线前缀 - 此方法不供公开使用)。如前所述,Web 方法会根据 UseContextKey
的值传递 2 或 3 个参数。这些参数是 knownCategoryValues
、category
和 contextKey
。所有这些参数都是字符串。逻辑上,_category
和 _contextKey
字段值被作为第二个和第三个参数传递。第一个参数的值更有趣。它的格式如下:如果子 select
没有父,则值为 null
。如果指定了父 select
,则会将它的 CascadingDropDownCategory
字段值和选定值添加到 knownCategoryValues
中。如果父 select
指定了 CascadingDropDownParentControlID
字段,则会获取父的父 select
,并重复上述操作,直到达到层次结构的末尾。新的键值对会添加到 knownCategoryValues
的开头,结果,knownCategoryValues
会变成这样:
'category1:value1;category2:value2;...'
这里,我想提醒大家注意一个不明显的细节。要使这一切正常工作,父 select
**必须**指定 CascadingDropDownCategory
字段。如果有一个 CascadingDropDownBehavior
客户端控件,并且该控件以父 select
作为目标控件,则会自动指定此字段。否则,您必须自己定义该字段。
但让我们回到 Web 方法。它不是通过生成的代理调用的,而是直接通过 Sys.Net.WebServiceProxy.invoke
方法调用的。收集完 Web 方法的所有参数后,会引发 populating
(Object
,Sys.CancelEventArgs
)客户端事件,如果未将 Sys.CancelEventArgs.cancel
属性在处理程序中设置为 false
,则会将请求发送到服务器。Web 方法应返回 CascadingDropDownNameValue
对象的数组。
如果 Web 方法调用失败,则错误信息(出于某个我不知道的原因)将作为选项放在子 select
中。如果 Web 方法调用成功,则子 select
的 选项集合将用接收到的值填充,并且如果其中一个接收到的对象具有 CascadingDropDownNameValue.value
字段等于 SelectedValue
,或者 CascadingDropDownNameValue.isDefaultValue == true
,则会选择其值。无论是否存在错误,最后都会引发 populated
(Object
,Sys.EventArgs
)客户端事件。好了,更新完成。
值得一提的是,在子 select
的选定值发生任何更改时,都会引发 SelectionChanged(Object, AjaxControlToolkit.CascadingDropDownSelectionChangedEventArgs)
客户端事件。
在撰写本文时,存在一个 bug。LoadingText
在 populating
事件引发之前设置,并且如果请求被取消,LoadingText
会保留在子 select
中,而不是 promptText
或旧选项,这可能会让用户感到困惑。
当我们了解更多时...
现在,我们对工作原理有了或多或少的了解,可以得出一些结论。
- 由于
CascadingDropDown
通过 Web 方法(Web 服务方法或PageMethod
)工作,因此它与服务器端或客户端的页面生命周期无关。因此,当CascadingDropDown
更新select
DOM 元素时,不会发生回发,不会激活 ASP.NET 验证器,不会引发生命周期事件,SelectedIndexChanged
等等也不会工作。此外,为了使CascadingDropDown
工作,您无需将其放入UpdatePanel
中——它反正也不会使用它。 - 由于
CascadingDropDown
通过 Web 方法工作,因此参数的顺序并不重要。重要的是 Web 方法的签名与参数的类型和**名称**匹配。也就是说,参数的名称**必须**与文档中给出的名称相同。同样,无论您使用的是 Web 服务方法还是PageMethod
,这都是无关紧要的。 CascadingDropDown
没有服务器端逻辑(不包括 Web 方法,因为它不是控件的一部分)。控件的服务器端部分所做的只是帮助我们简化客户端部分的创建。这就是为什么CascadingDropDownBehavior
可以独立于服务器端创建和使用,而且,更重要的是,完全不需要 ASP.NET 2.0。只需将必要的脚本添加到页面并使用 $create 方法即可。
Sys.Application.add_init(function() {
$create(
AjaxControlToolkit.CascadingDropDownBehavior,
{
"Category":"Model",
"ClientStateFieldID":"hiddenFieldClientStateId",
"ParentControlID":"parentDropDownList",
"PromptText":"Please select a model",
"ServiceMethod":"GetDropDownContents",
"ServicePath":"MyService.asmx",
"id":"exCascadingDropDown"
},
null,
null,
$get("childDropDownList")
);
});
我们在这里还没有考虑的是 ClientStateFieldID
属性。我们需要将某个隐藏字段的 ID 传递给此属性,以便 CascadingDropDownBehavior
可以使用此隐藏字段在回发期间存储选定的值(称为“ClientState”)。
- 与
UpdatePanel
不同,通过Sys.Net.WebServiceProxy.invoke
发送的请求是独立执行的。这就是为什么如果请求执行时间过长,我们可能会在客户端遇到并发问题。例如:用户在父select
中选择了一个值。此操作将发送第一个请求。在等待响应之前,用户在同一个父select
中选择了另一个值。此时,第二个请求被发送。假设执行第二个请求所需的时间远少于执行第一个请求所需的时间。结果,第二个请求的响应先于第一个请求返回。子select
已被填充,用户正准备在其中选择一些内容,此时,第一个请求的响应返回,并且select
被填充了无效的当前值。结果,子select
中的值与父select
中的选择不匹配。我不知道这是否可以被视为一个 bug。但是,我们必须自己解决这种情况。 - 为了进行客户端验证、控制请求优先级以及显示和隐藏动画 GIF 而不是“加载”消息,我们需要使用
populating
和populated
客户端事件。再次强调,为了清晰起见:我们必须订阅以子select
作为目标元素的CascadingDropDownBehavior
的事件,但这些事件将在用户更改父select
中的选择时引发。 - 由于需要指定
CascadingDropDownCategory
属性,因此(虽然不是必需的)最好使用CascadingDropDown
来填充顶部的DropDownList
,而不是经典的绑定。 CascadingDropDown
在 Web 窗体中的位置(因此,它们的初始化顺序)起着重要作用。填充父select
的CascadingDropDown
应该首先被初始化(并放置)。
最后,关于我一开始提到的那两个辅助方法。
ParseKnownCategoryValuesString
方法将knownCategoryValues
Web 方法参数从String
解析为StringDictionary
。这非常有用。QuerySimpleCascadingDropDownDocument
方法可以帮助您根据现有的类别/值StringDictionary
从数据集中检索CascadingDropDownNameValue
对象集合。我非常不喜欢数据集,所以我不用这个方法。但如果您喜欢它们,可以查看这个教程。