3.1.3 使用W3C DOM动态编辑页面
Web最初只是作为媒介向各处分发静态的文本文档,如今它本身已经发展为一个应用开发平台。遗留的企业系统通常通过纯文本的终端部署,或者作为客户—服务器应用部署,这些遗留系统正在被完全通过Web浏览器部署的系统所取代。
随着最终用户越来越习惯于使用基于Web的应用,他们开始有了新的要求,需要一种更丰富的用户体验。用户不再满足于完全页面刷新,即每次在页面上编辑一些数据时页面都会完全刷新。他们想立即看到结果,而不是坐等与服务器完成完整的往返通信。
你已经了解了解析服务器发送的XML消息是多么容易。W3C DOM提供了一些属性和方法,使你能轻松地遍历XML结构,并抽取所需的数据。
前面的例子对于服务器发送的XML响应并没有做多少有用的事情。在警告框中显示XML文档的值没有太大的实际意义。你真正想做到的是让用户享有丰富的客户体验,不再遭遇一般Web应用中常见的连续页面刷新问题。页面连续刷新不仅使用户不满意,还会浪费服务器上宝贵的处理器时间,因为页面刷新需要重新构建整个页面的内容,而且会不必要地使用网络带宽来传送刷新的页面。
当然,最好的解决办法是根据需要修改页面上已有的内容。如果页面上大多数数据没有改变,则不应刷新整个页面,只需要修改页面中信息有变化的部分。
以往,在Web浏览器的限制之下,这一点很难做到。浏览器只是一个工具,它解释特殊的标记(HTML),并根据一组预定的规则显示这些标记。Web以及Web浏览器原来只是为了显示静态的信息,如果不以新页面的形式从服务器请求新的数据,这些信息不会改变。
除了一些例外情况,当前的浏览器都使用W3C DOM来表示Web页面的内容。这样做可以确保在不同的浏览器上Web页面会以同样的方式呈现,同时在不同的浏览器上,用于修改页面内容的脚本也会有相同的表现。Web浏览器的W3C DOM和JavaScript实现越来越成熟,这大大简化了在浏览器上动态创建内容的任务。原来总是要苦心积虑地解决浏览器间的不兼容性,如今这已经不太需要。表3-3列出了用于动态创建内容的DOM属性和方法。
表3-3 动态创建内容时所用的W3C DOM属性和方法
|
属性/方法 |
描述 |
|
document.createElement(tagName) |
文档对象上的createElement方法可以创建由tagName指定的元素。如果以串div作为方法参数,就会生成一个div元素 |
|
document.createTextNode(text) |
文档对象的createTextNode方法会创建一个包含静态文本的节点 |
|
<element>.appendChild(childNode) |
appendChild方法将指定的节点增加到当前元素的子节点列表(作为一个新的子节点)。例如,可以增加一个option元素,作为select元素的子节点 |
|
<element>.getAttribute(name) <element>.setAttribute(name, value) |
这些方法分别获得和设置元素中name属性的值 |
|
<element>.insertBefore(newNode, targetNode) |
这个方法将节点newNode作为当前元素的子节点插到targetNode元素前面 |
|
<element>.removeAttribute(name) |
这个方法从元素中删除属性name |
|
<element>.removeChild(childNode) |
这个方法从元素中删除子元素childNode |
|
<element>.replaceChild(newNode, oldNode) |
这个方法将节点oldNode替换为节点newNode |
|
<element>.hasChildnodes() |
这个方法返回一个布尔值,指示元素是否有子元素 |
关于浏览器的不兼容性
尽管当前Web浏览器中W3C DOM和JavaScript的实现在不断改进,但还是存在一些特异性和不兼容性,这使得应用DOM和JavaScript进行开发时很是头疼。
IE的W3C DOM和JavaScript实现最受限制。2000年初,一些统计称IE占据了整个浏览器市场95%的份额,由于没有竞争压力,Microsoft决定不完全实现各个Web标准。
这些特异问题大多都能得到解决,不过这样做会让脚本更是混乱不堪而且不合标准。例如,如果使用appendChild将<tr>元素直接增加到<table>中,则在IE中这一行并不出现,但在其他浏览器中却会显示出来。对此的解决之道是,将<tr>元素增加到表的<tbody>元素中,这种解决办法在所有浏览器中都能正确工作。
关于setAttribute方法,IE也有麻烦。IE不能使用setAttribute正确地设置class属性。对此有一个跨浏览器的解决方法,即同时使用setAttribute("class", "new- ClassName") 和setAttribute("className","newClassName")。另外,在IE中不能使用setAttribute设置style属性。最能保证浏览器兼容的技术不是<element>.setA-
ttribute("style, "font-weight:bold;"),而是<element>.style.cssText = "font
- weight:bold;"。
本书中的例子会尽可能地遵循W3C DOM和JavaScript标准,不过如果必须确保大多数当前浏览器的兼容性,可能也会稍稍偏离标准。
下面的例子展示了如何使用W3C DOM和JavaScript来动态创建内容。这个例子是假想的房地产清单搜索引擎,点击表单上的Search(搜索)按钮,会使用XMLHttpRequest对象以XML格式获取结果。使用JavaScript处理响应XML,从而生成一个表,其中列出搜索到的结果(见图3-3)。

图3-3 使用W3C DOM方法和JavaScript动态创建搜索结果
服务器返回的XML很简单(见代码清单3-5)。根节点properties包含了得到的所有property元素。每个property元素包含3个子元素:address、price和comments。
代码清单3-5 dynamicContent.xml
<?xml version="1.0" encoding="UTF-8"?>
<properties>
<property>
<address>812 Gwyn Ave</address>
<price>$100,000</price>
<comments>Quiet, serene neighborhood</comments>
</property>
<property>
<address>3308 James Ave S</address>
<price>$110,000</price>
<comments>Close to schools, shopping, entertainment</comments>
</property>
<property>
<address>98320 County Rd 113</address>
<price>$115,000</price>
<comments>Small acreage outside of town</comments>
</property>
</properties>
具体向服务器发送请求并对服务器响应做出回应的JavaScript与前面的例子是一样的。不过,从handleReadyStateChange函数开始有所不同。假设请求成功地完成,接下来第一件事就是调用clearPreviousResults函数,将以前搜索所创建的内容删除。
clearPreviousResults函数完成两个任务:删除出现在最上面的“Results”标题文本,并从结果表中清除所有行。首先使用hasChildNodes方法查看可能包括标题文本的span元素是否有子元素。应该知道,只有hasChildNodes方法返回true时才存在标题文本。如果确实返回true,则删除span元素的第一个(也是惟一的)子节点,因为这个子节点表示的就是标题文本。
clearPreviousResults的下一个任务是在显示搜索结果的表中删除所有行。所有结果行都是tbody节点的子节点,所以先使用document.getElementById方法得到该tbody节点的引用。一旦有了tbody节点,只要这个tbody节点还有子节点(tr元素)就进行迭代处理。每次迭代时都会从表体中删除childNodes集合中的第一个子节点。当表体中再没有更多的表行时,迭代结束。
搜索结果表在parseResults函数中建立。这个函数首先创建一个名为results的局部变量,这是使用XMLHttpRequest对象的responseXML属性得到的XML文档。
使用getElementsByTagName方法来获得XML文档中包含所有property元素的数组,然后将这个数组赋给局部变量properties。一旦有了property元素的数组,可以迭代处理数组中的各个元素,并获得property的address、price和comments。
var properties = results.getElementsByTagName("property");
for(var i = 0; i < properties.length; i++) {
property = properties[i];
address = property.getElementsByTagName("address")[0].firstChild.nodeValue;
price = property.getElementsByTagName("price")[0].firstChild.nodeValue;
comments = property.getElementsByTagName("comments")[0].firstChild.nodeValue;
addTableRow(address, price, comments);
}
下面来仔细分析这个循环,因为这正是parseResults函数的核心。在for循环中,首先得到数组中的下一个元素,并把它赋给局部变量property。接下来,对于你感兴趣的各个子元素(address、price和comments),分别获得它们的节点值。
请考虑address元素,这是property元素的一个子元素。首先在property元素上调用getElementsByTagName方法来得到单个address元素。getElementsByTagName方法返回一个数组,不过因为你知道有且仅有一个address元素,所以可以使用[0]记法来引用这个元素。
沿着XML结构继续向下,现在有了address标记的引用,你需要得到它的文本内容。记住,文本实际上是父元素的一个子节点,所以可以使用firstChild属性来访问address元素的文本节点。有了文本节点后,可以引用文本节点的nodeValue属性来得到文本。
采用同样的办法来得到price和comments元素的值,并把各个值分别赋给局部变量price和comments。再将address、price和comments传递给名为addTableRow的辅助函数,它会用这些结果数据具体建立一个表行。
addTableRow函数使用W3C DOM方法和JavaScript建立一个表行。使用document.cre-
ateElement方法创建一个row对象,之后,再使用名为createCellWithText的辅助函数分别为address、price和comments值创建一个cell对象。createCellWithText函数会创建并返回一个以指定的文本作为单元格内容的cell对象。
createCellWithText函数首先使用document.createElement方法创建一个td元素,然后使用document.createTextNode方法创建一个包含所需文本的文本节点,所得到的文本节点追加到td元素。这个函数再把新创建的td元素返回给调用函数(addTableRow)。
addTableRow函数对address、price和comments值重复调用createCellWithText函数,每一次向tr元素追加一个新创建的td元素。一旦向row(行)增加了所有cell(单元格),这个row就将被增加到表的tbody元素中。
就这么多!你已经成功地读取了服务器返回的XML文档,而且动态创建了一个结果表。代码清单3-6显示了这个例子完整的JavaScript和可扩展HTML代码。
代码清单3-6 dynamicContent.html
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Dynamically Editing Page Content</title>
<script type="text/javascript">
var xmlHttp;
function createXMLHttpRequest() {
if (window.ActiveXObject) {
xmlHttp = new ActiveXObject("Microsoft.XMLHTTP");
}
else if (window.XMLHttpRequest) {
xmlHttp = new XMLHttpRequest();
}
}
function doSearch() {
createXMLHttpRequest();
xmlHttp.onreadystatechange = handleStateChange;
xmlHttp.open("GET", "dynamicContent.xml", true);
xmlHttp.send(null);
}
function handleStateChange() {
if(xmlHttp.readyState == 4) {
if(xmlHttp.status == 200) {
clearPreviousResults();
parseResults();
}
}
}
function clearPreviousResults() {
var header = document.getElementById("header");
if(header.hasChildNodes()) {
header.removeChild(header.childNodes[0]);
}
var tableBody = document.getElementById("resultsBody");
while(tableBody.childNodes.length > 0) {
tableBody.removeChild(tableBody.childNodes[0]);
}
}
function parseResults() {
var results = xmlHttp.responseXML;
var property = null;
var address = "";
var price = "";
var comments = "";
var properties = results.getElementsByTagName("property");
for(var i = 0; i < properties.length; i++) {
property = properties[i];
address = property.getElementsByTagName("address")[0].firstChild.nodeValue;
price = property.getElementsByTagName("price")[0].firstChild.nodeValue;
comments = property.getElementsByTagName("comments")[0]
.firstChild.nodeValue;
addTableRow(address, price, comments);
}
var header = document.createElement("h2");
var headerText = document.createTextNode("Results:");
header.appendChild(headerText);
document.getElementById("header").appendChild(header);
document.getElementById("resultsTable").setAttribute("border", "1");
}
function addTableRow(address, price, comments) {
var row = document.createElement("tr");
var cell = createCellWithText(address);
row.appendChild(cell);
cell = createCellWithText(price);
row.appendChild(cell);
cell = createCellWithText(comments);
row.appendChild(cell);
document.getElementById("resultsBody").appendChild(row);
}
function createCellWithText(text) {
var cell = document.createElement("td");
var textNode = document.createTextNode(text);
cell.appendChild(textNode);
return cell;
}
</script>
</head>
<body>
<h1>Search Real Estate Listings</h1>
<form action="#">
Show listings from
<select>
<option value="50000">$50,000</option>
<option value="100000">$100,000</option>
<option value="150000">$150,000</option>
</select>
to
<select>
<option value="100000">$100,000</option>
<option value="150000">$150,000</option>
<option value="200000">$200,000</option>
</select>
<input type="button" value="Search" onclick="doSearch();"/>
</form>
<span id="header">
</span>
<table id="resultsTable" width="75%" border="0">
<tbody id="resultsBody">
</tbody>
</table>
</body>
</html>
RSS订阅






收 藏
推 荐