2008 年 7 月 11 日 更新
雖然很多人都熟知文檔對象模型(DOM)是處理 XML的基礎,但使用 HTML 的浏覽器開始使這個主題發生一些變化。随着更新的浏覽器通過用戶端腳本(比如 JavaScript)實作 W3C文檔對象模型,現在 DOM 又經曆了一個輪回。本文将介紹通過 JavaScript 實作 DOM,并描述 Web頁面的建構,使用者可以向這個頁面添加并編輯注釋。
注意:本文使用相容 DOM 的浏覽器,比如 Netscape 6.x、Internet Explorer 6 或 Mozilla 1.0。熟悉 Java 技術或其他語言的文檔對象模型會有所幫助,但不是必要條件。不過您應該大體熟悉 JavaScript。
為什麼使用 DOM 而不使用 DHTML?
在本文中,我從文檔對象模型的角度分析 Web頁面的結構:檢查子節點和父節點,以及向現有文檔添加并編輯節點。為了示範這個過程,我将建構一個頁面。使用者可以在這個頁面上任意建立注釋并編輯這些注釋的内容(這個頁面不會很複雜;我将把一些任務留給您,比如儲存和移動内容以及重新調整内容的大小等)。既然使用 DHTML技術就可以輕松實作這些操作,為什麼還要使用 DOM 呢?
開發 XML 的其中一股推動力是将 HTML 片段化,而 DHTML 順應了這一潮流。雖然某些方面已經形成标準,但浏覽器如何處理不同的方面仍然存在很大的分歧。DOM 是可以在整個領域範圍内實作的标準接口集,是以程式設計人員隻需編寫一個版本的頁面。
另外,Web 頁面不斷朝 XML 的方向發展。XHTML 1.0 隻是由 HTML 4.0.1 改編而來的 XML,并且也越來越多地使用 XML 生成 Web 頁面。最終,DOM 将是在這些頁面内通路元素和文本的主要技術(也可将它用于 XSLT 樣式表)。
![]() |
|
基礎頁面
讓我們從基礎的頁面開始,該頁面包含一個工作區域和表單,它顯示了已建立注釋的數量:
清單 1. 基礎頁面
|
清單 1 展示了帶有幾種樣式(隻是為了使頁面好看些)的基礎頁面。頁面的正文包括一個單個的
div
元素,該元素包含一個 heading 元素(
h1
)和一個
form
元素。即使是在沒有使用 DOM 的浏覽器裡,也可以在表單内通路資訊。
圖 1. 基礎頁面
![]() |
|
使用 DHTML
使用 DHTML 可以通路甚至修改表單字段的資訊。例如,您可以建立一個腳本将目前表單的值增加 1,然後告訴頁面在使用者點選按鈕時執行這個腳本:
清單 2. 增加目前的數值
|
在 清單 2 中,
incrementCurrent()
函數接受文檔對象,然後将
noteForm
對象從頁面内的表單數組中抽出。該函數從
noteForm
對象獲得表單字段
total
并檢索值,然後在頁面内更新這個值。
這種更改是即時的。更改頁面、重新加載并反複按按鈕,您将發現文本字段每一次都會相應地進行更新。
同樣,您也可以使用 DHTML 屬性檢索
div
元素的文本。由于它有一個
mainDiv
的
id
,是以可以使用
innerHTML
屬性,如:
theText = mainDiv.innerHTML;
在這個例子中,使用了兩個 DHTML 技術:
forms
數組,以及通過基于屬性值的名稱調用元素(而不是通過元素名或總體結構)。但存在的問題是這種方法不夠通用。雖然可以對表單的名稱使用一個變量,如果碰到不使用表單的呈現,應該怎麼辦呢?
![]() |
|
DOM 和 JavaScript
如果存在一個定義文檔對象模型的原則,它将把資訊排列成父-子層次結構。例如,參看以下的 XML:
<parentElement><childElement>My Text Node</childElement></parentElement>
有 3 個節點:root 節點是
parentElement
。它有一個子元素
childElement
。
childElement
元素有自己的父元素
parentElement
,作為子元素的文本節點的值是 “My Text Node”。這個文本節點的父元素是
childElement
。共享一個父元素的兩個節點稱為兄弟(sibling)節點。注意,文本就是它本身的節點。元素實際上并沒有值,它們僅有文本子節點。
您應該比較熟悉如何使用 Java 技術或其他語言讀取 XML 文檔的結構。實施這個操作要用到一個帶有定義良好的函數和對象屬性的 API。例如,這個文檔有一個作為其根元素的
html
元素,該元素有兩個子元素
head
和
body
(為簡單起見,我删除了正常情況下會出現在這些元素之間的空格;至于如何處理這些空格,不同的浏覽器之間還沒有達成一緻)。要通過 Java 技術通路
body
元素,您可以使用幾個不同的表達式,假如您已經命名了
Document
對象
document
。
第一個步驟是獲得所有元素的子元素的清單,然後從該清單選擇一個特定的條目,就像以下的例子一樣:
bodyElement = document.getChildNodes().item(0).getChildNodes().item(1);
在這裡,首先獲得
html
元素(該元素是文檔的第一個子元素),然後獲得它的第二個子元素
body
(
getChildNodes()
是基于零的)。另一種辦法是将
html
元素作為第一個子元素直接通路,然後移動到它的第一個子元素(
head
),最後移動到該元素的下一個兄弟元素(第二個子元素
body
):
bodyElement = document.getFirstChild().getFirstChild().getNextSibling();
從這裡您可以獲得節點的類型:
typeInt = bodyElement.getNodeType()
節點類型以整數傳回,并且允許您适當地處理每一個節點;一個元素(類型
1
)有名稱沒有值,但是一個文本節點(類型
3
)有值沒有名稱。
了解了這些後,就可以擷取元素的名稱:
elementName = bodyElement.getNodeName();
或它的文本内容:
elementContent = bodyElement.getFirstChild().getNodeValue();
(記住,元素的文本本身就是一個節點,而該元素則是它的父節點)。
當将這些函數移動到 JavaScript 時,相同的基本 API 已經準備就緒,但處理方式有些不同。例如,可以直接通路屬性,而不是通過
get
和
set
方法通路,是以 JavaScript 中的相同語句會表示為:
bodyElement = document.childNodes.item(0).childNodes.item(1);
bodyElement = document.firstChild.firstChild.nextSibling;
您可以從這裡獲得元素的類型和名稱及其内容:
ElementType = bodyElement.nodeType;
elementName = bodyElement.nodeName;
elementContent = bodyElement.firstChild.nodeValue;
但是需要注意,隻能更改屬性的名稱。傳回對象的函數保持不變。例如,可以根據 ID 檢索某個特定的對象,就像下面的示例一樣:
formElement = document.getElementById("noteForm");
這隻是基本原理,現在我們看看它的實際運作情況。
![]() |
|
添加新的注釋
實際節點本身隻是一個帶有标準文本和連結的框,這個框允許使用者稍後再編輯文本,如 圖 2 所示。
圖 2. 新的節點
這個新節點與下面的 HTML 等效:
清單 3. 目标 HTML
|
使用标準的 DOM 添加實際元素,如 清單 4 所示:
清單 4. 添加新的注釋元素
|
在這個示例中,更改了按鈕進而執行
makeNewNote()
而不是
incrementCurrent()
,盡管
makeNewNote()
還使用該函數。首先,通過
getElementById()
獲得針對主元素
div
的引用,這個元素最終會包含注釋。然後可以使用
document
對象建立一個名為
div
的新元素,就像在其他語言中一樣。要設定
id
屬性,您隻需要在新的元素上使用
setAttribute()
方法。
每一個注釋都有一個獨特的
id
屬性,是以您需要知道目前的總和是多少。要獲得這個資訊,必須從 form 元素本身開始。可以從這裡擷取子元素清單。第一個(帶有一個索引
)是文本,第二個是(帶有一個索引
1
)是實際的
input
元素。
但
.value
又是什麼呢?是不是輸入錯誤?它應該是
nodeValue
,是嗎?
實際上不是。記住,元素沒有值。乍一看,好像我将 DOM 和 DHTML 屬性混在一起,但實際上現在擷取的元素不僅是
org.w3c.dom.Element
的實作,還是
org.w3c.dom.html.HTMLInputElement
(它也包含了一個代表表單字段值的值屬性)的實作。在這一方面,DOM 模仿了一些(雖然不是全部)通過 DHTML 可以實作的屬性。
設定了屬性之後,隻需要将新的
div
元素附加到
mainDiv
元素,
div
元素将出現在這個位置上。或者,如果它有任何顯示屬性或文本的話,它至少會出現在這個位置上。
要添加樣式資訊,将需要實際使用 DHTML 樣式對象:
清單 5. 添加樣式資訊
|
添加的結果是産生一個藍邊的黃色小框,如 圖 3 所示。
圖 3. 空的框
注意,注釋的
left
屬性取決于目前注釋的數量,它随着每個注釋的添加而增長。您可以通過這種方式添加一系列框,如 圖 3 所示。
![]() |
|
添加内容
添加
div
的内容會呈現一個小問題。我可以使用 innerHTML 屬性:
|
但我如何直接使用 DOM 方法來實作?首先想到的可能是簡單地設定
div
元素的文本子節點的值:
|
雖然文本真的得到了添加,但結果可能與預期不同,如 圖 4 所示。
圖 4. 框中的文本
問題是您并沒有真的添加文本,而是混合了内容,包括文本和元素。浏覽器認為您将這看作是 CDATA(完全按照字面意義得出),并且也沒有建立元素。不是簡單地在一個塊中添加所有的内容,您必須實際添加每個元素:
清單 6. 添加内容
|
首先,您已經建立了一個傳回一個對象的新函數
getEditLink
。這個對象是用标準 DOM 方法建立的
a
元素。接下來,添加一個标準的 break 标記
br
,最後添加包含實際節點文本的節點。
結果是得到完成的節點,而元素保持完整并可使用。
![]() |
|
更改現有的節點
現在有了内容,但怎樣才能更改它呢?因為您單個地添加元素,是以隻能編輯代表節點文本的單個文本節點。這可以通過 3 種方式來實作。首先删除有問題的節點,然後添加一個新的節點:
清單 7. 删除節點并添加一個新的節點
|
另一種方法很簡單,隻需代換現有的節點:
清單 8. 替換一個節點
|
在這個示例中,用
newNode
代替了
oldNode
,同時也相應地更改了文檔。
最後一個辦法是更改現有節點的文本:
清單 9. 更改現有的節點
|
因為
editNote
使用相應的
div
的
id
值作為參數,相同的函數可以用于任意節點,如 圖 5 所示。
圖 5. 最後的頁面
![]() |
|
結束語
在本文,您已經了解在 Web 頁面的 JavaScript 中使用 DOM 的基礎知識。您可以在使用 JavaScript 的地方(比如在 XSLT 樣式表中)使用這些基本原理。
文檔對象模型将 XML 文檔内的元素、文本和其他類型的節點呈現為一系列的父子關系。操作這些單獨的節點可以影響頁面本身。除了 DOM Core方法以外,XHTML 頁面也可以公開作為 DOM HTML 子產品的一部分的屬性和方法,DOM HTML 子產品試圖整合許多程式設計人員用了好幾年的DHTML 屬性。