本系列的上一篇文章中考察了文件物件模型(DOM)編程中涉及到的概念——網頁瀏覽器如何把網頁看作一棵樹,現在您應該理解了 DOM 中使用的編程結構。本期教程將把這些知識用於實踐,建立一個簡單的包含一些特殊效果的 Web 頁面,所有這些都使用 JavaScript 操縱 DOM 來建立,不需要重新載入或者重新整理頁面。
前面兩期文章已經詳細介紹了文件物件模型或者 DOM,讀者應該很清楚 DOM 是如何工作的了。(前兩期 DOM 文章以及 Ajax 系列更早文章的連結請參閱參考資料。)本教程中將把這些知識用於實踐。我們將開發一個簡單的 Web 應用程式,其使用者介面可根據使用者動作改變,當然要使用 DOM 來處理介面的改變。閱讀完本文之後,就已經把學習到的關於 DOM 的技術和概念付諸應用了。
假設讀者已經閱讀過上兩期文章,如果還沒有的話,請先看一看,切實掌握什麼是 DOM 以及 網頁瀏覽器如何將提供給它的 HTML 和 CSS 轉化成單個表示網頁的樹狀結構。到目前為止我一直在討論的所有 DOM 原理都將在本教程中用於建立一個能工作的(雖然有點簡單)基於 DOM 的動態 Web 頁面。如果遇到不懂的地方,可以隨時停下來複習一下前面的兩期文章然後再回來。
從一個應用程式範例開始
關於代碼的說明
為了把注意力集中到 DOM 和 JavaScript 代碼上,我編寫 HTML 的時候有些隨意地採用內聯樣式(比如 h1 和 p 元素的 align 屬性)。雖然對實驗來說這樣做是可接受的,但是對於開發的任何產品應用程式,我建議花點時間把所有的樣式都放到外部 CSS 樣式表中。
我們首先建立一個非常簡單的應用程式,然後再添加一點 DOM 魔法。要記住,DOM 可以移動網頁中的任何東西而不需要提交表單,因此足以和 Ajax 媲美;我們建立一個簡單的網頁,上面只顯示一個普通的舊式大禮帽,還有一個標記為 Hocus Pocus! 的按鈕(猜猜這是幹什麼的?)
初始 HTML
清單 1 顯示了這個網頁的 HTML。除了標題和表單外,只有一個簡單的圖片和可以點擊的按鈕。
清單 1. 應用程式範例的 HTML
<html>
<head>
<title>Magic Hat</title>
</head>
<body>
<h1 align="center">Welcome to the DOM Magic Shop!</h1>
<form name="magic-hat">
<p align="center">
<img src="topHat.gif" />
<br /><br />
<input type="button" value="Hocus Pocus!" />
</p>
</form>
</body>
</html>
可以在本文後面的下載中找到這段 HTML 和本文中用到的圖片。不過我強烈建議您只下載那個圖片,然後隨著本文中逐漸建立這個應用程式自己動手輸入代碼。這樣要比讀讀本文然後直接開啟完成的應用程式能夠更好地理解 DOM 代碼。
查看樣本網頁
這裡沒有什麼特別的竅門,開啟網頁可以看到圖 1 所示的結果。
圖 1. 難看的大禮帽
關於 HTML 的補充說明
應該 注意的重要一點是,清單 1 和圖 1 中按鈕的類型是 button 而不是提交按鈕。如果使用提交按鈕,單擊該按鈕將導致瀏覽器提交表單,當然表單沒有 action 屬性(完全是有意如此),從而會造成沒有任何動作的無限迴圈。(應該自己試試,看看會發生什麼。)通過使用一般輸入按鈕而不是提交按鈕,可以把 javaScript 函數和它串連起來與瀏覽器互動而無需 提交表單。
向應用程式範例添加元素
現在用一些 JavaScript、DOM 操作和小小的圖片戲法裝扮一下網頁。
使用 getElementById() 函數
顯然,魔法帽子沒有兔子就沒有看頭了。這裡首先用兔子的圖片替換頁面中原有的圖片(再看看圖 1), 2 所示。
圖 2. 同樣的禮帽,這一次有了兔子
完成這個 DOM 小戲法的第一步是找到網頁中表示 img 元素的 DOM 節點。一般來說,最簡單的辦法是用 getElementById() 方法,它屬於代表 Web 頁面的 document 對象。前面已經見到過這個方法,用法如下:
var elementNode = document.getElementById("id-of-element");
為 HTML 添加 id 屬性
這是非常簡單的 JavaScript,但是需要修改一下 HTML:為需要訪問的元素增加 id 屬性。也就是希望(用帶兔子的新圖片)替換的 img 元素,因此將 HTML 改為清單 2 的形式。
清單 2. 增加 id 屬性
<html>
<head>
<title>Magic Hat</title>
</head>
<body>
<h1 align="center">Welcome to the DOM Magic Shop!</h1>
<form name="magic-hat">
<p align="center">
<img src="topHat.gif" id="topHat" />
<br /><br />
<input type="button" value="Hocus Pocus!" />
</p>
</form>
</body>
</html>
如果重新載入(或者開啟)該頁面,可以看到毫無變化,增加 id 屬性對網頁的外觀沒有影響。不過,該屬性可以協助 JavaScript 和 DOM 更方便地處理元素。
抓住 img 元素
現在可以很容易地使用 getElementById() 了。已經有了需要元素的 ID,即 topHat,可以將其儲存在一個新的 JavaScript 變數中。在 HTML 頁面中增加清單 3 所示的代碼。
清單 3. 訪問 img 元素
<html>
<head>
<title>Magic Hat</title>
<script language="JavaScript">
function showRabbit() {
var hatImage = document.getElementById("topHat");
}
</script>
</head>
<body>
<h1 align="center">Welcome to the DOM Magic Shop!</h1>
<form name="magic-hat">
<p align="center">
<img src="topHat.gif" id="topHat" />
<br /><br />
<input type="button" value="Hocus Pocus!" />
</p>
</form>
</body>
</html>
現在開啟或重新載入該網頁同樣沒有什麼驚奇的地方。雖然現在能夠訪問圖片,但是對它還什麼也沒有做。
修改圖片,麻煩的辦法
完成所需修改有兩種方法:一種簡單,一種麻煩。和所有的好程式員一樣,我也喜歡簡單的辦法;但是運用較麻煩的辦法是一次很好的 DOM 練習,值得您花點時間。首先看看換圖片比較麻煩的辦法;後面再重新分析一下看看有沒有更簡單的辦法。
用帶兔子的新照片替換原有圖片的辦法如下:
1、建立新的 img 元素。
2、訪問當前 img 元素的父元素,也就是它的容器。
3、在已有 img 元素之前 插入新的 img 元素作為該容器的子級。
4、刪除原來的 img 元素。
5、結合起來以便在使用者單擊 Hocus Pocus! 按鈕時調用剛剛建立的 JavaScript 函數。
建立新的 img 元素
通過上兩期文章應該記住 DOM 中最關鍵的是 document 對象。它代表整個網頁,提供了 getElementById() 這樣功能強大的方法,還能夠建立新的節點。現在要用到的就是這最後一種性質。
具體而言,需要建立一個新的 img 元素。要記住,在 DOM 中一切都是節點,但是節點被進一步劃分為三種基本類型:
1、元素
2、屬性
3、文本節點
還有其他類型,但是這三種可以滿足 99% 的編程需要。這裡需要一個 img 類型的新元素。因此需要下列 JavaScript 代碼:
var newImage = document.createElement("img");
這行代碼可以建立一個 element 類型的新節點,元素名為 img。在 HTML 中基本上就是:
<img />
要記住,DOM 會建立結構良好的 HTML,就是說這個目前為空白的元素包括起始和結束標籤。剩下的就是向該元素增加內容或屬性,然後將其插入到網頁中。
對內容來說,img 是一個空元素。但是需要增加一個屬性 src,它指定了要載入的圖片。您也許認為要使用 addAttribute() 之類的方法,但情況並非如此。DOM 規範的制定者認為程式員可能喜歡簡潔(的確如此!),因此他們規定了一個方法同時用於增加新屬性和改變已有的屬性值:setAttribute()。
如果對已有的屬性調用 setAttribute(),則把原來的值替換為指定的值。但是,如果調用 setAttribute() 並指定一個不 存在的屬性,DOM 就會使用提供的值增加一個屬性。一個方法,兩種用途!因此需要增加下列 JavaScript 代碼:
var newImage = document.createElement("img");
newImage.setAttribute("src", "rabbit-hat.gif");
它建立一個圖片元素然後設定適當的資源屬性。現在,HTML 應該如清單 4 所示。
清單 4. 使用 DOM 建立新圖片
<html>
<head>
<title>Magic Hat</title>
<script language="JavaScript">
function showRabbit() {
var hatImage = document.getElementById("topHat");
var newImage = document.createElement("img");
newImage.setAttribute("src", "rabbit-hat.gif");
}
</script>
</head>
<body>
<h1 align="center">Welcome to the DOM Magic Shop!</h1>
<form name="magic-hat">
<p align="center">
<img src="topHat.gif" id="topHat" />
<br /><br />
<input type="button" value="Hocus Pocus!" />
</p>
</form>
</body>
</html>
可以載入該頁面,但是不要期望有任何改變,因為目前所做的修改實際上還沒有影響頁面。另外,如果再看看工作清單中的第 5 步,就會發現還沒有調用我們的 JavaScript 函數!
獲得原始圖片的父元素
現在有了要插入的圖片,還需要找到插入的地方。但是不能將其插入到已有的圖片中,而是要將其插入到已有圖片之前然後再刪除原來的圖片。為此需要知道已有圖片的父元素,實際上這就是插入和刪除操作的真正關鍵所在。
應該記得,前面的文章中曾經指出 DOM 確實把網頁看成一棵樹,即節點的階層。每個節點都有父節點(樹中更高層次的節點,該節點是它的一個子級),可能還有自己的子節點。對於圖片來說,它沒有子級 —— 要記住圖片是空元素,但是它肯定有父節點。甚至不需要知道父節點是什麼,但是需要訪問它。
為此,只要使用每個 DOM 節點都有的 parentNode 屬性即可,比如:
var imgParent = hatImage.parentNode;
確實非常簡單!可以肯定這個節點有子節點,因為已經有了一個:原來的圖片。此外,完全不需要知道它是一個 div、p 或者頁面的 body,都沒有關係!
插入新圖片
現在得到了原來圖片的父節點,可以插入新的圖片了。很簡單,有多種方法可以添加子節點:
1、insertBefore(newNode, oldNode)
2、appendChild(newNode)
因為希望把新圖片放在舊圖片的位置上,需要使用 insertBefore()(後面還要使用 removeChild() 方法)。可使用下面這行 JavaScript 代碼把新圖片元素插入到原有圖片之前:
var imgParent = hatImage.parentNode;
imgParent.insertBefore(newImage, hatImage);
現在原圖片的父元素有了兩個 子項目:新圖片和緊跟在後面的舊圖片。必須指出,這裡包圍 這些圖片的內容沒有變,而且這些內容的順序也和插入之前完全相同。僅僅是這個父節點中增加了一個子節點,即舊圖片之前的新圖片。
刪除舊圖片
現在只需要刪除舊圖片,因為網頁中只需要新圖片。很簡單,因為已經得到了舊圖片元素的父節點。只要調用 removeChild() 並把需要刪除的節點傳遞給它即可:
var imgParent = hatImage.parentNode;
imgParent.insertBefore(newImage, hatImage);
imgParent.removeChild(hatImage);
現在,用新圖片替換舊圖片的工作已基本完成了。HTML 應該如清單 5 所示。
清單 5. 用新圖片替換舊圖片
<html>
<head>
<title>Magic Hat</title>
<script language="JavaScript">
function showRabbit() {
var hatImage = document.getElementById("topHat");
var newImage = document.createElement("img");
newImage.setAttribute("src", "rabbit-hat.gif");
var imgParent = hatImage.parentNode;
imgParent.insertBefore(newImage, hatImage);
imgParent.removeChild(hatImage);
}
</script>
</head>
<body>
<h1 align="center">Welcome to the DOM Magic Shop!</h1>
<form name="magic-hat">
<p align="center">
<img src="topHat.gif" id="topHat" />
<br /><br />
<input type="button" value="Hocus Pocus!" />
</p>
</form>
</body>
</html>
串連 JavaScript
最後一步,可能也是最簡單的,就是把 HTML 表單串連到剛剛編寫的 JavaScript 函數。需要每當使用者點擊 Hocus Pocus! 按鈕的時候運行 showRabbit() 函數。為此只要向 HTML 中增加一個簡單的 onClick 事件處理常式即可。
<input type="button" value="Hocus Pocus!" onClick="showRabbit();" />
這種簡單的 JavaScript 編程應該非常容易了。將其添加到 HTML 頁面中,儲存它然後在 網頁瀏覽器中開啟。頁面初看起來應該和圖 1 相同,但是點擊 Hocus Pocus! 後應該看到圖 3 所示的結果。
圖 3. 兔子戲法
替換圖片,簡單的辦法
如果回顧替換圖片的步驟,再看看節點的各種方法,可能會注意到方法 replaceNode()。該方法可用於把一個節點替換為另一個節點。再考慮一下前面的步驟:
1、建立新的 img 元素。
2、訪問當前 img 元素的父元素,也就是它的容器。
3、在已有 img 元素之前 插入新的 img 元素作為該容器的子項目。
4、刪除原來的 img 元素。
5、串連起來以便在使用者點擊 Hocus Pocus! 的時候調用剛剛建立的 JavaScript 函數。
使用 replaceNode() 可以減少需要的步驟數。可以將第 3 步和第 4 步合并在一起:
1、建立新的 img 元素。
2、訪問當前 img 元素的父元素,也就是它的容器。
3、用建立的新元素替換舊的 img 元素。
4、串連起來以便在使用者點擊 Hocus Pocus! 的時候調用剛剛建立的 JavaScript 函數。
這看起來不是什麼大事,但確實能夠簡化代碼。清單 6 說明了這種修改:去掉了 insertBefore() 和 removeChild() 方法調用。
清單 6. 用新圖片替換舊圖片(一步完成)
<html>
<head>
<title>Magic Hat</title>
<script language="JavaScript">
function showRabbit() {
var hatImage = document.getElementById("topHat");
var newImage = document.createElement("img");
newImage.setAttribute("src", "rabbit-hat.gif");
var imgParent = hatImage.parentNode;
imgParent.replaceChild(newImage, hatImage);
}
</script>
</head>
<body>
<h1 align="center">Welcome to the DOM Magic Shop!</h1>
<form name="magic-hat">
<p align="center">
<img src="topHat.gif" id="topHat" />
<br /><br />
<input type="button" value="Hocus Pocus!" onClick="showRabbit();" />
</p>
</form>
</body>
</html>
當然這不是什麼大的修改,但是說明了 DOM 編碼中一件很重要的事:執行一項任務通常有多種方法。如果仔細審閱可用 DOM 方法看看是否有更簡單的方法可以完成任務,很多時候都會發現可以將四五個步驟壓縮為兩三個步驟。
替換圖片,(真正)簡單的辦法
既然指出了執行一項任務幾乎總是有更簡單的方法,現在就說明用兔子圖片替換帽子圖片的簡單得多 的辦法。閱讀本文的過程中有沒有想到這種方法?提示一下:與屬性有關。
要記住,圖片元素很大程度上是由其 src 屬性控制的,他引用了某個地方的檔案(不論是本地 URI 還是外部 URL)。到目前為止,我們一直用新圖片替換圖片節點,但是直接修改已有圖片的 src 屬性要簡單得多!這樣就避免了建立新節點、尋找父節點和替換舊節點的所有工作,只要一步就能完成了:
hatImage.setAttribute("src", "rabbit-hat.gif");
這樣就夠了!看看清單 7,它顯示了這種解決方案,包括整個網頁。
清單 7. 修改 src 屬性
<html>
<head>
<title>Magic Hat</title>
<script language="JavaScript">
function showRabbit() {
var hatImage = document.getElementById("topHat");
hatImage.setAttribute("src", "rabbit-hat.gif");
}
</script>
</head>
<body>
<h1 align="center">Welcome to the DOM Magic Shop!</h1>
<form name="magic-hat">
<p align="center">
<img src="topHat.gif" id="topHat" />
<br /><br />
<input type="button" value="Hocus Pocus!" onClick="showRabbit();" />
</p>
</form>
</body>
</html>
這是 DOM 最棒的一點:更新屬性的時候網頁馬上就會改變。只要圖片指向新的檔案,瀏覽器就載入該檔案,頁面就更新了。不需要重新載入,甚至不需要建立新的圖片元素!結果仍然和圖 3 相同,只不過代碼簡單得多了。
把兔子藏起來
現在網頁看起來很漂亮,但是仍然有點原始。雖然兔子從帽子中跳出來了,但是螢幕下方的按鈕仍然顯示 Hocus Pocus! 和調用 showRabbit()。這就是說如果在兔子出來之後仍然點擊按鈕,就是在浪費處理時間。更重要的是,它毫無用處,而沒有用的按鈕不是好東西。我們來看看能否利用 DOM 再作一些修改,無論兔子在帽子裡還是出來都讓這個按鈕派上用場。
修改按鈕的標籤
最簡單的是當使用者點擊按鈕之後改變它的標籤。這樣就不會看起來像還有什麼魔法,網頁中最糟糕的就是暗示使用者錯誤的東西。在修改按鈕的標籤之前需要訪問該節點,而在此之前需要引用按鈕 ID。這是老套路了,清單 8 為按鈕增加了 id 屬性。
清單 8. 增加 id 屬性
<html>
<head>
<title>Magic Hat</title>
<script language="JavaScript">
function showRabbit() {
var hatImage = document.getElementById("topHat");
hatImage.setAttribute("src", "rabbit-hat.gif");
}
</script>
</head>
<body>
<h1 align="center">Welcome to the DOM Magic Shop!</h1>
<form name="magic-hat">
<p align="center">
<img src="topHat.gif" id="topHat" />
<br /><br />
<input type="button" value="Hocus Pocus!" id="hocusPocus"
onClick="showRabbit();" />
</p>
</form>
</body>
</html>
現在用 JavaScript 訪問按鈕很簡單了:
function showRabbit() {
var hatImage = document.getElementById("topHat");
hatImage.setAttribute("src", "rabbit-hat.gif");
var button = document.getElementById("hocusPocus");
}
當然,您可能已經輸入了下面這行 JavaScript 來改變按鈕的標籤值。這裡再次用到了 setAttribute():
function showRabbit() {
var hatImage = document.getElementById("topHat");
hatImage.setAttribute("src", "rabbit-hat.gif");
var button = document.getElementById("hocusPocus");
button.setAttribute("value", "Get back in that hat!");
}
通過這個簡單的 DOM 操作,兔子跳出來之後按鈕的標籤馬上就會改變。現在,HTML 和完成的 showRabbit() 函數如清單 9 所示。
清單 9. 完成的網頁
<html>
<head>
<title>Magic Hat</title>
<script language="JavaScript">
function showRabbit() {
var hatImage = document.getElementById("topHat");
hatImage.setAttribute("src", "rabbit-hat.gif");
button.setAttribute("value", "Get back in that hat!");
}
</script>
</head>
<body>
<h1 align="center">Welcome to the DOM Magic Shop!</h1>
<form name="magic-hat">
<p align="center">
<img src="topHat.gif" id="topHat" />
<br /><br />
<input type="button" value="Hocus Pocus!" id="hocusPocus"
onClick="showRabbit();" />
</p>
</form>
</body>
</html>
把兔子收回去
從此新的按鈕標籤中可能已經猜到,現在要把兔子收回帽子中去。基本上和放兔子出來完全相反:將圖片的 src 屬性再改回舊圖片。建立一個新的 JavaScript 函數來完成這項任務:
function hideRabbit() {
var hatImage = document.getElementById("topHat");
hatImage.setAttribute("src", "topHat.gif");
var button = document.getElementById("hocusPocus");
button.setAttribute("value", "Hocus Pocus!");
}
實際上僅僅把 showRabbit() 函數的功能翻轉了過來。將圖片改為原來的沒有兔子的大禮帽,抓取按鈕,將標籤改為 Hocus Pocus!
事件處理常式
現在這個應用程式範例有一個大問題:雖然按鈕的標籤 改變了,但是單擊按鈕時的動作沒有 變。幸運的是,當使用者單擊按鈕時可以使用 DOM 改變事件或者發生的動作。因此,如果按鈕上顯示 Get back in that hat!,點擊的時候需要運行 hideRabbit()。相反,一旦兔子藏了起來,按鈕又返回來運行 showRabbit()。
避免使用 addEventHandler()
除了 onclick 屬性外,還有一個方法可用於添加 onClick 或 onBlur 這樣的事件處理常式,毫不奇怪這個方法就叫 addEventHandler()。不幸的是,Microsoft Internet Explorer 不支援這個方法,如果在 JavaScript 中使用它,就會有數百萬 Internet Explorer 使用者除了錯誤外從網頁中什麼也看不到(可能還有抱怨)。不使用這個方法,應用本文中介紹的辦法也能達到同樣的效果,而且在 Internet Explorer 上也有效。
查看 HTML 就會發現這裡處理的事件是 onClick。在 JavaScript 中,可以通過按鈕的 onclick 的屬性來引用該事件。(要注意,在 HTML 中該屬性通常稱為 onClick,其中 C 大寫;而在 JavaScript 中則稱為 onclick,全部小寫。)因此可以改變按鈕觸發的事件:只要賦給 onclick 屬性一個新的函數。
但是有點細微的地方:onclick 屬性需要提供函數引用——不是函數的字串名稱,而是函數本身的引用。在 JavaScript 中,可以按名稱引用函數,不需要帶括弧。因此可以這樣修改點擊按鈕時執行的函數:
button.onclick = myFunction;
因此在 HTML 中作這種修改很簡單。看看清單 10,它切換按鈕觸發的函數。
清單 10. 改變按鈕的 onClick 函數
<html>
<head>
<title>Magic Hat</title>
<script language="JavaScript">
function showRabbit() {
var hatImage = document.getElementById("topHat");
hatImage.setAttribute("src", "rabbit-hat.gif");
var button = document.getElementById("hocusPocus");
button.setAttribute("value", "Get back in that hat!");
button.onclick = hideRabbit;
}
function hideRabbit() {
var hatImage = document.getElementById("topHat");
hatImage.setAttribute("src", "topHat.gif");
var button = document.getElementById("hocusPocus");
button.setAttribute("value", "Hocus Pocus!");
button.onclick = showRabbit;
}
</script>
</head>
<body>
<h1 align="center">Welcome to the DOM Magic Shop!</h1>
<form name="magic-hat">
<p align="center">
<img src="topHat.gif" id="topHat" />
<br /><br />
<input type="button" value="Hocus Pocus!" id="hocusPocus"
onClick="showRabbit();" />
</p>
</form>
</body>
</html>
這樣就得到了一個完成的、可以使用的 DOM 應用程式。自己試試吧!
結束語
現在您應該非常熟悉 DOM 了。前面的文章介紹了使用 DOM 所涉及到的基本概念,詳細地討論了 API,現在又建立一個簡單的基於 DOM 的應用程式。一定要花點時間仔細閱讀本文,並自己嘗試一下。
雖然這是專門討論文件物件模型的系列文章的最後一期,但肯定還會看到其他關於 DOM 的文章。事實上,如果在 Ajax 和 JavaScript 世界中不使用 DOM 就很難做多少事,至少在一定程度上如此。無論要建立複雜的反白還是移動效果,或者僅僅處理文字區塊或圖片,DOM 都提供了一種非常簡單易用的訪問 Web 頁面的方式。
如果對如何使用 DOM 仍然感覺沒有把握,花點時間溫習一下這三篇文章;本系列的其他文章在使用 DOM 的時候不再多作解釋,讀者也不希望迷失在這些細節之中而忽略關於其他概念的重要訊息,比如 XML 和 JSON。為了保證能夠熟練地使用 DOM,自己編寫幾個基於 DOM 的應用程式試試,這樣就很容易理解後面將要討論的一些資料格式問題了。
完整的樣本,包括 HTML 和圖形、代碼下載:
[hide][/hide]