AJAX DropDownList






4.63/5 (34投票s)
2005年6月6日
10分钟阅读

787396

12958
自定义下拉列表,它利用 AJAX 填充内容,并实现观察者模式以参与依赖下拉列表链。

引言
AJAX(Asynchronous JavaScript and XML,异步 JavaScript 和 XML)已变得非常流行,这要归功于 Google Suggest。AJAX 使得构建更具响应性和交互性的 Web 应用程序成为可能,让它们更接近 Windows 窗体应用程序。Web 开发者们起初都很高兴。他们有了新玩具,这个玩具由他们之前忽视的旧玩具组成,现在他们可以用这个玩具做出很酷的东西。另一方面,在获得 GMail 的免费帐户后,最终用户对他们的部门 Web 应用程序提出了更高的要求。他们讨厌 postback(回发),不想要刷新,一切都保持原样但仍然是最新的。这可能吗?
那些有要求用户是激励我创建这个自定义控件的动力。让我介绍 AjaxDropDownList,这是我为 AJAX 世界做出的第一个贡献。AjaxDropDownList 是一个下拉列表控件,具有以下特性:
- 在后台从服务器源异步获取数据,无需 postback。
- 可以触发其他下拉列表的更改事件,从而生成级联链接的下拉列表效果。
- 封装在一个单独的控件中,可以轻松地将其拖放到设计器中,或从服务器代码中添加。
- 使用通用代码访问 selectedItem,就像普通的下拉列表一样,因此可以轻松地与其他 UI 框架集成。
- 兼容 Internet Explorer 6、Mozilla Firefox 1.04 和 Netscape 8.02。.
虽然我将该控件称为 AjaxDropDownList,但它并不使用 XML 来传输数据。我使用了 Douglas Crockford 的 JSON(JavaScript Object Notation),它更轻量级,并且可以轻松地被 JavaScript 作为对象使用。我意识到 XML 与 JSON 相比所能提供的潜力和灵活性。但在我的情况下,JSON 足以满足需求。
如何使用该控件
从示例项目开始
下载并使用 VS.NET 2003 打开项目。编辑 GetLookupData.aspx.cs 文件,并更改连接字符串,使其指向一个有效的 NorthWind 数据库。如果出于某种原因您没有 NorthWind 数据库,可以从 Microsoft 下载数据库脚本。只需在 Google 上搜索即可。
成功生成解决方案后,运行并浏览 default.aspx。评估这是否是您想要的控件。
演示内容
在此演示页面中,我们有三个 AjaxDropDownList:Customers(客户,ddlCustomers)、Orders(订单,ddlOrders)和 Products(产品,ddlProducts)。
Orders 下拉列表取决于 Customers。这意味着如果我们选择 Customers 下拉列表中的一个选项,那么 Order 下拉列表将根据我们的选择进行过滤。此外,Products 取决于 Orders。因此,选择 Customers 将触发 Orders 的更改,并随后触发 Products 的更改。请注意,所有这些都无需任何 postback。
在操作下拉列表时,您可能会遇到一个显示错误消息的 JavaScript 警报框。这可能意味着很多事情,但最可能的原因是您的数据库连接不起作用。
现在,单击 Submit(提交)按钮,页面最终将进行 postback。每个下拉列表的选定文本将显示在右侧。这是为了演示访问 ASP.NET DropDownList 中选定项的标准代码仍然适用于 AjaxDropDownList。
包含的内容
包含三个重要部分:
- AjaxDropDownList.cs 
包含自定义控件。将此文件放在一个单独的类库项目中,以便我们可以轻松地将控件添加到工具箱中。 
- Default.aspx 
这是一个使用 AjaxDropDownList控件的示例 Web 页面。控件从工具箱中拖放到设计器中。要将 AjaxDropDownList添加到您的工具箱中,请右键单击工具箱窗格,然后选择 Add/Remove Items(添加/删除项)。这将打开 Customize Toolbox(自定义工具箱)对话框。单击 Browse(浏览)按钮,然后选择 CustomControl.dll 或包含AjaxDropDownList的程序集。将出现一个带有复选框的控件列表。确保选择了AjaxDropDownList,然后关闭对话框。该控件将出现在工具箱中,可供拖放到设计器中。
- GetLookupData.aspx 
此页面处理来自 xmlHttp的请求并返回适当的 JSON。它需要处理两个查询字符串:- “id”是查找名称或标识符,例如 Country、Currency、Order、Product 和 InvoiceStatus。
- “filter”(可选)描述了名称-值对的过滤器,例如 Customer、ALFKI。
 一个像这样的请求: https:///GetLookupData.aspx?id=Orders&filter=Customer,ALFKI 会翻译成: “Get data from Orders for Customer code = ALFKI” 如何实现请求处理程序完全取决于您。 警告:GetLookupData.aspx 只是一个简单的示例,不适用于生产环境。 
- “
代码演练
AjaxDropDownList 控件大量使用了 JavaScript。JavaScript 代码嵌入在控件中,并在控件渲染时注入到响应流中。为了最小化 HTTP 有效负载,JavaScript 代码已使用 JavaScript Minifier 进行最小化处理。但是,这会使对实际代码的调试变得困难和令人沮丧。
因此,在示例项目中,我提供了原始格式的源代码,并且所有注释都保留了下来。请在本次演练中参考 SourceScript.aspx。
XMLHTTP
JavaScript 代码利用 xmlHttp 对象来异步或同步地向 Web 服务器发出请求。由于请求可以在不刷新页面的情况下发出,因此 Web 页面看起来更具响应性和交互性。getXMLHTTP() 方法被调用以获取对 xmlHttp 对象的引用,无论浏览器如何实现该对象。
控制器 (Controller)
每个 AjaxDropDownList 在 HTML 中都渲染为一个 <SELECT> 元素。其中每个元素都有其控制器,称为 AjaxDropDownController。控制器有很多事情要做:
- 向 Web 服务器执行异步请求以获取数据。
- 填充下拉列表。
- 监听下拉列表的更改事件。
- 充当观察者和被观察者。
- 在客户端持久化下拉列表的内容。
可以从模型-视图-控制器 (MVC) 的角度来考虑控制器,其中 <SELECT> 元素是视图,Web 服务器上的数据是模型,控制器本身是控制器,尽管我不想强调这一点,因为它并非完全实现。
执行异步后台请求
当控制器需要更新其下拉列表时,它会调用 load(),而 load() 又会调用 getSource()。在调用这些方法时,它可能会传递一个过滤器字符串,即它所依赖的下拉列表的名称-值对。在 getSource() 方法内部,会构建一个包含 id 和 filter 参数的请求 URL。
var requestUrl = baseUrl + "?id=" + self.lookupName;
if (filter != undefined && filter != "") 
{ 
            requestUrl += "&filter=" + filter; 
}
然后,在获取到 xmlHttp 对象的引用后,它将发送请求。请注意 xmlHttp.open 中的最后一个参数,它设置为 true 以指示异步请求。
xmlHttp = getXMLHTTP(); 
if (xmlHttp) 
{ 
            xmlHttp.onreadystatechange = doReadyStateChange; 
            xmlHttp.open("GET", requestUrl, true); 
            xmlHttp.send();
}
由于请求的异步性质,我们无法确定响应何时可用。因此,我们将事件处理程序 doReadyStateChange 分配给 onreadystatechange 属性。每次请求状态发生变化时,都会调用此事件处理程序。
function doReadyStateChange(){
    if (xmlHttp.readyState == 4)
    {
         if (xmlHttp.status == 200)
         {
             eval("var d=" + xmlHttp.responseText);
             if (d != null)
             {
                 populateList(d);
             }
          }
          else
          {
              alert("There was a problem retrieving the XML data:\n" +
                      xmlHttp.statusText);
          }
       }
   }
}
在 doReadyStateChange 中,我们检查 readyState = 4(表示“完成”)和 status = 200(表示“OK”HTTP 状态码)。一旦满足这些条件,就可以处理响应流了。
处理响应流
如前所述,我使用 JavaScript Object Notation (JSON) 将数据从 Web 服务器传输到客户端。JSON 比 XML 更轻量级,并且可以轻松地转换为 JavaScript 对象层次结构。
例如,当我们发送这样的请求时:
https:///GetLookupData.aspx?id=Product&filter=Order,10280
服务器将返回:
[{"value":"24","name":"Guaraná Fantástica"},
{"value":"55","name":"Pâté chinois"},
{"value":"75","name":"Rhönbräu Klosterbier"}]
我们将 responseText 与另一个字符串连接起来,形成一个有效的 JavaScript 语句,并使用 eval 执行该语句。
eval("var d=" + xmlHttp.responseText);
结果是,创建了一个如下所示的内存对象层次结构:
d +--- [0] +--- value: 24
  |        |--- name: Guaraná Fantástica
  |
  +--- [1] +--- value: 55
  |        |--- name: Pâté chinois
  |
  +--- [2] +--- value: 75
           |--- name: Rhönbräu Klosterbier
我们可以轻松地遍历对象层次结构,例如:
- d[0].value将返回 24。
- d[2].name将返回 Rhönbräu Klosterbier。
- d.length将返回 3。
然后将此对象传递给 populateList(),它负责填充相应的 <SELECT> 元素。populateList 首先会清除 select 中的选项项,然后遍历对象层次结构并创建新的选项项。
观察者模式
AjaxDropDownController 实现观察者模式。AjaxDropDownController 的一个实例既可以是观察者也可以是被观察者。每个实例都维护一个观察者列表,因此当相应下拉列表中的值发生更改时,控制器可以通知每个观察者有关更改的信息。
观察者列表保存在这个数组中:
var observers = [];
方法
- addObserver()- 调用此方法将新观察者添加到列表中。在添加到列表之前,它会检查新观察者是否已在列表中,从而确保唯一性。 
- removeObserver()- omitted.- 观察者模式的完整实现需要此方法。调用此方法可将观察者从列表中移除。目前,我没有看到任何实际用途,所以没有实现该方法。 
- notify()- 我们调用此方法来通知所有观察者有关相应下拉列表中的更改。此方法将根据下拉列表的 - selectedIndex构建一个过滤器字符串,然后遍历观察者列表并调用- load()方法。
- load()- 当调用此方法时,控制器将调用 - getSource以使用- xmlHttp异步获取源数据。
持久化内容
当下拉列表的内容在客户端更改时,此更改不会在 postback 时传递到服务器端。因此,我们无法使用标准代码访问下拉列表中的选定项,例如使用下拉列表的 SelectedItem 或 SelectedIndex。这会引发一个问题,即当我们想将控件集成到数据绑定框架中,或者我们实在无法编写特殊代码来处理该控件时。
这就是我将下拉列表内容保留在客户端的原因。对于每个 AjaxDropDownList,都会有一个与之关联的隐藏字段。这个隐藏字段是下拉列表内容的容器,其值为分隔符分隔的字符串,例如:
24|Guaraná Fantástica|55|Pâté chinois|75|Rhönbräu Klosterbier
分隔符字符可以在控件中配置。因此,如果您不喜欢 '|',可以更改为其他字符。
在 postback 期间,将读取分隔符分隔的字符串,并在下拉列表中创建相应的项。
服务器端
关于代码的讨论不完整,除非涉及到服务器端,因为这是数据来源。与客户端不同,服务器端代码相对简单。基本上,我们需要处理 xmlHttp 发送的请求,并以 JSON 格式提供数据。
如何从数据库获取数据完全取决于您。GetLookupData.aspx 不是一个好例子,因为它容易受到 SQL 注入攻击。在实际情况中,您可能需要调用数据访问框架,而不是直接连接到数据库。您可能还需要将数据缓存到内存中以节省与数据库的往返。
对客户端最重要的响应格式是您返回给客户端的格式。必须是这种格式:
[ {"value":"value1","name","name1"},
{"value":"value2","name","name2"},
 ...
{"value":"valueN","name","nameN"} ]
其中 value1...valueN 表示项值,name1...nameN 表示项文本。
自定义控件
AjaxDropDownList 是一个自定义控件,它继承自 System.Web.UI.WebControls.DropDownList。它封装了所有代码,以提供从客户端动态填充数据,并让下拉列表参与到链接下拉列表链中。
除了 DropDownList 的标准属性之外,它还有四个附加的公共属性:
- 观察者- 是一个 - ArrayList,包含观察者对象。当您将另一个- AjaxDropDownList添加到其中时,- AjaxDropDownList将依赖于当前的- DropDownList。例如:- DropDownList1.Observers.Add(DropDownList2); - 这将使 - DropDownList2依赖于- DropDownList1。
- SourceUrl- 设置源数据的 URL,不包含 - id和- filter参数,例如 https:///getLookupHandler.aspx。
- LookupName- 与当前 - DropDownList关联的查找列表的标识符,例如 Country、InvoiceStatus。此标识符将作为- id参数在请求中传递,例如:- https:///getLookupHandler.aspx?id=Country - 它也将用于构成 - filter,例如:- https:///getLookupHandler.aspx?id=State&filter=Country,AU 
- 分隔符- 一个字符,用于分隔在客户端隐藏字段中持久化的值。默认为 '|'。 
兼容性
该控件已在 Microsoft Internet Explorer 6、Mozilla Firefox 1.04 和 Netscape 8.02 上进行了测试。
愿望清单
在设计器方面还有一些工作要做。欢迎提出意见和建议。如果社区表现出足够的兴趣,我将投入更多时间来完善这个控件。
修订历史
- 2005年7月11日:文章和下载文件已更新 - 现在兼容 Mozilla Firefox 1.04 和 Netscape 8.02。
- 2005年6月6日:首次发布。
