65.9K
CodeProject 正在变化。 阅读更多。
Home

AJAX DropDownList

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.63/5 (34投票s)

2005年6月6日

10分钟阅读

viewsIcon

787396

downloadIcon

12958

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

Sample Image - ajaxdropdownlist.gif

引言

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() 方法内部,会构建一个包含 idfilter 参数的请求 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 时传递到服务器端。因此,我们无法使用标准代码访问下拉列表中的选定项,例如使用下拉列表的 SelectedItemSelectedIndex。这会引发一个问题,即当我们想将控件集成到数据绑定框架中,或者我们实在无法编写特殊代码来处理该控件时。

这就是我将下拉列表内容保留在客户端的原因。对于每个 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,不包含 idfilter 参数,例如 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日:首次发布。

链接

© . All rights reserved.