2008 年 6 月 10 日 目前,Web 應用程序開發(fā)幾乎等同于 Ajax 開發(fā)。Ajax 不再是在特殊情況下才添加到應用程序的補充物了。它現(xiàn)在已經(jīng)成為 Web 開發(fā)不可或缺的一部分。對于某些人而言,用 Ajax 增強應用程序曾經(jīng)是一項極具挑戰(zhàn)的任務。處理跨瀏覽器限制、編寫大量復雜的 JavaScript 以及熟悉其中的數(shù)字編碼,這些只不過是 Ajax 開發(fā)人員所面臨的挑戰(zhàn)的一小部分。還好,目前已經(jīng)有幾種開源 JavaScript 庫,使上述操作更加容易。在這個包含三個部分的系列文章的第 1 部分,您將使用 Prototype JavaScript 庫和 script.aculo.us 創(chuàng)建一個用來管理歌曲的 Ajax 應用程序。 本系列文章包含三個部分,使用兩個獨立的開源項目(Prototype JavaScript 庫和 script.aculo.us)為 Web 2.0 站點創(chuàng)建優(yōu)秀的 Ajax 應用程序。在此系列文章的第 1 部分,我們先介紹 Prototype JavaScript 庫(參見 參考資料 獲得有關(guān)鏈接)。本文使用的是 Prototype 當前的最新版本 1.6.0.2(參見 參考資料)。Ajax 涉及到動態(tài)數(shù)據(jù),所以需要用到服務器端的技術(shù)。在本文中,我們將 PHP 5.2.1 與 Apache 2.0.59 和 MySQL 5.0.41 結(jié)合起來一起使用。(參見 參考資料)。當然,您也可以選擇自己的編程語言、Web 服務器和數(shù)據(jù)庫。 | developerWorks Ajax 資源中心 查看 Ajax 參考資料中心, 您可以從這個一站式站點獲得關(guān)于開發(fā) Ajax 應用程序的免費工具、代碼和信息。由 Ajax 專家 Jack Herrington 主持的 活動 Ajax 社區(qū)論壇 將提供與其他 Ajax 應用程序開發(fā)人員交流的機會,幫助您解決困難。 | | Prototype 簡介 如果進行查找,可以找到很多 JavaScript 庫。原因有兩個:首先,JavaScript 是瀏覽器語言,因此也是軟件開發(fā)的關(guān)鍵部分。許多人都在編寫 JavaScript 代碼,所以就有很多 JavaScript 庫存在。其次,JavaScript 很復雜,不同瀏覽器間的差異常常使 JavaScript 開發(fā)多少有些痛苦。幸運的是,JavaScript 庫通常都提供了各種抽象來減輕這種痛苦。Prototype 就是這樣的 JavaScript 庫。 Prototype 是一種相當寬泛的庫,具有很多功能。它的功能可以簡化普通任務,并側(cè)重于 Ajax。Prototype 提供一種很酷的方式,實現(xiàn)了在 JavaScript 內(nèi)繼承 Java? 和 C++ 風格、對 HTML DOM 元素的擴展以及用于 JSON 的實用工具。在本文中,您將重點學習 Prototype 能為 Ajax 做些什么,同時還會了解幾個 Prototype 的其他功能。
使用 Prototype 的 Ajax 庫 Prototype 具有很多為了幫助您學習 Ajax 開發(fā)而設計的功能。Prototype 如此受歡迎的原因之一就是它不限制您如何進行 Ajax 編程。比如,有兩種常用的模式可以響應 XMLHttpRequest (Ajax 內(nèi)的底層機制):一種方式是使用用來重繪部分屏幕的 HTML 進行響應;一種是用數(shù)據(jù)進行響應,而將解析數(shù)據(jù)和重繪留給其他的 JavaScript 代碼處理。Prototype 支持這兩種模式。讓我們來看看它是如何啟用第一種模式的,即用 HTML 響應。 處理 HTML 通過 Prototype,在 Ajax 響應上處理服務器生成的 HTML 十分容易。它有一個專門為此構(gòu)建的 API,并允服務器端代碼只生成 HTML 片斷,而不要求任何特定于 Prototype 的格式。學習這個知識的最好途徑是觀察其實際運行。 在示例中,我們在構(gòu)建一個用來管理歌曲的應用程序。該應用程序需要用到數(shù)據(jù)庫。清單 1 給出了創(chuàng)建此數(shù)據(jù)庫的腳本: 清單 1. 創(chuàng)建數(shù)據(jù)庫的腳本 CREATE TABLE 'songs' ( 'id' int(11) NOT NULL auto_increment, 'title' varchar(120) NOT NULL, 'artist' varchar(120) NOT NULL, 'genre' varchar(80) default NULL, 'album' varchar(120) default NULL, 'year' year(4) default '2008', PRIMARY KEY ('id') ) ENGINE=MyISAM AUTO_INCREMENT=5 DEFAULT CHARSET=latin1 | 如前所述,我使用的是 MySQL,但您輕松地調(diào)整此腳本使它適用于另一個數(shù)據(jù)庫。使用提供的腳本創(chuàng)建一些示例數(shù)據(jù)(參見 下載 部分)。 現(xiàn)在讓我們創(chuàng)建一個純 HTML 頁面來查看音樂庫,如清單 2 所示: 清單 2. index.html 頁面 <html> <head> <title>MyTunes Library</title> <script type="text/javascript" src="prototype.js"></script> <script type="text/javascript"> function loadTunes(){ new Ajax.Updater('tunesBox', 'list.php', { method: 'get' }); } </script> <link rel="stylesheet" href="tunes.css" type="text/css"/> </head> <body onload="loadTunes();"> <div id="pageTitle">MyTunes</div> <div id="tunesBox"> <img id="spinner" src="wait_spinner.gif" height="33" width="33"/> </div> </body> </html> | 接下來的事情將會很有意思。清單 2 所示的頁面是靜態(tài)的純 HTML 和 JavaScript 代碼。這意味著瀏覽器可以緩存這個頁面從而減輕服務器的負荷。當加載該頁面時,將調(diào)用 JavaScript 函數(shù) loadTunes() 。您將在此處使用 Prototype,尤其它的 Ajax.Updater 對象。Prototype 處理創(chuàng)建適合于瀏覽器的傳輸機制(用于發(fā)送 Ajax 請求)的任務。這個任務曾經(jīng)是個大事情,但由于 Prototype 這樣的庫的流行,現(xiàn)在已經(jīng)沒有這種感覺了。 此對象(清單 2)接受頁面上一個元素的 ID、Web 服務器的一個 URL 和一個表示選項的映射鍵值對。對于這種情況,我們設置的 ID 是 tunesBox ??梢钥吹巾撁嫔嫌幸粋€具有此 ID 的 div 。我們設置的 URL 是 list.php 。Prototype 將會調(diào)用這個 URL、接受其響應并將其轉(zhuǎn)儲到 tunesBox div。最后,我們指定惟一選項是使用 HTTP GET 而不是(默認)的 POST 。使用 POST 也可以,但在這里我們遵循 REST 標準,使用 GET ,因為我們只讀數(shù)據(jù)。下面是 Prototype 已經(jīng)調(diào)用的 PHP 文件(參見清單 3): 清單 3. list.php 文件 <?php $message = ""; $sql = "select * from songs"; try{ require_once(dirname(__FILE__)."/db.php"); $result = mysql_query($sql,$conn); $list = array(); if (!$result) { $message = "Could not successfully run query ($sql) from DB: " . mysql_error(); } else if (mysql_num_rows($result) == 0) { $message = "Your library is empty!"; } else { while ($row = mysql_fetch_assoc($result)){ array_push($list, $row); } } mysql_free_result($result); } catch (Exception $e) { $message = "Sorry there was an error: " . mysql_error(); } ?> <?php if ($message): ?> <div><span class="error"><?= $message ?></span></div> <?php else: ?> <table border="1" width="100%" cellpadding="8"> <thead> <tr> <td>Name</td> <td>Artist</td> <td>Album</td> <td>Genre</td> <td>Year</td> </tr> </thead> <tbody> <?php foreach($list as $song): ?> <tr> <td><a href="edit.html?id=<?= $song["id"] ?>"><?= $song["title"] ?></a></td> <td><?= $song["artist"] ?></td> <td><?= $song["album"] ?></td> <td><?= $song["genre"] ?></td> <td><?= $song["year"] ?></td> </tr> <?php endforeach; ?> </tbody> </table> <?php endif; ?> | 這是一段很簡單的 PHP 腳本。它先查詢之前創(chuàng)建的表,然后在結(jié)果集上進行迭代,創(chuàng)建一個 HTML 表。這里沒有什么特別要說明的。將 JavaScript、CSS 和其他 PHP 文件(能在下載代碼中找到)部署到 PHP 服務器并在瀏覽器上顯示結(jié)果,如圖 1 所示: 圖 1. MyTunes 該頁面將加載并無縫地使用 Ajax 和 Prototype 從服務器獲得歌曲表。想要更詳細地了解其中的原理,可以使用流行的 Firefox 擴展 Firebug 等工具(參見 參考資料 獲得相關(guān)鏈接)。圖 2 顯示了其輸出: 圖 2. 針對 MyTunes 的 Firebug 控制臺 單擊 Net>XHR 選項卡,Prototype 將創(chuàng)建一個 XMLHttpRequest 。它還會為響應創(chuàng)建 JavaScript 處理函數(shù)。該處理函數(shù)實際上使用來自 PHP 腳本的響應動態(tài)地更改 HTML DOM。從清單 2 中可以看到所有這些只由一行代碼實現(xiàn)。再也沒有比這更簡單的 Ajax 開發(fā)了。如前所述,這是 Ajax 的常見模式。另一種模式是只從服務器返回數(shù)據(jù),而不返回標記(HTML)。讓我們看看 Prototype 是如何為這種方法提供幫助的。 處理數(shù)據(jù) 讓服務器為 Ajax 請求返回 HTML 固然很方便,但也有缺點。由于標記和數(shù)據(jù)都由服務器返回,這會占據(jù)一定的帶寬。如果 HTML 需要 JavaScript (至少事件偵聽程序)附加到其上,這種方法也會變得非常復雜,使您不得不在服務器上跟蹤客戶機的狀態(tài)。只從服務器上只返回純數(shù)據(jù)有時簡單些。同樣,Prototype 也讓這個過程變得很簡單。讓我們通過示例加以說明。 您可能已經(jīng)在前面的示例中注意到,每首歌的名稱實際上都是鏈接到另一個 edit.html 頁面,如清單 4 所示: 清單 4. edit.html 頁面 <html> <head> <title>Edit a Song</title> <script type="text/javascript" src="prototype.js"></script> <script type="text/javascript" src="editor.js"></script> <link rel="stylesheet" href="tunes.css"/> </head> <body onload="loadSong();"> <div id="pageTitle">Edit Song</div> <div id="tunesBox"> <span id="spinner"> <img src="wait_spinner.gif" height="33" width="33"/> </span> <form id="songForm" onsubmit="catchSubmit();"> <input type="hidden" name="id" id="id"/> <div id="name"> <span id="nameLbl">Name:</span> <span id="title" onclick="edit(this)"?></span> </div> <div id="artistDiv"> <span id="artistLbl">Artist:</span> <span id="artist" onclick="edit(this)"?></span> </div> <div id="albumDiv"> <span id="albumLbl">Album:</span> <span id="album" onclick="edit(this)"?></span> </div> <div id="genreDiv"> <span id="genreLbl">Genre:</span> <span id="genre" onclick="edit(this)"?></span> </div> <div id="yearDiv"> <span id="yearLbl">Year:</span> <span id="year" onclick="edit(this)"?></span> </div> </form> </div> <div class="backLink"> <a href="index.html">Back to MyTunes Library</a> </div> </body> </html> | 同樣,這也是個靜態(tài)頁面,而且是純 HTML。當頁面加載時,將調(diào)用一個稱為 loadSong() 的 JavaScript 函數(shù)。該函數(shù)在單獨的文件 editor.js 內(nèi)。讓我們來看看這個 loadSong 函數(shù)(參見清單 5): 清單 5. loadSong() JavaScript 函數(shù) function loadSong(){ var params = window.location.search.parseQuery(); function loadSong(){ var params = parseQueryString(); var id = params["id"]; // create handler that will be invoked // when response is received from server var handler = function(xhr){ // use responseJSON property added by Prototype var json = xhr.responseJSON; // check for error if (json.error){ // display the error } var song = json.song; // clear the spinner // use Prototype's $() shortcut notation $("spinner").innerHTML = ""; // set the display data $("id").value = song.id; $("title").innerHTML = song.title; $("artist").innerHTML = song.artist; $("album").innerHTML = song.album; $("genre").innerHTML = song.genre; $("year").innerHTML = song.year; }; // create options for var options = { method : "get", onSuccess : handler, parameters : { "id" : id } }; // send the request new Ajax.Request("song.php", options); } | 表達式 window.location.search 使您可以訪問頁面的 URL 查詢字符串。在這種情況下,該字符串類似于 “?id=2”。然后,會使用一個 parseQuery() 函數(shù)。這并不是 window.location.search 對象固有的特殊函數(shù) — 該對象只是一個字符串。parseQuery() 函數(shù)是 Prototype 添加到 JavaScript 的字符串類的函數(shù),所以可以在任何字符串上調(diào)用。parseQueryString() 函數(shù)所執(zhí)行的功能正如其名所示。它將頁面 URL 的查詢字符串轉(zhuǎn)變成名稱/值對。這就讓您能夠獲得由前一頁傳遞過來的 ID 參數(shù)。然后您可以使用此參數(shù)調(diào)用 PHP 腳本 song.php。它是服務器端腳本,用來加載歌曲并將其返回給您。其代碼如清單 6 所示: 清單 6. song.php 腳本 <?php header('Content-Type: application/json'); $message = ""; $resp = array(); $sql = "select * from songs where id =" . $_REQUEST["id"]; try{ require_once(dirname(__FILE__)."/db.php"); $result = mysql_query($sql,$conn); $song = array(); if (!$result) { $message = "Could not successfully run query ($sql) from DB: " . mysql_error(); } else if (mysql_num_rows($result) == 0) { $message = "No song found with that id!"; } else { $song = mysql_fetch_assoc($result); } $resp["song"] = $song; mysql_free_result($result); } catch (Exception $e) { $message = "Sorry there was an error: " . mysql_error(); } $resp["error"] = $message; echo(json_encode($resp)); ?> | 清單 6 查詢此數(shù)據(jù)庫。注意,您發(fā)回的是 JSON 代碼。雖然原本可以使用 XML 進行數(shù)據(jù)傳遞,但在客戶機上,JSON 更容易處理,也更有效。要正確地發(fā)回 JSON,需要將內(nèi)容類型設置為 application/json。這不僅對瀏覽器來說是正確的,而且也是 Prototype 的要求,它使 Prototype 將響應識別為 JSON,進而讓處理過程更為容易。最后,要將數(shù)據(jù)作為 JSON 返回,必須使用 PHP 內(nèi)置的 json_encode 函數(shù)(您所使用的編程語言都有類似的庫函數(shù))。 現(xiàn)在,我們回到 清單 5, 看看所創(chuàng)建的響應處理程序(var handler = function ......)。Prototype 自動地對所返回 JSON 進行 safe-eval;這通過傳遞給處理程序的對象上的 responseJSON 屬性來提供。這使您能夠輕松地地訪問數(shù)據(jù),比如 song.title 等。現(xiàn)在,讓我們看看以 $("spinner") 開頭的那一行代碼。$() 符號是 Prototype 提供的快捷方式。它也是類似的 document.getElementById() 函數(shù)的快捷方式。所以 $("spinner") 基本上等同于 document.getElementById("spinner") 。實際上,根據(jù)對象類型,Prototype 會向返回對象添加一些額外的方法(稍后介紹)。借助 Prototype,可以使用簡單的語法,比如 $("title").innerHTML = song.title ,來初始化顯示。在瀏覽器中,初始化后的頁面如圖 3 所示: 圖 3. Edit 頁面:初始化后 同樣,您可以使用 Firebug 查看從服務器獲取的數(shù)據(jù),如圖 4 所示: 圖 4. Firebug 顯示加載的歌曲數(shù)據(jù) 現(xiàn)在,標題顯示的是 “Edit Song”,但此頁面看上去是只讀的。單擊任何一個屬性值,就可以對它進行編輯,如圖 5 所示: 圖 5. 編輯歌曲 如果關(guān)閉此選項卡或按 Enter,數(shù)據(jù)就會發(fā)送給服務器并在數(shù)據(jù)庫中更新。將數(shù)據(jù)發(fā)送給服務器的代碼也來自 editor.js 文件,如清單 7 所示: 清單 7. 用于更新歌曲的 JavaScript 代碼 function makeText(input){ // save record var formData = $("songForm").serialize(true); saveData(formData); // go back to display input.parentNode.innerHTML = input.value; } function saveData(song){ var handler = function(xhr){ var json = xhr.responseJSON; if (json.error){ // display the error } }; var options = { method : "post", onSuccess : handler, parameters : song }; new Ajax.Request("update.php", options); } | 代碼開始處是 makeText() 函數(shù)?,F(xiàn)在,當使用 Prototype shortcut $("songForm") 時,就會得到其增強了的 Form 對象。這會帶來一個新的可調(diào)用的函數(shù) serialize ,該函數(shù)獲取表單內(nèi)的所有輸入字段并創(chuàng)建這些字段的名稱和值的哈希表。您可以將這個哈希表傳遞給 saveData() 函數(shù)。Prototype 再次被用來向服務器發(fā)送一個 Ajax 請求。注意,這次使用了一個 POST,原因是正在更改數(shù)據(jù) 和調(diào)用 update.php,參見清單 8: 清單 8. update.php 腳本 <?php header('Content-Type: application/json'); $message = ""; $clause = ""; foreach ($_POST as $key => $value){ if ($key != "id"){ $clause = $key . " = '" . $value . "' "; } } $sql = "update songs set " . $clause . " where id=" . $_POST["id"]; try{ require_once(dirname(__FILE__)."/db.php"); $result = mysql_query($sql,$conn); if (!$result) { $message = "Could not successfully run query ($sql) from DB: " . mysql_error(); } mysql_free_result($result); } catch (Exception $e) { $message = "Sorry there was an error: " . mysql_error(); } $resp["error"] = $message; echo(json_encode($resp)); ?> | 在這里您又會看到一個很簡單的 PHP 處理。注意,Ajax 請求把歌曲的 ID 和被修改的字段發(fā)送回來,這是 Ajax 處理數(shù)據(jù)庫修改高效性的一個很好的例子。您可以再次在 Firebug 中看到所有這些內(nèi)容,如圖 6 所示: 圖 6. Firebug 顯示歌曲更新 Prototype 不僅為發(fā)送 Ajax 請求提供了便利的方法,也為訪問被發(fā)送的數(shù)據(jù)、訪問響應數(shù)據(jù)及修改顯示提供了便利。 結(jié)束語 Ajax 技術(shù)已經(jīng)不是幾年前的未知疆界了。聰明的開發(fā)人員可以利用大多數(shù)免費的開源庫來減少 Ajax 編程偶爾會碰到的復雜性。Prototype 是一個以簡化為目標的功能強大的庫。除了全方位簡化 Ajax 的開發(fā)外,它也能夠與 Ajax 內(nèi)容驅(qū)動(從服務器返回的 HTML)及數(shù)據(jù)驅(qū)動方法一起很好地工作。它是所有資深 Web 開發(fā)人員工具箱中的一個必備工具。
|