从属列表问题(dependent list problem)"的问题时常被提出。问题时常出现于当你有两个以上的选择列表时,一个主列表有若干个选项,你希望当用户选择主列表中的某个选项时,在其他的从属列表中显示相关的选项。你可以通过eXtensible Markup Language(XML)的数据岛(data islands)来实现这一功能,把XML内嵌到你的HTML中。这一结果对在客户端的XML应用具有实践意义。
假设你为一家名为"CheapPc"的公司工作,你负责让访问公司网站的用户找到用户所在州的"CheapPc"商店。在公司网站上你列出了州名的列表。当一个顾客在主列表中点击选择一个州时,你希望在从属列表中显示相关的商店列表。
问题在于:你怎样填充从属列表而不用刷新该页面或不用每次用户在主列表中选择时都向服务器发送消息?XML数据岛完美地解决这一问题。当用户发出请求,所有商店的数据都连接成一个XML字符串(Listing 1)。返回的结果是一个镶嵌在HTML页面中的数据岛。该数据岛使用以下XML结构:
根元素包含州名的列表,每个州名的列表依次包含商店的列表。每个商店都有一个相应的ID和城市名。为了生成这一XML字符串,你得用ActiveX Data Object(ADO)查询数据库,并用XML标记符格式化查询结果。把记录集数据格式化成HTML表格和把记录集数据格式化成XM文件之间有一些不同。在本文的后半部分,我将向你展示如何避免过多的串联工作以提高响应时间。你可以点击这里下载例程。
把处理过程移到客户端
当Web服务器返回网页后,所有进一步的操作处理都在客户端进行。我原先认为如果向客户端发送一个大的数据ji hui使程序的速度放慢。但后来我却发现一次性发送所有数据比在客户与服务器之间来回地进行数据交换快得多。唯一可被注意到的停顿只是在IE5的XML解析器读取数据时的一个短暂的初始化停顿。
HTML文件本身包含有4个元素:一个保存州名数据的选择列表(主列表),XML数据,一个从属列表的div标记占位符,和两个脚本。当州名选择列表的选项发生变化时,第二个脚本就执行(Listing 2)。
你希望每次用户选择一个州时都能从数据岛重新得到相应的商店列表。所以你需要一个脚本去处理州名选择列表的onChange事件:
在这个脚本中重新得到了州名选择列表的值:
Dim stateCode
stateCode=document.all.State.value
接着重新得到所选定州的商店列表。你可以通过两个方法得到这个列表。第一个办法就是获得<XML>标记符的记录集属性的引用:
Dim R
Set R = xmldata.recordset
通过这个方法得到的记录集是一个标准的ADO记录集对象,字段名都与标记符相对应。这个方法只有在每个记录都有同样的字段的"简单"XML数据文件中才能很好地工作。
第二个方法就比较强大和普遍。IE5把XML数据岛象其他HTML元素一样对待。为了在客户端访问XML数据,你只需通过ID来引用该元素。IE5将返回XML树的根元素(document元素):
set xml = document.all("xmldata")
你用这个document元素可以执行Extensible Stylesheet Language (XSL)查询语句。使用XSL查询语句,你可以得到某个给定层次上的节点列表,包含指定属性的节点列表,或以上两者的任意组合。例如,以下的查询语句返回所有符合用户所选州的商店节点:
Dim nodes
set nodes = xml.selectNodes ("locations/state[@code='" & stateCode & "']/store")
XSL搜索字符串看起来很象一个文件路径;它有XML树相应各层次的值。用"/"分隔每个层次。以"节点=数值"的匹配方式加到相关节点的"[]"中搜索指定数值的节点,在属性名前加上"@"来搜索包含指定属性的节点。运行selectNodes方法将返回一个IXMLDOMNodes集合对象。最后,显示从属列表。最简单的方法:根据XSL查询语句返回的节点列表来改变从属列表的标记符的值:
set aList = document.all("cities")
aList.options.length = 0
i = 0
For Each N In nodes
anID = N.selectSingleNode("id").text
aCity = N.selectSingleNode _("city").text
aList.options.length = _aList.options.length + 1
aList.options(i).id = anID
aList.options(i).value = aCity
aList.options(i).innerText = aCity
i = i + 1
Next
现在你知道了如何利用XML数据岛来解决从属列表问题。注意:这一技巧不适合于大规模的数据-如那些成千上万的记录-但如果那样的话,那主要还是一个如何显示的问题,而非技术上的问题。如果你发现一个查询语句可能返回不适合的大组记录,你即可以限制返回记录的数目(例如为用户提供一个"More Record"按钮),又可以修改用户界面使用户可以点击下一层次的内容,因此而限制了用户必须选择的记录数目。
把数据库的表数据保存到文件中
我曾答应向你展示如何避免每次请求都要读取数据库和串联成XML字符串的步骤。答案就是把数据库的表数据保存到文件中;这样做有几个好处。读取文件比查询数据库并把记录集格式化成XML要快。这样做也可以简化你的HTML文件,因为你只需在HTML中包含对XML的引用。最后这样做使数据对于客户端来说是透明的。如果你以Listing 1中的方式发送XML标记符的话,用户可以通过IE的"查看/源文件"看到数据。使用文件引用的话,用户只能看到引用标记符,而非数据。
只要你的数据库记录不是永不改变的,你就还得在数据改变时查询数据库。例如,你可以在sql server中写一个触发器,当"Location"表发生改变时更新"LastChanged"表中的日期和时间(Listing 3)。对于每一次请求,你可以比较Location"表与保存数据的XML文件的日期和时间。本文使用的Access数据库不支持触发器,但你可以编写代码来实现当"Location"表发生改变时更新"LastChanged"表的功能。你也可以每隔一段时间,如一天或一个小时就重新生成文件。
用Scripting.FileSystemObject 和 Scripting.TextStream对象写XML文件到硬盘中。为了能写XML文件,你必须给IUSR_MacHINENAME匿名用帐户足够的权限以打开、读、写和删除文件(如果你使用的是NTFS格式的分区硬盘的话)。如果你只想利用XML数据重新在客户端建立一个记录集的话,你可以用adPersistXML方法保存记录集到硬盘中。如果你这样做的话,必须注意ADO为了能从XML数据重新建立记录集而把记录集保存成优化的格式。这样生成的XML对于XSL查询语句而言即不美观又不方便:
Dim fs
Dim ts
dim aFilename
aFilename = Server.MapPath _("/XMLDataIslands") & _"/locations.xml"
set fs = Server.CreateObject _("Scripting.FileSystemObject")
set ts = fs.OpenTextFile _(aFilename, ForWriting)
ts.Write s
现在,当你收到一个请求时,查看"LastChanged"表中日期和时间:
Dim SQL
Dim R
Dim conn
dim tableModifyDate
set conn = Server.CreateObject _("ADODB.Connection")
conn.mode = adModeRead
conn.open "DSN=CheapPC;UID=sa;PWD="
SQL = "SELECT " & _"LastChanged.LastChanged " & _"FROM Locations WHERE " & _"Tablename='Locations'"
set R = conn.execute(SQL,,adCmdText)
tableModifyDate = R("LastChanged")
接下来,通过获取一个File对象来比较XML文件与tableModifyDate的日期和时间,并查看它的FileCreatedOn属性值:
dim fs
dim aFile
dim rewriteFile
rewriteFile=False
set fs = Server.CreateObject _("Scripting.FileSystemObject")
if fs.FileExists(aFilename) then
Set aFile = fs.GetFile(aFilename)
If aFile.DateCreated < _LocationsModifiedDate Then
rewriteFile=True
end if
else
rewriteFile=true
end if
如果XML文件不存在或需要更新时,你必须写文件(Listing 4)。这样就重新从数据库读取数据并格式化成XML,但你只需覆盖旧的XML文件而不必把XML发送到浏览器。现在在你的HTML文件中,你只需引用该XML文件即可。当你的XML文件不需要更新,你的服务器会略过数据的读取与格式化,只是简单地读取XML数据到浏览器。从整体上看,读取XML数据为你的服务器节省了大量的处理能力,使你的页面更有效率。
新闻热点
疑难解答