Ajax(Asynchronous JavaScript + XML)正在迅速地成為時(shí)髦的技術(shù),它可以為在瀏覽器中運(yùn)行的 Web 應(yīng)用程序提供具有桌面質(zhì)量的軟件特性。這個(gè)分三部分的系列討論如何使用開(kāi)放源碼技術(shù)開(kāi)發(fā)端到端 Ajax 應(yīng)用程序,本文是這個(gè)系列的第二篇文章。 在這個(gè)分三部分的系列的 第 1 部分 中,了解了 Firefox、Zend Core 和 MySQL 等開(kāi)放源碼技術(shù)的重要特性。我們討論了一個(gè)比較復(fù)雜的銀行場(chǎng)景,這個(gè)場(chǎng)景跨越 Ajax 應(yīng)用程序的所有三層。還設(shè)置了開(kāi)發(fā)端到端 Ajax 應(yīng)用程序所需的數(shù)據(jù)庫(kù)服務(wù)器、中間層服務(wù)器和基于 Eclipse 的 IDE。在本系列的第 2 部分中,將開(kāi)發(fā)銀行場(chǎng)景的一些部分。具體地說(shuō),將使用 MySQL 數(shù)據(jù)庫(kù)創(chuàng)建一個(gè)后端數(shù)據(jù)庫(kù)。我們將研究幾個(gè) MySQL 命令行工具,并使用這些工具連接數(shù)據(jù)庫(kù)、在數(shù)據(jù)庫(kù)中創(chuàng)建、定義和填充與銀行相關(guān)的數(shù)據(jù)。然后,開(kāi)發(fā)一個(gè)中間層 PHP 模塊來(lái)提供銀行的業(yè)務(wù)邏輯,這個(gè)模塊使用 ODBC 連接 MySQL 數(shù)據(jù)庫(kù)。最后,開(kāi)發(fā)一個(gè)銀行門戶,用戶可以通過(guò)這個(gè)簡(jiǎn)單的瀏覽器用戶界面與這個(gè)端到端應(yīng)用程序進(jìn)行交互,這個(gè)應(yīng)用程序不久之后就可以在 Zend Core 上運(yùn)行。 簡(jiǎn)介 | 請(qǐng)?jiān)L問(wèn) Ajax 技術(shù)資源中心,這是有關(guān) Ajax 編程模型信息的一站式中心,包括很多文檔、教程、論壇、blog、wiki 和新聞。任何關(guān)于 Ajax 的新信息都能在這里找到。 | | 正如 第 1 部分 所指出的,這個(gè)銀行場(chǎng)景主要提供銀行出納員執(zhí)行的基本服務(wù)。如果您還不了解這個(gè)場(chǎng)景,請(qǐng)閱讀 第 1 部分??蛻魯?shù)據(jù)是這個(gè)場(chǎng)景的重要部分。對(duì)于本系列的場(chǎng)景,所有客戶數(shù)據(jù)將同時(shí)填充進(jìn)數(shù)據(jù)庫(kù)表中。在此之后,可以通過(guò) Zend Core 提供的 ODBC MySQL 驅(qū)動(dòng)程序獲取和更新存儲(chǔ)的客戶數(shù)據(jù)。處理了客戶數(shù)據(jù)之后,重點(diǎn)轉(zhuǎn)移到提供銀行出納功能所需的核心銀行邏輯。我們開(kāi)發(fā)一個(gè) PHP 代碼模塊來(lái)提供核心銀行邏輯,并使用 ODBC 進(jìn)行必要的數(shù)據(jù)庫(kù)訪問(wèn)。使用 Zend Core 和 PHP 實(shí)現(xiàn)銀行邏輯的主要優(yōu)點(diǎn)是,可以利用內(nèi)置的 MySQL 支持。 在建立數(shù)據(jù)庫(kù)并開(kāi)發(fā) PHP 模塊之后,為銀行出納員提供執(zhí)行四個(gè)核心功能的用戶界面。我們通過(guò)一個(gè)瘦客戶機(jī)訪問(wèn) PHP 模塊中封裝的核心銀行邏輯。具體地說(shuō),這個(gè)基于 Web 的瘦客戶機(jī)是按照 Ajax 風(fēng)格生成的:XHTML、Cascading Style Sheet(CSS)、JavaScript 和 XMLHttpRequest(XHR)。它為銀行出納員提供執(zhí)行核心銀行功能的簡(jiǎn)單用戶界面。這個(gè)瀏覽器用戶界面還演示瀏覽器客戶機(jī)邏輯與服務(wù)器端 PHP 邏輯進(jìn)行網(wǎng)絡(luò)通信的方法。 | 使用 Ajax 的目的是設(shè)計(jì)出對(duì)用戶交互響應(yīng)得非??斓臑g覽器應(yīng)用程序。Ajax 還會(huì)減輕服務(wù)器加載頁(yè)面的沉重負(fù)載。在 Ajax 應(yīng)用程序中,瀏覽器和服務(wù)器之間的交互僅僅是為了交換與應(yīng)用程序相關(guān)的數(shù)據(jù),這會(huì)優(yōu)化服務(wù)器資源的使用,讓服務(wù)器資源能夠支持更多的用戶和更多的應(yīng)用程序。本系列講解一些基本的 Ajax 技術(shù),介紹構(gòu)建單頁(yè)面瀏覽器應(yīng)用程序所涉及的概念。 | | 到本文結(jié)束時(shí),我們會(huì)建立數(shù)據(jù)庫(kù)、一個(gè)提供核心銀行邏輯的 PHP 模塊以及一個(gè)單頁(yè)面瀏覽器用戶界面,這些都是銀行場(chǎng)景的組成部分。 MySQL 數(shù)據(jù)庫(kù) 正如在 第 1 部分 中指出的,MySQL 是一種開(kāi)放源碼數(shù)據(jù)庫(kù)。在我們的場(chǎng)景中,使用社區(qū)服務(wù)器版本,這是一個(gè)緊湊的數(shù)據(jù)庫(kù)服務(wù)器,具有許多有用的特性。因?yàn)檫@個(gè)銀行場(chǎng)景的實(shí)現(xiàn)基于開(kāi)放源碼產(chǎn)品,所以 MySQL 和 Zend Core PHP 是合適的組合。Zend Core 本身支持 MySQL,還有各種支持 MySQL 管理和編程的工具。在我們的場(chǎng)景中,只使用 MySQL 命令行客戶機(jī)執(zhí)行 MySQL 的管理。我們將用 MySQL 數(shù)據(jù)庫(kù)為這個(gè)場(chǎng)景建立銀行帳戶數(shù)據(jù)庫(kù)。 創(chuàng)建并填充銀行數(shù)據(jù)庫(kù) 在這個(gè)場(chǎng)景中,將為給定的客戶存儲(chǔ)以下帳戶信息: AccountHolderName AccountNumber CheckingBalance StockName StockQuantity StockValue 給定客戶的帳戶信息包括帳戶持有人的姓名、帳戶號(hào)、當(dāng)前的資產(chǎn)余額、客戶擁有的一只股票的編號(hào)、擁有的股票總數(shù)以及股票投資組合的當(dāng)前市值。下面幾節(jié)詳細(xì)介紹如何創(chuàng)建數(shù)據(jù)庫(kù)表,然后用一些虛構(gòu)的銀行客戶的帳戶信息填充這個(gè)表。我們開(kāi)始吧! 按照以下步驟創(chuàng)建數(shù)據(jù)庫(kù),然后用應(yīng)用程序相關(guān)數(shù)據(jù)填充這個(gè)表: - 如果 Eclipse 還未運(yùn)行的話,就啟動(dòng)它(c:\eclipse\eclipse.exe)。
- 確保在 Eclipse 中啟用了 PHP 透視圖:
- 選擇 Window->Open Perspective->Other->PHP 并單擊 OK。
- 在 Eclipse 中,選擇 File->New->Project。
- 選擇 General->Project 并單擊 Next。
- 在 project name 字段中輸入 BankDB。
- 單擊 Finish。
- 右擊 BankDB project 并選擇 New->Other。
- 選擇 General-> File 并單擊 Next。
- 在 File name 字段中,輸入
BankDB.sql 并單擊 Finish。 - 輸入或粘貼 清單 1 中的代碼作為 BankDB.sql 的內(nèi)容。
- 保存并關(guān)閉這個(gè)文件。
- 為了啟動(dòng) MySQL 命令行客戶機(jī),單擊 Windows Start Menu->All Programs->MySQL->MySQL Server->MySQL Command Line Client。
- 在 MySQL 命令行窗口中,輸入密碼
webtech 并按 Enter。 - 在 mysql> 提示下,輸入
source c:\eclipse\workspace\BankDB\BankDB.sql 并按 Enter。 - 檢查 BankDB 數(shù)據(jù)庫(kù)是否存在,以此確認(rèn)前面的命令已經(jīng)正確地執(zhí)行了。檢查所用的命令如下:
show databases; use bankdb; show tables; describe account; - 在 MySQL 命令行客戶機(jī)中,輸入
exit 關(guān)閉它。 清單 1. BankDB.sql 文件的內(nèi)容 -- This file is part of the End-to-End Ajax development article in -- the IBM developerWorks. This file contains a simple DB script to -- create a database and populate it with the data. -- -- Last Modified: May/10/2007 -- -- To execute the following statements in MySQL, do the following steps. -- 1) Start MySQL command line client. -- 2) Enter your MySQL admin password. -- 3) Type the following line by substituting <YOUR_SQL_FILE_DIR> with the -- directory name where the file is stored. -- source <YOUR_SQL_FILE_DIR>\bankdb.sql -- -- Table structure for table ‘BankDB‘ -- DROP DATABASE BankDB; CREATE DATABASE BankDB; USE BankDB; CREATE TABLE account ( AccountHolderName VARCHAR(20) NOT NULL, AccountNumber INTEGER NOT NULL, CheckingBalance DOUBLE NOT NULL, StockName VARCHAR(6), StockQuantity INTEGER, StockValue DOUBLE, PRIMARY KEY(AccountHolderName, AccountNumber) ); -- -- Populating data for table ‘a(chǎn)ccount‘ -- insert into ACCOUNT values (‘Frodo‘, 435245, 2344.45, ‘GOOG‘, 100, 3453.32); insert into ACCOUNT values (‘Sam‘, 928462, 7583.32, ‘CSCO‘, 200, 5323.43); insert into ACCOUNT values (‘Pippin‘, 234233, 3444.62, ‘INTC‘, 300, 4213.76); insert into ACCOUNT values (‘Merry‘, 642445, 1005.32, ‘MSFT‘, 250, 1353.32); insert into ACCOUNT values (‘Aragorn‘, 972321, 6424.24, ‘HPQ‘, 525, 12043.94); insert into ACCOUNT values (‘Gandalf‘, 432134, 5392.23, ‘IBM‘, 400, 10043.78); insert into ACCOUNT values (‘Legolas‘, 590134, 4313.82, ‘DELL‘, 325, 5926.62); | 使用 PHP 訪問(wèn) MySQL 數(shù)據(jù)庫(kù) PHP 最受人喜愛(ài)的特性之一是,它為訪問(wèn)不同廠商的數(shù)據(jù)庫(kù)中的數(shù)據(jù)提供了簡(jiǎn)單且出色的支持,包括 MySQL。它提供了一種集成業(yè)務(wù)邏輯和數(shù)據(jù)庫(kù)的簡(jiǎn)便有效的方法。在過(guò)去幾年中,PHP 社區(qū)完成了幾項(xiàng)改進(jìn),比如 PHP Data Objects(PDO),PDO 提供一個(gè)抽象層,無(wú)論使用哪種數(shù)據(jù)庫(kù)服務(wù)器,這個(gè)抽象層都公開(kāi)同樣的 API 函數(shù)。有兩種風(fēng)格的 PHP 數(shù)據(jù)庫(kù) API 函數(shù):過(guò)程式的和面向?qū)ο蟮?。在這個(gè)場(chǎng)景中,我們使用針對(duì) MySQL 的直接數(shù)據(jù)庫(kù) API 來(lái)獲得一個(gè)基本的了解。PHP 提供兩種訪問(wèn) MySQL 的方法: 常規(guī)的 MySQL 擴(kuò)展不完全支持 MySQL 4.1.0 的所有功能。這些特性包括存儲(chǔ)過(guò)程、觸發(fā)器和視圖。MySQL Improved(mysqli)擴(kuò)展是訪問(wèn)這些功能的最新方式。在 PHP 5.0 或更高版本中,可以使用 mysqli 的擴(kuò)展。但是,mysqli 在默認(rèn)情況下沒(méi)有啟用 —— 必須使用 Zend Core 管理工具啟用它。在這個(gè)系列的 第 1 部分 中,在安裝和配置 Zend Core 時(shí)已經(jīng)啟用了 mysqli。 PHP 數(shù)據(jù)庫(kù) API 的出色特性之一是,它們可以將數(shù)據(jù)庫(kù)操作的結(jié)果以許多不同方式映射到 PHP 數(shù)據(jù)結(jié)構(gòu)。換句話說(shuō),PHP 中的數(shù)據(jù)庫(kù) API 可以以 PHP 關(guān)聯(lián)數(shù)組的形式返回?cái)?shù)據(jù)庫(kù)結(jié)果,其中數(shù)據(jù)庫(kù)表的列名作為數(shù)組的鍵。在另一種情況下,它可以以 PHP 類對(duì)象的形式返回結(jié)果,其中數(shù)據(jù)庫(kù)表的列名作為對(duì)象的屬性。因此可以以方便的方式表示數(shù)據(jù)庫(kù)查詢的結(jié)果。 構(gòu)建 PHP MySQLi 函數(shù)塊 正如前面提到的,PHP 提供了許多 mysqli 函數(shù)。本節(jié)只解釋那些用來(lái)實(shí)現(xiàn)銀行場(chǎng)景的函數(shù)。具體地說(shuō),這里討論連接、讀、寫(xiě)和釋放連接等數(shù)據(jù)庫(kù)函數(shù)。更多細(xì)節(jié)請(qǐng)參考 參考資料 中列出的 PHP 官方文檔。 在執(zhí)行任何數(shù)據(jù)庫(kù)操作之前,必須建立到數(shù)據(jù)庫(kù)服務(wù)器的連接。在 PHP 中,使用以下函數(shù)連接數(shù)據(jù)庫(kù): $link = mysqli_connect("hostname", "user", "password", "dbname"); | 這個(gè)函數(shù)嘗試用正確的憑證連接位于指定主機(jī)上的指定數(shù)據(jù)庫(kù)。這個(gè)函數(shù)還接受其他可選參數(shù)(參見(jiàn) PHP 文檔)。如果成功地連接到數(shù)據(jù)庫(kù),這個(gè)函數(shù)會(huì)返回一個(gè)連接對(duì)象,后續(xù)的數(shù)據(jù)庫(kù)讀寫(xiě)操作需要使用這個(gè)對(duì)象。如果無(wú)法建立數(shù)據(jù)庫(kù)連接,那么這個(gè)函數(shù)返回 false。如果不希望在代碼中以明文形式直接嵌入數(shù)據(jù)庫(kù)服務(wù)器密碼,那么可以考慮使用 MySQL 的密碼摘要特性。 與連接操作對(duì)應(yīng)的函數(shù)是數(shù)據(jù)庫(kù)關(guān)閉函數(shù): $result = mysqli_close($link); | 這個(gè)函數(shù)嘗試關(guān)閉以前打開(kāi)的數(shù)據(jù)庫(kù)連接。它以 MySQL 數(shù)據(jù)庫(kù)連接對(duì)象作為參數(shù);如果數(shù)據(jù)庫(kù)關(guān)閉操作成功,它就返回 true,否則返回 false。 在討論了連接管理函數(shù)之后,我們來(lái)看看數(shù)據(jù)庫(kù)讀/寫(xiě)操作涉及的函數(shù)。下面是讀/寫(xiě)操作涉及的一些函數(shù): - (1)
$result = mysqli_query($link, $queryStr); - (2)
$numberOfRows = mysqli_num_rows($result); - (3)
$row = mysqli_fetch_assoc($result); - (4)
$row = mysqli_fetch_object($result); 函數(shù) (1) 對(duì)數(shù)據(jù)庫(kù)執(zhí)行一個(gè) SQL 查詢。它的參數(shù)是連接對(duì)象和一個(gè)有效的 SQL 查詢字符串。它在查詢成功時(shí)返回 true,在失敗時(shí)返回 false。但是,對(duì)于 SELECT SQL 查詢,這個(gè)函數(shù)返回一個(gè)結(jié)果對(duì)象。 函數(shù) (2) 計(jì)算一個(gè) SQL 查詢返回的結(jié)果集中的行數(shù)。它的參數(shù)是結(jié)果對(duì)象。 函數(shù) (3) 以 PHP 關(guān)聯(lián)數(shù)組的形式返回結(jié)果行。這個(gè)數(shù)組以數(shù)據(jù)庫(kù)列名作為數(shù)組鍵,以數(shù)據(jù)庫(kù)字段值作為數(shù)組值。例如,如果一個(gè)數(shù)據(jù)庫(kù)查詢返回 “capital” 列的值,那么可以通過(guò)引用數(shù)組來(lái)訪問(wèn)結(jié)果: $stateCapital = $row[‘capital‘]; | 函數(shù) (4) 以 PHP 對(duì)象的形式返回結(jié)果行。這個(gè)對(duì)象以數(shù)據(jù)庫(kù)列名作為對(duì)象屬性,以數(shù)據(jù)庫(kù)字段值作為對(duì)象屬性值。例如,如果一個(gè)數(shù)據(jù)庫(kù)查詢返回 “park” 列的值,那么可以通過(guò)引用對(duì)象值來(lái)訪問(wèn)結(jié)果: $nationalPark = $row->park; | 函數(shù) (3) 和 (4) 可以非常簡(jiǎn)便地將數(shù)據(jù)庫(kù)值映射到程序變量。正如前面提到的,PHP 還提供了執(zhí)行數(shù)據(jù)庫(kù)值映射的其他方法。 在我們的場(chǎng)景中,在執(zhí)行數(shù)據(jù)庫(kù)操作之后,要使用另外兩個(gè)函數(shù)進(jìn)行錯(cuò)誤處理。了解數(shù)據(jù)庫(kù)操作是否成功執(zhí)行是非常重要的。下面兩個(gè)函數(shù)可以方便地處理這個(gè)問(wèn)題: $returnCode = mysqli_errno($link); $errorMsg = mysqli_error($link); mysqli_errno 函數(shù)返回最近的函數(shù)調(diào)用的錯(cuò)誤碼。它的參數(shù)是連接對(duì)象,它返回已經(jīng)執(zhí)行的函數(shù)的返回碼。返回碼為 0 就說(shuō)明沒(méi)有錯(cuò)誤。mysqli_error 函數(shù)返回最近的錯(cuò)誤的字符串描述。這兩個(gè)函數(shù)對(duì)于完成數(shù)據(jù)庫(kù)查詢的整個(gè)處理過(guò)程是非常重要的。 在 PHP 模塊中實(shí)現(xiàn)銀行業(yè)務(wù)邏輯 按照以下步驟創(chuàng)建一個(gè) PHP 模塊,并實(shí)現(xiàn)中間層業(yè)務(wù)邏輯: - 切換到 Eclipse PHP 透視圖:?jiǎn)螕?Window->Open Perspective->Other->PHP 并單擊 OK。
- 單擊 File->New->PHP Project:
- 在 Project name 字段中,輸入
BankTeller 。 - 單擊 Finish。
- 在 Project Explorer 視圖中,右擊 BankTeller 項(xiàng)目并選擇 New->PHP File:
- 在 File Name 字段中,輸入 BankLogic.php 并單擊 Finish。
- 輸入或粘貼 清單 2 中的源代碼作為這個(gè)文件的內(nèi)容。
- 單擊 File->Save 保存文件。
- 通過(guò)這個(gè)文件中的注釋了解代碼的作用,或者參考下一節(jié)中對(duì)代碼邏輯的解釋。
可以看出在 PHP 中執(zhí)行數(shù)據(jù)庫(kù)操作是多么簡(jiǎn)便。您可能會(huì)注意到,在清單 2 中密碼是明文形式的。這個(gè)密碼只用在為本文創(chuàng)建的測(cè)試數(shù)據(jù)庫(kù)中??梢允褂妹艽a摘要替代明文,從而進(jìn)一步改進(jìn)這個(gè) PHP 模塊。 清單 2. BankLogic.php 文件的內(nèi)容 <?php /* ============================================================ Project: End-to-End-Ajax application development Purpose: This is an example scenario to be used in an IBM developerWorks article. Last modified: May/11/2007. This PHP module provides the core bank teller logic required to access the customer related data from the database. These functions can be called by other PHP modules present in the Bank scenario. All the logic involved in bank teller actions are divided into these three core functions. a) Get all account information b) Process Transaction (deposit or debit) c) Compute stock portfolio value It uses direct mysqli functions to access the database as opposed to the PHP PDO functions. ============================================================ */ // These globals will be used to hold the following values: // 1) $link is for storing the database connection object. // 2) $dbResult is for storing the db query result object. // 3) $finalResult is an associative array where the results // from individual bank teller actions are packaged and // returned to the caller. global $link, $dbResult, $finalResult; /* ============================================================ Function: connect_to_db Last modified: May/11/2007. This function connects to the BankDB MySQL database. If the connection attempt is successful, it stores the connection object in a global scoped variable and returns true. If the connection attempt is not successful, then it returns false. ============================================================ */ function connect_to_db() { // We will use these global scoped variables here. global $link, $dbResult, $finalResult; // Initialize the variables. $link = null; $dbResult = null; $finalResult = null; // Do a mysqli connect to the local BankDB database using // proper credentials. $link = mysqli_connect("localhost", "root", "webtech", "bankdb"); // Check if DB connection worked. if (mysqli_connect_errno()) { // It looks like there is some problem. // Get the MySQL error number and the error string. $resultMsg = "DB connection error: " . mysqli_connect_errno() . " [" . mysqli_connect_error() . "]"; // Store the results in an associative array. // Set the result of the operation as not a successful one. $finalResult["ResultCode"] = 1; // Set the result message. $finalResult["ResultMsg"] = $resultMsg; // Return false to indicate the DB connection failure. return (false); } else { // DB connection is good. return(true); } // End of if (mysqli_connect_errno()) } // End of function connect_to_db /* ============================================================ Function: close_connection_to_db Last modified: May/11/2007. This function closes the connection made earlier to the BankDB database. ============================================================ */ function close_connection_to_db() { // We will use these globally scoped variables. global $link, $dbResult; // Close the connection if we have an active // DB connection object. if ($link != null) { if (($dbResult != null) && (is_object($dbResult))) { // If the DB result contains query data object, free it now. mysqli_free_result($dbResult); $dbResult = null; } // End of if ($dbResult != null) // Time to close the DB connection. mysqli_close($link); // Set the connection object variable to null. $link = null; } // End of if ($link != null) } // End of function close_connection_to_db /* ============================================================ Function: getAllAccountInformation Last modified: May/11/2007. This function reads all the account information stored in the BankDB and returns them in an associative array. ============================================================ */ function getAllAccountInformation() { // We will use these globally scoped variables. global $link, $finalResult, $dbResult; // Get a connection to the BankDB. $result = connect_to_db(); // If db connection failed, return now. if ($result == false) { return ($finalResult); } // End of if ($result == false) // Make an SQL statement to query all rows in the account table. $queryStr = "Select * from account"; // Make the SQL query. $dbResult = mysqli_query($link, $queryStr); // Process if there are any query errors. if (mysqli_errno($link)) { // Close the connection first. close_connection_to_db(); // Set the error message in the final result assoc. array. $resultMsg = "DB read error: " . mysqli_errno($link) . " [" . mysqli_error($link) . "]"; // Set the application specific return code as FAILURE. $finalResult["ResultCode"] = 1; $finalResult["ResultMsg"] = $resultMsg; // Return the final result. return ($finalResult); } // End of if (mysqli_errno($link)) // Get the number of rows queried. $rowCount = mysqli_num_rows($dbResult); // If the database is empty, return now. if ($rowCount <= 0) { // Close the connection first. close_connection_to_db(); // Set the return code and return message. $finalResult["ResultCode"] = 1; $finalResult["ResultMsg"] = "No accounts were found in BankDB."; // Return the final result. return ($finalResult); } // End of if ($rowCount <= 0) // Set the counter to 0. $cnt = 0; // Create an array to hold all the fields from each row. $accountInfo = array(); // From the query result set, fetch data fields from each row. while($row = mysqli_fetch_assoc($dbResult)) { // Initialize it to null. $data = null; // Create an associative array on the fly and store the // individual data fields read from the current database row. $data["AccountHolderName"] = $row["AccountHolderName"]; $data["AccountNumber"] = $row["AccountNumber"]; $data["CheckingBalance"] = doubleval($row["CheckingBalance"]); $data["StockName"] = $row["StockName"]; $data["StockQuantity"] = intval($row["StockQuantity"]); $data["StockValue"] = doubleval($row["StockValue"]); // Now, store the entire associative array in to a regular array. $accountInfo[$cnt++] = $data; } // End of while($row = mysqli_fetch_assoc($dbResult)) // We finished reading all the account information from the database. // Set the final result code as success. $finalResult["ResultCode"] = 0; // Set the final result message as success. $finalResult["ResultMsg"] = "ReadAllAccountsInfoFromDB successful"; // Make the entire array holding all the account information into the // final result associative array. // $finalResult is an associative array containing an // regular array which in turn contains several associative arrays as // its elements. $finalResult["AccountInfo"] = $accountInfo; // Close the DB connection. close_connection_to_db(); // Return the final result array now. return($finalResult); } // End of function getAllAccountInformation /* ============================================================ Function: accountTransaction Last modified: May/11/2007. This function performs the bank teller account transaction. This function takes the account holder name, an amount and the transaction type as function parameter. The transaction type is 1 for deposit into checking account. The transaction type is 2 for debit from a checking account. After the transaction is done, it returns the snapshot of the account before this transaction and another snapshot of the account after this transaction. ============================================================ */ function accountTransaction($accountHolderName, $amount, $transactionType) { // We will use these globally scoped variables. global $link, $finalResult, $dbResult; // Get a database connection. $result = connect_to_db(); // If the connection failed, return now. if ($result == false) { return ($finalResult); } // End of if ($result == false) // Make an SQL statement to read a particular account data for a // given account holder name. $queryStr = "Select * from account where AccountHolderName=‘$accountHolderName‘"; // Perform the SQL query. $dbResult = mysqli_query($link, $queryStr); // Did you encounter a DB error? if (mysqli_errno($link)) { // Close the connection now. close_connection_to_db(); // Set the DB error message. $resultMsg = "DB read error: " . mysqli_errno($link) . " [" . mysqli_error($link) . "]"; // Set the application-specific result code. $finalResult["ResultCode"] = 1; // Set the result message. $finalResult["ResultMsg"] = $resultMsg; // Return the associative array with the error result. return ($finalResult); } // End of if (mysqli_errno($link)) // How many rows were read from the DB? $rowCount = mysqli_num_rows($dbResult); // If no rows are obtained from DB, return now. if ($rowCount <= 0) { // Close the connection now. close_connection_to_db(); // Setup the return value as not a successful one. $finalResult["ResultCode"] = 1; $finalResult["ResultMsg"] = "No accounts were found in BankDB."; // Return the final result. return ($finalResult); } // End of if ($rowCount <= 0) // Store a record with the current checking balance. // Create an array to hold the pre-transaction and // post-transaction account snapshots. $accountInfo = array(); // Fetch the query result as an associative array. $row = mysqli_fetch_assoc($dbResult); $data = null; // Record the filed values from the database row into // a new associative array. $data["AccountHolderName"] = $row["AccountHolderName"]; $data["AccountNumber"] = $row["AccountNumber"]; $data["PreviousCheckingBalance"] = doubleVal($row["CheckingBalance"]); $data["StockName"] = $row["StockName"]; $data["StockQuantity"] = intval($row["StockQuantity"]); $data["StockValue"] = doubleVal($row["StockValue"]); // Store the pre-transaction account snapshot into a regular array. $accountInfo[0] = $data; // Initialize the variables. $newBalance = 0.0; // Since we are doing a transaction that changes the // checking balance, we need to update the DB later. $updateDB = true; if ($transactionType == 1) { // It is deposit. // Ensure that user wants to deposit an amount greater than 0. if ($amount > 0) { $newBalance = doubleval($data["PreviousCheckingBalance"]) + $amount; } else { // Deposit amount is 0. There is no need to do a database update. $updateDB = false; $newBalance = doubleval($data["PreviousCheckingBalance"]); } // End of if ($amount > 0) } else if ($transactionType == 2) { // It is debit. // Ensure that the user wants to debit an amount greater than 0 and // the amount is less than the money available in the checking account. if (($amount > 0) && ($amount < doubleval($data["PreviousCheckingBalance"]))) { $newBalance = doubleval($data["PreviousCheckingBalance"]) - $amount; } else { // Debit amount is either 0 or it is bigger than a allowed value. $updateDB = false; $newBalance = doubleval($data["PreviousCheckingBalance"]); } } // End of if ($transactionType == 1) // Bank teller transaction is completed now. // Life is good. Send the result back. $finalResult["ResultCode"] = 0; $finalResult["ResultMsg"] = "AccountTransaction successful."; // If we modified the current checking balance because of a // deposited or debited amount, then update the database. if ($updateDB == true) { // Prepare an SQL Update statement. $updateStr = "update account set CheckingBalance=$newBalance " . "where AccountHolderName=‘$accountHolderName‘"; // Execute the update query. $dbResult = mysqli_query($link, $updateStr); // See if there are any DB errors? if (mysqli_errno($link)) { // Close the connection to the database. close_connection_to_db(); // Prepare the error message to be returned. $resultMsg = "DB write error: " . mysqli_errno($link) . " [" . mysqli_error($link) . "]"; $finalResult["ResultCode"] = 1; $finalResult["ResultMsg"] = $resultMsg; // Return the error message. return ($finalResult); } if ($dbResult == true) { // All fine. // Prepare to send back the result of this operation. $finalResult["ResultCode"] = 0; $finalResult["ResultMsg"] = "New balance for $accountHolderName has been stored in DB."; } else { $finalResult["ResultCode"] = 1; $finalResult["ResultMsg"] = "New balance for $accountHolderName could not be stored in DB."; // Return the final result that has an error. return($finalResult); } // End of if ($dbResult == true) } // End of if ($updateDB == true) // We already stored the pre-transaction account snapshot in an // array at the top of this function. // Store a second record that will have the updated checking balance. $data = null; $data["AccountHolderName"] = $row["AccountHolderName"]; $data["TransactionAmount"] = $amount; $data["AccountNumber"] = $row["AccountNumber"]; $data["NewCheckingBalance"] = $newBalance; $data["StockName"] = $row["StockName"]; $data["StockQuantity"] = intval($row["StockQuantity"]); $data["StockValue"] = doubleval($row["StockValue"]); $accountInfo[1] = $data; $finalResult["AccountInfo"] = $accountInfo; // Close the DB connection. close_connection_to_db(); // Return the final result. return($finalResult); } // End of function accountTransaction. /* ============================================================ Function: portfolioValue Last modified: May/11/2007. This function stores the current portfolio value of a given account holder to the database. It takes the account holder name and the current stock price as input arguments. It reads the currently held position (in the Bank scenario, every account holder owns only a single company‘s stock. It is done to make the task simpler.) and computes the current market value. Then it updates the database with the new total stock value. ============================================================ */ function portfolioValue($accountHolderName, $stockPrice) { // We will use the globally scoped variables. global $link, $finalResult, $dbResult; // Connect to the MySQL BankDB database. $result = connect_to_db(); // If error in connecting to the DB, return now. if ($result == false) { return ($finalResult); } // End of if ($result == false) // Prepare the query string to get account info for a given account holder. $queryStr = "Select * from account where AccountHolderName=‘$accountHolderName‘"; // Execute the MySQL query. $dbResult = mysqli_query($link, $queryStr); // Are there any errors? if (mysqli_errno($link)) { // Error there. Close the db connection. close_connection_to_db(); // Prepare error message to be returned. $resultMsg = "DB read error: " . mysqli_errno($link) . " [" . mysqli_error($link) . "]"; $finalResult["ResultCode"] = 1; $finalResult["ResultMsg"] = $resultMsg; // Return the error. return ($finalResult); } // End of if (mysqli_errno($link)) // Did we read any rows from the DB? $rowCount = mysqli_num_rows($dbResult); // If there are no matching rows in DB, return now. if ($rowCount <= 0) { // Close the DB connection. close_connection_to_db(); $finalResult["ResultCode"] = 1; $finalResult["ResultMsg"] = "No accounts were found in BankDB."; // Return the final result. return ($finalResult); } // End of if ($rowCount <= 0) // Store a record with the current portfolio value. // Allocate an array to store the pre-transaction and the // post-transaction account snapshots. $accountInfo = array(); // Do a database fetch to get the results as an associative array. $row = mysqli_fetch_assoc($dbResult); // Store the data fields of a row to an associative array. $data = null; $data["AccountHolderName"] = $row["AccountHolderName"]; $data["AccountNumber"] = $row["AccountNumber"]; $data["CheckingBalance"] = doubleval($row["CheckingBalance"]); $data["StockName"] = $row["StockName"]; $data["StockQuantity"] = intval($row["StockQuantity"]); $data["PreviousPortfolioValue"] = doubleval($row["StockValue"]); // Store the associative array in a regular array. $accountInfo[0] = $data; // Calculate the portfolio value. $finalResult["ResultCode"] = 0; $finalResult["ResultMsg"] = "PortfolioValue successfully calculated."; $newPortfolioValue = intval($row["StockQuantity"]) * doubleval($stockPrice); // Since the stock portfolio value has changed, update it in DB. $updateStr = "update account set StockValue=$newPortfolioValue " . "where AccountHolderName=‘$accountHolderName‘"; // Perform an SQL update. $dbResult = mysqli_query($link, $updateStr); // Check if there are any DB errors. if (mysqli_errno($link)) { // Close the connection to DB. close_connection_to_db(); // Prepare the error message to returned. $resultMsg = "DB write error: " . mysqli_errno($link) . " [" . mysqli_error($link) . "]"; $finalResult["ResultCode"] = 1; $finalResult["ResultMsg"] = $resultMsg; // Return the error message. return ($finalResult); } // End of if (mysqli_errno($link)) if ($dbResult == true) { // All fine. // Prepare the success message to be returned. $finalResult["ResultCode"] = 0; $finalResult["ResultMsg"] = "New stock value for $accountHolderName has been stored in DB."; } else { $finalResult["ResultCode"] = 1; $finalResult["ResultMsg"] = "New stock value for $accountHolderName could not be stored in DB."; // Update failed. Hence return the error result. return($finalResult); } // End of else in if ($dbResult == true) // Store a second record that will have the updated portfolio value. // We have already stored the pre-transaction account snapshot in an array. // Let us store the post-transaction account snapshot also there. $data = null; $data["AccountHolderName"] = $row["AccountHolderName"]; $data["AccountNumber"] = $row["AccountNumber"]; $data["CheckingBalance"] = doubleval($row["CheckingBalance"]); $data["StockName"] = $row["StockName"]; $data["CurrentStockPrice"] = doubleval($stockPrice); $data["StockQuantity"] = intval($row["StockQuantity"]); $data["NewPortfolioValue"] = $newPortfolioValue; // Store the associative array in a regular array. $accountInfo[1] = $data; $finalResult["AccountInfo"] = $accountInfo; // Close the DB connection and return the result. close_connection_to_db(); return($finalResult); } // End of function portfolioValue. ?> | BankLogic PHP 模塊邏輯 清單 2 中的 PHP 文件包含核心銀行出納員函數(shù)所需的業(yè)務(wù)邏輯。具體地說(shuō),這些函數(shù)包括獲取數(shù)據(jù)庫(kù)中存儲(chǔ)的所有帳戶信息、執(zhí)行存款或取款操作以及計(jì)算給定客戶的股票投資組合價(jià)值。這些函數(shù)都接受某些輸入?yún)?shù),并返回一個(gè)包含相關(guān)帳戶信息的應(yīng)用程序?qū)S械年P(guān)聯(lián)數(shù)組。這些函數(shù)中的邏輯都需要連接 BankDB MySQL 數(shù)據(jù)庫(kù),并執(zhí)行讀或更新操作。清單 2 的開(kāi)頭定義三個(gè)全局范圍的 PHP 變量。這些變量存儲(chǔ)數(shù)據(jù)庫(kù)連接對(duì)象 ($link) 、SQL 查詢結(jié)果對(duì)象 ($dbResult) 以及要返回給這個(gè) PHP 模塊的調(diào)用者的最終結(jié)果 ($finalResult) 。這個(gè) PHP 模塊用兩個(gè)實(shí)用程序函數(shù)連接 MySQL 數(shù)據(jù)庫(kù)和斷開(kāi)連接。在建立連接時(shí),創(chuàng)建一個(gè)連接對(duì)象并將它存儲(chǔ)在 $link 變量中,因?yàn)檫@是一個(gè)全局變量,所以可以在整個(gè) PHP 文件中使用它。同樣,在執(zhí)行數(shù)據(jù)庫(kù)讀或更新時(shí),將一個(gè) SQL 查詢結(jié)果存儲(chǔ)在 $dbResult 變量中,這也是一個(gè)全局變量。當(dāng)需要關(guān)閉數(shù)據(jù)庫(kù)連接時(shí),使用另一個(gè)實(shí)用程序函數(shù) (close_connection_to_db) 斷開(kāi)數(shù)據(jù)庫(kù)連接。在關(guān)閉數(shù)據(jù)庫(kù)連接之前,這個(gè)實(shí)用程序函數(shù)確保釋放 $dbResult 中的結(jié)果集占用的內(nèi)存。 這個(gè)文件中有三個(gè)核心函數(shù): - 調(diào)用者通過(guò)調(diào)用
getAllAccountInformation 函數(shù)獲取數(shù)據(jù)庫(kù)中存儲(chǔ)的所有帳戶數(shù)據(jù)。這里的代碼使用 MySQLi 擴(kuò)展 API 建立到 MySQL 數(shù)據(jù)庫(kù)的 ODBC 連接,并獲取帳戶表中存儲(chǔ)的所有帳戶信息。在數(shù)據(jù)庫(kù)讀操作成功之后,它循環(huán)遍歷結(jié)果集并收集帳戶數(shù)據(jù)信息。它將所有帳戶數(shù)據(jù)信息存儲(chǔ)在一個(gè) PHP 關(guān)聯(lián)數(shù)組中,并將這個(gè)數(shù)組返回給調(diào)用者。 - 調(diào)用者通過(guò)調(diào)用
accountTransaction 函數(shù)執(zhí)行存款或取款操作。調(diào)用這個(gè)方法時(shí)使用的參數(shù)是帳戶持有人姓名、交易數(shù)量和交易類型(指明是存款還是取款操作)。建立到 MySQL 數(shù)據(jù)庫(kù)的 ODBC 連接,從數(shù)據(jù)庫(kù)獲取給定帳戶持有人的當(dāng)前帳戶數(shù)據(jù),并將這些信息存儲(chǔ)在一個(gè)關(guān)聯(lián)數(shù)組中。這是這個(gè)帳戶在交易之前的快照。然后,執(zhí)行所需的操作(存款或取款)。用存款或取款操作產(chǎn)生的新的帳戶余額更新數(shù)據(jù)庫(kù)。這是交易之后的數(shù)據(jù),也存儲(chǔ)在另一個(gè)關(guān)聯(lián)數(shù)組中?,F(xiàn)在,將交易之前和交易之后的帳戶快照存儲(chǔ)在一個(gè)常規(guī)數(shù)組中,并將這個(gè)數(shù)組作為最終結(jié)果返回給調(diào)用者。還要注意,只有在交易數(shù)量符合交易類型的限制時(shí),代碼才會(huì)執(zhí)行交易。 - 調(diào)用者通過(guò)調(diào)用
portfoliovalue 方法計(jì)算給定帳戶持有人的股票投資組合價(jià)值。調(diào)用這個(gè)方法時(shí)使用的參數(shù)是帳戶持有人姓名,以及這個(gè)帳戶持有人擁有的單只股票的當(dāng)前價(jià)格。這個(gè)方法獲取帳戶數(shù)據(jù)的交易前快照,并將它存儲(chǔ)在一個(gè)關(guān)聯(lián)數(shù)組中。然后,計(jì)算新的股票總價(jià)值,并更新 BankDB 數(shù)據(jù)庫(kù)中對(duì)應(yīng)的數(shù)據(jù)庫(kù)字段。它還將帳戶數(shù)據(jù)的交易后快照存儲(chǔ)在另一個(gè)關(guān)聯(lián)數(shù)組中。它將兩個(gè)帳戶數(shù)據(jù)快照存儲(chǔ)在一個(gè)常規(guī)數(shù)組中,并將這個(gè)數(shù)組作為最終結(jié)果返回給客戶機(jī)。 源文件 中的注釋可以幫助您理解代碼邏輯。 單頁(yè)面 Ajax 用戶界面 為了提供銀行出納員用戶界面,我們要開(kāi)發(fā)一個(gè)單頁(yè)面瀏覽器應(yīng)用程序。注意,單頁(yè)面瀏覽器應(yīng)用程序與傳統(tǒng)的 “點(diǎn)擊并等待” 式 Web 應(yīng)用程序不同。在 “點(diǎn)擊并等待” 式 Web 應(yīng)用程序中,每當(dāng)用戶到達(dá)新的頁(yè)面時(shí),瀏覽器會(huì)從服務(wù)器下載這個(gè) Web 頁(yè)面的完整內(nèi)容。另一方面,單頁(yè)面瀏覽器應(yīng)用程序背后的模型完全相反;在這種模型中,應(yīng)用程序所需的所有表示內(nèi)容(XHTML、CSS 和 JavaScript)只從服務(wù)器下載一次。這種一次性的下載通常在啟動(dòng)時(shí)進(jìn)行,也就是當(dāng)用戶通過(guò)瀏覽器訪問(wèn)應(yīng)用程序的 URL 時(shí)。XHTML 文件定義應(yīng)用程序所需的所有用戶界面元素。CSS 文件提供用戶界面不同部分所需的所有樣式。特殊的 Document Object Model(DOM)技術(shù)用來(lái)隱藏和顯示用戶界面的某些部分,因此無(wú)需從服務(wù)器獲取另一個(gè)頁(yè)面,就可以模擬多頁(yè)面的效果。在瀏覽器中運(yùn)行的與應(yīng)用程序相關(guān)的 JavaScript 可以處理大部分應(yīng)用程序邏輯。使用 Ajax 技術(shù)的目的是設(shè)計(jì)出對(duì)用戶交互響應(yīng)得非??斓臑g覽器應(yīng)用程序。Ajax 還會(huì)減輕服務(wù)器加載頁(yè)面的沉重負(fù)載。在 Ajax 應(yīng)用程序中,瀏覽器和服務(wù)器之間的交互僅僅是為了交換與應(yīng)用程序相關(guān)的數(shù)據(jù),這會(huì)優(yōu)化服務(wù)器資源的使用,讓服務(wù)器資源能夠支持更多的用戶和更多的應(yīng)用程序。 除了這里提到的好處之外,還有其他體系結(jié)構(gòu)方面的考慮因素,比如數(shù)據(jù)安全性、數(shù)據(jù)私密性和業(yè)務(wù)邏輯的適當(dāng)分隔,由于業(yè)務(wù)邏輯在瀏覽器客戶機(jī)上運(yùn)行,在這些方面也會(huì)有好處。另外,當(dāng) HTTP 事務(wù)被中斷時(shí),需要從錯(cuò)誤中恢復(fù)。這些考慮因素超出了本文的范圍,本文只希望實(shí)現(xiàn)一個(gè)簡(jiǎn)單的單頁(yè)面瀏覽器應(yīng)用程序。同樣,可以組合使用許多 Ajax 技術(shù),為瀏覽器應(yīng)用程序提供豐富的圖形效果。在本文中,我們只討論一些基本的技術(shù),介紹構(gòu)建單頁(yè)面瀏覽器應(yīng)用程序所涉及的概念。 在單頁(yè)面瀏覽器應(yīng)用程序中,另一種用來(lái)隱藏和顯示不同部分的技術(shù)是使用 HTML 標(biāo)記 <DIV>、<SPAN> 和 <TABLE>。圖 1 簡(jiǎn)要說(shuō)明了這些標(biāo)記的作用。<DIV> 標(biāo)記可以將 HTML 文檔分隔成單獨(dú)的部分。在分隔一個(gè)應(yīng)用程序的許多屏幕時(shí),第一步是將一個(gè)屏幕中的用戶界面元素放在一個(gè) <DIV> 標(biāo)記中。在此之后,通過(guò) JavaScript 控制在應(yīng)用程序的某個(gè)執(zhí)行階段應(yīng)該顯示哪個(gè)屏幕(即 <DIV>)。另一個(gè)方便的標(biāo)記是 <SPAN> 標(biāo)記,可以使用這個(gè)標(biāo)記對(duì)瀏覽器應(yīng)用程序中的文本段應(yīng)用樣式。在銀行場(chǎng)景中,使用這個(gè)標(biāo)記在屏幕上創(chuàng)建菜單控件。另外,還使用 <TABLE> 標(biāo)記管理文檔的布局。 圖 1. <SPAN>、<DIV> 和 <TABLE> 標(biāo)記 在瀏覽器應(yīng)用程序中,創(chuàng)建豐富的效果的另一種常用方法是對(duì)各個(gè)用戶界面元素應(yīng)用樣式。CSS 常常用來(lái)管理瀏覽器應(yīng)用程序的整體外觀。CSS 是單獨(dú)的代碼,具有它自己的語(yǔ)法。CSS 很容易設(shè)置,因?yàn)樗恍枰魏尾寮?—— 只需在文本文件中定義樣式規(guī)則即可。CSS 規(guī)則由一個(gè)選擇符和一個(gè)聲明塊組成。每個(gè)規(guī)則的開(kāi)頭是選擇符,后面是大括號(hào)。聲明塊放在大括號(hào)中,由聲明組成。聲明是屬性和值對(duì),聲明之間由分號(hào)分隔。下面的 清單 3 給出一個(gè)簡(jiǎn)單的 CSS 規(guī)則示例。圖 2 顯示各種 CSS 選擇符。 | 有其他文章解釋了 CSS 選擇符的所有特性。要想全面了解 CSS 的內(nèi)部工作原理,應(yīng)該花些時(shí)間閱讀其他 CSS 資料。參考資料 中給出的 CSS Zen Garden 站點(diǎn)是體會(huì) CSS 功能的好地方。 | | 清單 3. 一個(gè)簡(jiǎn)單的 CSS 規(guī)則 body { color: maroon; font-size: medium; text-align: center; background:green; } |
圖 2. CSS 選擇符類型 幾個(gè) Ajax 框架為 I/O、網(wǎng)絡(luò)、圖形、數(shù)據(jù)等方面提供了代碼庫(kù)?;?Eclipse 的 Aptana 工具將許多 Ajax 框架集成在一個(gè) IDE 中。但是,對(duì) Ajax 框架的討論超出了本文的范圍。銀行場(chǎng)景的 Ajax 相關(guān)代碼是用普通的 JavaScript 實(shí)現(xiàn)的,這主要是為了保持簡(jiǎn)單,并讓您能夠直接體會(huì)應(yīng)用程序中的代碼,而不是通過(guò)框架庫(kù)。在學(xué)完本系列之后,您應(yīng)該研究一下 Ajax 框架,比如 Dojo、Rico、Script.aculo.us 和 Prototype。 | Ajax 可以在與服務(wù)器交換數(shù)據(jù)時(shí)保持用戶界面的響應(yīng)性,可以避免中斷用戶的操作。 | | Ajax HTTP 交互 前一節(jié)討論了一些 XHTML、CSS 和 DOM 技術(shù),這些都是 Ajax 的組成部分。另外,與中間層服務(wù)器的 HTTP 交互方式也是 Ajax 應(yīng)用程序的核心性質(zhì)。Ajax 使瀏覽器應(yīng)用程序能夠以一種新方式與服務(wù)器進(jìn)行異步交互。與傳統(tǒng)的瀏覽器應(yīng)用程序不同,在 Ajax 應(yīng)用程序中,當(dāng)用戶單擊 HTML 表單上的 Submit 按鈕時(shí),用戶不必等待頁(yè)面出現(xiàn)。這種異步的 HTTP 通信由 XHR 支持,XHR 是一個(gè)瀏覽器對(duì)象,它使瀏覽器端 JavaScript 代碼可以發(fā)出異步的 HTTP 服務(wù)器請(qǐng)求。因此,可以完全在后臺(tái)發(fā)出 HTTP 請(qǐng)求、接收響應(yīng)并更新頁(yè)面的某些部分,這樣就不會(huì)中斷用戶的操作。Ajax 可以在與服務(wù)器交換數(shù)據(jù)時(shí)保持用戶界面的響應(yīng)性。W3C 當(dāng)前還在對(duì) XHR 進(jìn)行標(biāo)準(zhǔn)化(參見(jiàn) 參考資料)。圖 3 顯示 XHR 的各個(gè)對(duì)象方法和屬性: 圖 3. XML HTTP Request(XHR)對(duì)象方法和屬性 下面是 XHR 的操作流程: - 創(chuàng)建一個(gè)
XMLHttpRequest 對(duì)象實(shí)例。 - 使用
XMLHttpRequest 對(duì)象對(duì)服務(wù)器進(jìn)行調(diào)用,采用的方法是定義一個(gè)回調(diào)函數(shù),當(dāng)收到服務(wù)器響應(yīng)時(shí),會(huì)自動(dòng)執(zhí)行這個(gè)函數(shù)。 - 在回調(diào)函數(shù)中處理服務(wù)器的響應(yīng)。
- 如果需要,再執(zhí)行第 2 步,與服務(wù)器進(jìn)行下一次異步交互。
后面將討論在銀行場(chǎng)景中使用 XHR 的實(shí)現(xiàn)細(xì)節(jié)。 構(gòu)建 Ajax 瀏覽器應(yīng)用程序的組件 我們的瀏覽器應(yīng)用程序由以下部分組成: - XHTML 文件
- CSS 文件
- XHR JavaScript 文件
- 用 JavaScript 編寫(xiě)的客戶端業(yè)務(wù)邏輯
- 需要的實(shí)用程序 JavaScript 文件
XHTML 文件是用戶啟動(dòng)這個(gè)應(yīng)用程序時(shí)瀏覽器指向的文件。這個(gè)文件包含所有用戶界面元素,這些元素是用常用的 HTML 標(biāo)記構(gòu)建的,比如 <DIV>、<SPAN>、<TABLE> 和 <FORM>。這個(gè)文件還使用另外幾種瀏覽器技術(shù)提供樣式、通信和數(shù)據(jù)格式支持。 CSS 文件為用戶界面元素提供樣式規(guī)則。因?yàn)?CSS 是一個(gè)大主題,在這個(gè)場(chǎng)景中不可能涉及所有 CSS 特性。但是,我將在這個(gè)應(yīng)用程序的上下文中介紹一些重要的 CSS 基本特性。 XHR 文件解釋 XMLHttpRequest 對(duì)象的初始化邏輯。它還演示了如何以異步通信模式使用 XHR。我們并不重新設(shè)計(jì) XHR 邏輯,而是按照 Web 開(kāi)發(fā)社區(qū)廣泛采用的一種方式使用 XHR。 在客戶端業(yè)務(wù)邏輯中,使用 JavaScript 建立銀行場(chǎng)景屏幕的 Ajax 操作;這里采用一個(gè)事件模型,在這個(gè)模型中客戶機(jī)與服務(wù)器的交互只是為了交換數(shù)據(jù),而且可以異步地處理服務(wù)器響應(yīng)。對(duì)于那些不熟悉 Ajax 概念的讀者,這個(gè)模塊演示了一些關(guān)鍵的概念,這些概念可以應(yīng)用于任何單頁(yè)面 Ajax 瀏覽器應(yīng)用程序。 另外,這個(gè)瀏覽器應(yīng)用程序的組件還包括一個(gè)用于 JavaScript Object Notation(JSON)數(shù)據(jù)處理的開(kāi)放源碼實(shí)用程序。本系列的第 3 部分將詳細(xì)討論這個(gè)實(shí)用程序。 將銀行門戶實(shí)現(xiàn)為 Ajax 瀏覽器應(yīng)用程序 我們的銀行場(chǎng)景需要一個(gè)簡(jiǎn)單的瀏覽器界面,銀行出納員使用這個(gè)界面執(zhí)行 PHP 模塊中實(shí)現(xiàn)的核心函數(shù)。我們將使用 Aptana Web IDE 構(gòu)建這個(gè)界面。Aptana Web IDE 提供了一種用 XHTML、CSS 和 JavaScript 構(gòu)建瀏覽器應(yīng)用程序的簡(jiǎn)便方法。Aptana 是一個(gè)免費(fèi)的插件,可以無(wú)縫地插入 Eclipse 環(huán)境。這個(gè)插件仍然在開(kāi)發(fā)階段,但是目前的版本已經(jīng)能夠滿足銀行場(chǎng)景的需要了。盡管在銀行場(chǎng)景中將使用一般的 JavaScript 實(shí)現(xiàn) Ajax 特性,但是 Aptana 集成了幾個(gè)開(kāi)放源碼 Ajax 框架庫(kù)。 按照以下步驟創(chuàng)建單頁(yè)面 Ajax 瀏覽器應(yīng)用程序: - 在 Eclipse 中,切換到 Aptana 透視圖:選擇 Window->Open Perspective->Other->Aptana 并單擊 OK。
- 在 Aptana 透視圖左下方的窗格中,選擇 Project 視圖。
- 右擊 BankTeller 項(xiàng)目并選擇 New->HTML file:
- 在 File name 字段中,輸入
index.html 并單擊 Finish。 - 輸入或粘貼 清單 4 中的源代碼來(lái)替換 index.html 文件的內(nèi)容,并單擊 File->Save。
- 右擊 BankTeller 項(xiàng)目并選擇 New->CSS file:
- 在 File name 字段中,輸入
BankTeller.css 并單擊 Finish。 - 輸入或粘貼 清單 5 中的源代碼來(lái)替換 BankTeller.css 文件的內(nèi)容,并單擊 File->Save。
- 右擊 BankTeller 項(xiàng)目并選擇 New->JavaScript file:
- 在 File name 字段中,輸入
xhr.js 并單擊 Finish。 - 輸入或粘貼 清單 6 中的源代碼來(lái)替換這個(gè)文件的內(nèi)容,并單擊 File->Save。
- 通過(guò)這些文件中的注釋了解代碼的作用,或者參考下一節(jié)中對(duì)代碼邏輯的解釋。
到目前為止,這個(gè)單頁(yè)面 Ajax 瀏覽器應(yīng)用程序所需的組件已經(jīng)完成了三分之二!在本系列的第 3 部分中,將創(chuàng)建這個(gè)瀏覽器應(yīng)用程序中余下的部分。 清單 4. index.html <!-- ============================================================ Project: End-to-End-Ajax application development Purpose: This is an example scenario to be used in an IBM developerWorks article. Last modified: May/12/2007. This HTML file provides the required user-interface elements for the bank teller browser application. This is a single-page based Ajax browser application. It simply means that this application doesn‘t do any page fetches at all from the server other than the initial download of this single HTML file. This file liberally makes use of <DIV>, <SPAN> and <TABLE> tags to do some of the well-known Ajax tricks. It is a self-contained file that contains all the HTML markup required by the bank teller application. ============================================================ --> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> <html> <!-- HTML document header describes the various properties of the document including its title and relationship with other documents such as CSS and JavaScript files. In the Bank Teller single-page application, the following documents will be used: 1) json.js (It is an open-source JSON parser developed by Douglas Crockford. 2) xhr.js (It is a generic utility that provides XHR related functions. 3) BankTeller.js (All the client-side logic in this JavaScript file) 4) BankTeller.css (All the styling needed for the browser application is here) --> <head> <title>Ajax Bank Teller Application</title> <script language"JavaScript" type="text/javascript" src="json.js"></script> <script language"JavaScript" type="text/javascript" src="xhr.js"></script> <script language"JavaScript" type="text/javascript" src="BankTeller.js"></script> <link rel="stylesheet" style="text/css" href="BankTeller.css" media="all" /> </head> <!-- Set an event handler to initialize the application-specific stuff. This event handler will be triggered during page load time. In this event handler, initial screen for the bank teller application will be set and the other screens will be hidden. --> <body onload="initOnPageLoad();"> <!-- This application has seven different sections that are divided using the <DIV> elements. Those sections are: 1) Main section 2) Teller Options (Menu) section 3) Deposit section 4) Debit section 5) Stock portfolio section 6) Bank teller action result section 7) Page footer section All these sections will be shown selectively depending the application context where the user is in at a given time. This is done in the client side logic coded in JavaScript. This technique provides the dynamism and rich response to user actions. It elevates the user experience level drastically as compared to the traditional Web applications developed using the click and wait page refresh model. --> <!-- The following the main DIV container for the entire application. All the other sections are children of this. They all hang on to this DIV container node in DOM. --> <div id="mainPage" title="This is an Ajax based single-page browser application. No browser Back button please. HTML download from server occurs ONLY ONCE. From then on, back-end server is contacted asynchronously ONLY for data exchanges."> <h1 title="This web application provides Bank teller services."> Bank Teller Operations </h1> <!-- The tellerOptions DIV element holds the table that will provide the user with available teller options to select from. This container holds the menu choices for the entire bank teller application. --> <div id="tellerOptions" title="You can perform these teller operations."> <center><font color="Olive"> <u>Click an operation below</u> </font></center> <!-- Each menu option is a HTML text that is embedded within a SPAN tag. That will enable us to set the mouseover and mouseout events for that text element. We can combine those events with CSS styling to emulate a desktop-like menu behavior. When the user clicks on that menu, then the corresponding application logic can be performed. These different menu option are laid out inside a TABLE tag as individual rows. --> <table class="tellerOptionsTable" align="center"> <tr> <!-- Note that we are using class attribute in span to style the td value so that we can dynamically assign style classes (in JavaScript) as the user moves the mouse over different teller operation names. If we use the id attribute to CSS style, we can‘t dynamically assign styles in JavaScript. --> <!-- The event handler functions are called with a function parameter that indicates the menu option number. i.e. 1, 2 or 3. --> <!-- Deposit menu option --> <td> <span id="tellerOptionsLink1" class="tellerOptionsLink" title="Deposit to an account." onmouseover="changeOptionsLinkStyleForSelection(1);" onmouseout="changeOptionsLinkStyleToDefault(1);" onclick="processTellerOperation(1);"> 1) Deposit to an account </span> </td> </tr> <tr> <!-- Debit menu option --> <td> <span id="tellerOptionsLink2" class="tellerOptionsLink" title="Debit from an account." onmouseover="changeOptionsLinkStyleForSelection(2);" onmouseout="changeOptionsLinkStyleToDefault(2);" onclick="processTellerOperation(2);"> 2) Debit from an account </span> </td> </tr> <tr> <!-- Get stock portfolio value menu option --> <td> <span id="tellerOptionsLink3" class="tellerOptionsLink" title="Get Stock portfolio value." onmouseover="changeOptionsLinkStyleForSelection(3);" onmouseout="changeOptionsLinkStyleToDefault(3);" onclick="processTellerOperation(3);"> 3) Current portfolio value </span> </td> </tr> </table> </div> <!-- End of <div id="tellerOptions"> --> <!-- The tellerOptions DIV element holds a HTML form required for depositing money to an account. --> <div id="depositAction" title="Deposit to an account" class="depositAction"> <!-- This HTML form provides the UI elements for deposit action --> <form name="depositActionForm"> <span id="depositActionFormTitle" class="depositActionFormTitle"> Deposit to an account </span> <!-- This table provides a drop-down selection box and an input control to enter the deposit amount. --> <table class="depositActionTable" align="center"> <tr> <td>Select Account Owner:</td> <td> <select id="depositAccountOwner" size=7 name="depositAccountOwner"> </select> </td> </tr> <tr> <td>Amount:</td> <td><input type="text" name="depositAmount" id="depositAmount" maxlength="15" size="15" title="e-g: 100"/></td> </tr> </table> <!-- This table provides an action button. When pressed, it will trigger an onclick event handler that will perform the required client-side logic. --> <table align="center" class="depositAmountButtonTable"> <tr> <td> <input id="depositAmountActionButton" value="Deposit" type="button" title="Click here to deposit to an account." onclick="depositAmountAction_Async();"/> </td> </tr> </table> </form> </div> <!-- End of <div id="depositAction"> --> <!-- The tellerOptions DIV element holds a HTML form required for debiting money from an account. --> <div id="debitAction" title="Debit from an account" class="debitAction"> <form name="debitActionForm"> <span id="debitActionFormTitle" class="debitActionFormTitle"> Debit from an account </span> <table class="debitActionTable" align="center"> <tr> <td>Select Account Owner:</td> <td> <select id="debitAccountOwner" size=7 name="debitAccountOwner"> </select> </td> </tr> <tr> <td>Amount:</td> <td><input type="text" name="debitAmount" id="debitAmount" maxlength="15" size="15" title="e-g: 100"/></td> </tr> </table> <!-- This table provides an action button. When pressed, it will trigger an onclick event handler that will perform the required client-side logic. --> <table align="center" class="debitAmountButtonTable"> <tr> <td> <input id="debitAmountActionButton" value="Debit" type="button" title="Click here to debit from an account." onclick="debitAmountAction_Async();"/> </td> </tr> </table> </form> </div> <!-- End of <div id="depositAction"> --> <!-- The portfolioOption DIV element holds the form for updating the current portfolio value. --> <div id="portfolioAction" title="Update portfolio value" class="portfolioAction"> <form name="portfolioActionForm"> <span id="portfolioActionFormTitle" class="portfolioActionFormTitle"> Get portfolio value </span> <table class="portfolioActionTable" align="center"> <tr> <td>Select Account Owner:</td> <td> <select id="portfolioAccountOwner" size=7 name="portfolioAccountOwner"> </select> </td> </tr> </table> <!-- This table provides an action button. When pressed, it will trigger an onclick event handler that will perform the required client-side logic. --> <table align="center" class="portfolioValueButtonTable"> <tr> <td> <input id="portfolioActionButton" value="Get Stock Portfolio Value" type="button" title="Click here to get the portfolio value." onclick="portfolioAction_Async();"/> </td> </tr> </table> </form> </div> <!-- End of <div id="portfolioAction"> --> <!-- The tellerActionResult DIV element holds the form for getting portfolio value. --> <div id="tellerActionResult" title="Result of teller operation" class="tellerActionResult"> <form name="tellerActionResultForm"> <span id="tellerActionResultTitle" class="tellerActionResultTitle"> Result from Teller Operation </span> <!-- This table includes a text area that summarizes the result of the operation performed by the teller. --> <table class="tellerActionResultTable" align="center"> <tr> <td> <textarea id="tellerActionResultArea" rows="15" cols="80" readonly></textarea> </td> </tr> </table> </form> </div> <!-- End of <div id="tellerActionResult"> --> <!-- The pageFooter DIV element holds the footer information such as the currently logged in user and Go to main menu option. This footer section will be stapled at the bottom of every other section of this application. --> <div id="pageFooter"> <h1></h1> <table id="footerTable" class="footerTable" align="center"> <tr> <!-- Note that we are using class attribute in span to style the td value so that we can dynamically assign style classes (in JavaScript) as the user moves the mouse over the footer options. If we use the id attribute to CSS style, we can‘t dynamically assign styles in JavaScript. --> <td> <span id="currentUserDisplay" class="currentUserDisplay" title="Currently logged in user‘s name."> Teller: John Doe </span> </td> <!-- The event handler functions are called with a function parameter that indicates which footer option to be highlighted. --> <td> <span id="goToMainMenuLink" class="goToMainMenuLink" title="Click here to go to main menu." onmouseover="changeFooterLinkStyleForSelection(1);" onmouseout="changeFooterLinkStyleToDefault(1);" onclick="processFooterOperation(1);"> Back to main menu </span> </td> </tr> </table> </div> <!-- End of <div id="PageFooter"> --> </div> <!-- End of <div id="mainPage"> --> </body> </html> |
清單 5. BankTeller.css /* ============================================================ Project: End-to-End-Ajax application development Purpose: This is an example scenario to be used in an IBM developerWorks article. Last modified: May/12/2007. This file provides CSS rules that are used to style the UI controls for the bank teller browser application. This application doesn‘t address all the features of CSS. However, basic and important aspects of CSS are included here in the context of the bank teller application. ============================================================ */ /* This is a HTML selector that redefine the body tag with font and background attributes. */ body { font-family: Verdana, Geneva, Arial, sans-serif; font-size: medium; text-align: center; background: rgb(220, 220, 220); } /* This is a HTML selector used to style the <h1> element. */ h1 { color: #cc6600; border-bottom: medium double #888888; font-size: 1.7em; } /* This CSS ID class is applicable for the DIV element that holds the table in which all the available teller operations are listed. */ #tellerOptions { margin-left: 3%; margin-right: 3%; padding-top: 10px; padding-bottom: 10px; border: red; border-width: thick; border-style: double; border-collapse: collapse; } /* This qualified dependent selector applies style to the teller option table. */ table.tellerOptionsTable td { padding-top: 10px; padding-bottom: 10px; } /* This CSS generic class is applicable to style the margins for the tables in the teller actions form. */ .tellerOptionsTable, .depositActionTable, .debitActionTable, .portfolioActionTable { margin-top: 30px; } /* This CSS generic class is applicable for the SPAN elements that hold the clickable text representing various teller operations. User can select an operation to perform from this list. Generic class is used here so that inside of JavaScript, we can dynamically change the style of the text representing the teller operations as the user mouseover or mouseout of those text regions defined within the SPAN elements. The following is the default style when there is no user mouse activity or when the user moves the mouse out. */ .tellerOptionsLink { cursor: pointer; background-color: rgb(220, 220, 220); color: blue; font-weight: bold; font-style: normal; text-decoration: none; border-style: none; } .tellerOptionsLinkSelection, .goToMainMenuLinkSelection { cursor: pointer; background-color: green; color: yellow; font-weight: normal; font-style: italic; text-decoration: none; border-style: ridge; border-width: 7px; border-top-width: 7px; border-color: black; } .depositAction { margin-left: 3%; margin-right: 3%; padding-top: 10px; padding-bottom: 10px; cursor: default; border: navy; border-width: thick; border-style: double; border-collapse: collapse; } .debitAction { margin-left: 3%; margin-right: 3%; padding-top: 10px; padding-bottom: 10px; cursor: default; border: lightseagreen; border-width: thick; border-style: double; border-collapse: collapse; } .portfolioAction { margin-left: 3%; margin-right: 3%; padding-top: 10px; padding-bottom: 10px; cursor: default; border: purple; border-width: thick; border-style: double; border-collapse: collapse; } .tellerActionResult { margin-left: 3%; margin-right: 3%; padding-top: 10px; padding-bottom: 10px; cursor: default; border: rosybrown; border-width: thick; border-style: double; border-collapse: collapse; } /* This qualified CSS generic class defines the cell padding for the buttons in the teller actions form and others. */ table.depositAmountButtonTable td, table.debitAmountButtonTable td, table.portfolioValueButtonTable td, table.tellerActionResultTable td { padding-left: 25px; padding-right: 25px; } /* This qualified CSS generic class defines the margin for the tables holding the teller actions form and others. */ table.depositAmountButtonTable, table.debitAmountButtonTable, table.portfolioValueButtonTable, table.tellerActionResultTable { margin-top: 40px; } .depositActionFormTitle, .debitActionFormTitle, .portfolioActionFormTitle, .tellerActionResultTitle { color: olive; font-weight: normal; font-style: normal; text-decoration: underline; text-align: center; } /* This qualified CSS generic class is applicable in order to set the cellpadding for the table <td> where the footer options are listed. */ table.footerTable td { padding-left: 100px; padding-right: 100px; } /* This qualified CSS generic class is applicable in order to set the margin for the table where the footer options are listed. */ table.footerTable { margin-top: 20px; } /* This CSS generic class is applicable for the static display of the current username who is logged into the system. */ .currentUserDisplay { cursor: default; background-color: rgb(220, 220, 220); color: darkred; font-weight: normal; font-style: normal; } /* This CSS generic class is applicable for the SPAN elements that hold the clickable text representing footer options. User can select to logout from the system. Generic class is used here so that inside of JavaScript, we can dynamically change the style of the text representing the logout link as the user mouseover or mouseout of those text regions defined within the SPAN elements. The following is the default style when there is no user mouse activity or when the user moves the mouse out. */ .gotoMainMenuLink { cursor: pointer; background-color: rgb(220, 220, 220); color: black; font-weight: bold; font-style: normal; text-decoration: none; border-style: none; } /* This CSS ID class is applicable for the Teller actions button and others. */ #depositAmountActionButton, #debitAmountActionButton, #portfolioActionButton { background-color: saddlebrown; color: white; border: purple; border-width: thin; border-style: inset; font-weight: bold; font-style: normal; font-size: 90% } /* The following overrides a style defined previously in the same class. */ #debitAmountActionButton { background-color: midnightblue; } /* The following overrides a style defined previously in the same class. */ #portfolioActionButton { background-color: teal; } |
清單 6. xhr.js /* ============================================================ Project: End-to-End-Ajax application development Purpose: This is an example scenario to be used in an IBM developerWorks article. Last modified: May/12/2007. This JavaScript file provides specific functions related to XHR (XML HTTP Request). The logic explained here is a generic one which is being used hundreds of other Web applications. This logic can be found in any Ajax related book or article that describes the inner workings of XHR. ============================================================ */ /* ============================================================ Function: createRequest Last Modified: May/12/2007 This function creates an XHR object that is browser agnostic. As of this writing, XHR is somewhat browser dependent. We can divide XHR support in two major classes of browsers i.e. Internet Explorer and non-IE browsers. Then, within IE, there are different implementations of XHR in pre-IE6 and the rest. In summary, Microsoft supports XHR through its ActiveX. After listening to complaints from the Web developer community, Microsoft realized the need to expose XHR as a native browser object as done in other browsers. The rumor is that we will soon see XHR support in IE browsers via XMLHttpRequest object. Until then, we have to take care of browser dependency as shown in this function. ============================================================ */ function createRequest() { // Define a local variable and set it to null. var request = null; // Try different things to see which browser is being used. try { // Non Microsoft browsers (Firefox, Safari etc.) request = new XMLHttpRequest(); } catch(trymicrosoft) { try { // IE6 and above. request = new ActiveXObject("Msxml2.XMLHTTP"); } catch(othermicrosoft) { try { // Older versions of IE i.e. pre-IE6. request = new ActiveXObject("Microsoft.XMLHTTP"); } catch(failed) { // No support for XHR request = null; } // End of catch(failed) } // End of catch(othermicrosoft) } // End of catch(trymicrosoft) // Check if we have a valid XHR object. if (request == null) { alert("Error creating the XMLHttpRequest object!"); } else { // Return the valid XHR object that was created. return(request); } // End of if (request == null) } // End of function createRequest. /* ============================================================ Function: sendHttpRequest Last Modified: May/12/2007 This function transmits any arbitrary data to a server URL. It takes four arguments: 1) XHR object 2) Callback function (if any) to receive the server response 3) URL to which the content is to be sent. 4) Data to be sent. The depending on the input parameters, it will either communicate with the server asynchronously or synchronously to do the data interchange. It uses the POST verb of HTTP to do the REST-style call. ============================================================ */ function sendHttpRequest(request, callbackFunction, url, postData) { // Initialize a local variable to false. // This variable indicates if we need to communicate with the // server in an asynchronous mode. var async_request = false; // Did the caller give us a Callback function? if (callbackFunction != null) { // We have a callback function. // Set that function to XHR object‘s onreadystatechange property. request.onreadystatechange = callbackFunction; // Set the local variable to indicate that // we need to send and receive the response in a non-blocking mode // i.e. Async. async_request = true; } // Open a HTTP connection to the provided URL. request.open("POST", url, async_request); // Set a HTTP request header. request.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); // Send it now. // If async mode was not true, then this call will block until a // HTTP response is received from the server. // Otherwise, this statement will just send and return without waiting for a // server response. var response = request.send(postData); if (async_request == false) { // We sent the request synchronously. // Hence, return the response received from the server. return(response); } else { // If the request was made asynchronously, we need not bother to return // anything meaningful. The response will be sent directly to the // callback function that was provided by the caller. return(true); } } // End of function sendHttpRequest. | 銀行門戶瀏覽器應(yīng)用程序中的邏輯 我們已經(jīng)創(chuàng)建了 XHTML 和 CSS 文件,以及與 XHR 相關(guān)的實(shí)用程序文件。XHTML 文件(index.html)是這個(gè)應(yīng)用程序的起點(diǎn)。這是銀行出納員應(yīng)用程序的用戶在瀏覽器的地址欄中能夠看到的惟一文件。其他所有文件都從服務(wù)器下載到 index.html 文件的瀏覽器會(huì)話。如果看一下 index.html 文件的 <HEAD> 部分,可以看到下面的 HTML 標(biāo)記: <script language="JavaScript" type="text/javascript" src="xyz.js"></script> | script 標(biāo)記定義一個(gè)或多個(gè)腳本,文檔中的元素可以調(diào)用這些腳本。在這個(gè)示例中,它從外部文件加載 JavaScript 代碼。銀行出納員應(yīng)用程序的 JavaScript 代碼是從三個(gè)不同的 JavaScript 文件加載的。 <link rel="stylesheet" type="text/css" href="xyz.css" media="all" /> | link 標(biāo)記鏈接一個(gè)外部文件中定義的樣式表(CSS)規(guī)則。這個(gè)標(biāo)記將指定的 CSS 文件鏈接進(jìn) HTML 文檔,因此影響整個(gè) Web 應(yīng)用程序的樣式。 與銀行出納員應(yīng)用程序的表示相關(guān)聯(lián)的所有組件只在啟動(dòng)時(shí)下載一次。這意味著需要將應(yīng)用程序中的各個(gè)屏幕分隔開(kāi),這樣才能根據(jù)需要向用戶顯示不同的屏幕。這很容易實(shí)現(xiàn),只需將一個(gè)屏幕的所有用戶界面控件包含在一個(gè)單獨(dú)的 <DIV> 元素中。整個(gè)應(yīng)用程序放在一個(gè)稱為 mainPage 的 <DIV> 元素中。這個(gè)瀏覽器應(yīng)用程序中有另外六個(gè)部分,它們都放在自己的 <DIV> 元素中。這六個(gè) <DIV> 元素嵌套在 mainPage <DIV> 元素中。通過(guò)以這種方式組織這些用戶界面部分,就更容易通過(guò) DOM 控制它們,本系列的第 3 部分將解釋 DOM 操作。 當(dāng)加載這個(gè)應(yīng)用程序時(shí),在 <body> 元素中設(shè)置一個(gè)事件處理函數(shù)(initOnPageLoad )。在剛啟動(dòng)時(shí)調(diào)用這個(gè)事件處理函數(shù),將初始屏幕內(nèi)容設(shè)置為可見(jiàn)。與 <DIV> 標(biāo)記相似,<SPAN> 標(biāo)記也用來(lái)劃分文檔;在這個(gè)示例中,將文本包含在 <SPAN> 標(biāo)記中,從而在初始屏幕上建立菜單式的結(jié)構(gòu)。注意,這些 <SPAN> 元素上設(shè)置了與鼠標(biāo)操作相關(guān)的三個(gè)事件處理函數(shù)。這些事件處理函數(shù)僅僅使用 CSS 樣式規(guī)則改變菜單項(xiàng)的顯示方式。當(dāng)單擊這些菜單文本項(xiàng)時(shí),對(duì)應(yīng)的 onclick 事件處理函數(shù)讓對(duì)應(yīng)的屏幕出現(xiàn)在瀏覽器窗口中。這些操作都不需要訪問(wèn)服務(wù)器,只需在本地操作瀏覽器 DOM。另一個(gè)有意思的用戶界面控件是頁(yè)腳部分,這個(gè)部分出現(xiàn)在每個(gè)屏幕的底部。在任何時(shí)候,屏幕布局都包含 mainPage 和 pageFooter <DIV> 元素。其他部分(存款、取款、投資組合或銀行出納員操作結(jié)果)之一放在這兩個(gè) <DIV> 元素之間 —— 這是另一個(gè)靈活的 Ajax 設(shè)計(jì)原則。 CSS 文件(BankTeller.css )包含應(yīng)用于這個(gè)瀏覽器應(yīng)用程序的用戶界面元素的 CSS 規(guī)則。這些 CSS 規(guī)則演示了 CSS 的幾個(gè)特性。尤其是,這個(gè)應(yīng)用程序中大量使用了 HTML 選擇符、ID 選擇符和類選擇符。 例如,在 CSS 文件的頂部,body HTML 選擇符重新定義整個(gè)瀏覽器應(yīng)用程序頁(yè)面的字體和背景顏色。在 CSS 文件的底部,使用了 portfolioActionButton ,這是一個(gè) ID 選擇符。這意味著,HTML 文件(index.html)中帶有這個(gè) ID 字符串的 HTML 標(biāo)記將采用 #portfolioActionButton CSS 規(guī)則下定義的樣式。在 .tellerOptionsLink CSS 規(guī)則中使用了類選擇符,所以其中的樣式應(yīng)用于分配了 tellerOptionsLink 類的所有 HTML 標(biāo)記。還要注意組選擇符,也就是兩個(gè)或更多的選擇符組合在一起,使用相同的樣式聲明。BankTeller.css 文件中的許多地方使用了組選擇符。在這個(gè) CSS 文件的底部,可以看到三個(gè)操作按鈕的 ID 組合在一起(#depositAmountActionButton、#debitAmountActionButton 和 #portfolioActionButton),因此它們采用相同的按鈕樣式屬性。這里還演示了另一個(gè)高級(jí)的 CSS 特性,后代選擇符(descendant selector)。通過(guò)使用后代選擇符,可以根據(jù)父元素對(duì)后代元素應(yīng)用樣式。例如,對(duì)一個(gè)表格中的所有列應(yīng)用相同的單元格填充(padding)。在這個(gè) CSS 文件中搜索 table.depositAmountButtonTable td ,就會(huì)看到后代選擇符的使用方法??梢詤⒖?圖 2,了解 HTML、ID 和類選擇符的 CSS 語(yǔ)法。 XHR 文件(xhr.js )包含兩個(gè)函數(shù)。一個(gè)函數(shù)創(chuàng)建一個(gè) XHR 對(duì)象,可以使用這個(gè)對(duì)象與中間層服務(wù)交換(發(fā)送和接收)應(yīng)用程序信息。createRequest 函數(shù)準(zhǔn)備一個(gè) XHR 對(duì)象。到編寫(xiě)本文時(shí),不同的瀏覽器仍然以不同的方式支持 XMLHttpRequest 。特別是,兩種最流行的瀏覽器 —— Internet Explorer(IE)和 Mozilla Firefox —— 支持 XHR 的方式是不一致的。 在 IE 的不同版本中,以不同方式實(shí)現(xiàn) XHR。這個(gè)函數(shù)會(huì)嘗試使用瀏覽器特有的技術(shù)創(chuàng)建一個(gè) XHR 對(duì)象。在非 IE 瀏覽器中,只需對(duì)內(nèi)置的 XMLHttpRequest 類進(jìn)行實(shí)例化,就可以創(chuàng)建 XHR 對(duì)象。在 IE 6.0 和更高版本中,使用 ActiveXObject 和 Msxml2 中的 XMLHTTP 。在其他所有 IE 版本中,使用 ActiveXObject 和 XMLHTTP 的不同實(shí)例。Microsoft 正在開(kāi)發(fā)與其他瀏覽器相似的 XHR 支持。在 Microsoft 完成這項(xiàng)工作之前,仍然需要使用這個(gè)函數(shù)中的邏輯。另一個(gè)函數(shù) sendHttpRequest 用來(lái)向中間層服務(wù)發(fā)送任意數(shù)據(jù)。它按照 REST 風(fēng)格的訪問(wèn)機(jī)制使用 POST HTTP 方法與中間層服務(wù)進(jìn)行交互。這個(gè)函數(shù)接受以下四個(gè)參數(shù): - XHR 對(duì)象
- 回調(diào)函數(shù)(如果不需要回調(diào),就是 null)
- 數(shù)據(jù)需要發(fā)送到的目標(biāo) URL
- 數(shù)據(jù)(發(fā)送給中間層服務(wù)的應(yīng)用程序數(shù)據(jù))
sendHttpRequest 方法使用 XHR 對(duì)象將數(shù)據(jù)發(fā)送給中間層服務(wù)。如果提供了回調(diào),那么這個(gè)函數(shù)將 XHR 對(duì)象的 onreadystatechange 屬性設(shè)置為這個(gè)回調(diào)函數(shù)。然后,它打開(kāi)一個(gè)到中間層服務(wù)的 HTTP 連接。它設(shè)置所需的 HTTP 頭,然后將數(shù)據(jù)發(fā)送給中間層服務(wù)。如果提供了回調(diào)函數(shù),那么 XHR 對(duì)象的 send 方法立即返回,并不等待服務(wù)器響應(yīng)。在這種情況下,當(dāng)服務(wù)器響應(yīng)到達(dá)瀏覽器時(shí),回調(diào)函數(shù)會(huì)處理服務(wù)器響應(yīng)。第 3 部分將討論這個(gè)過(guò)程的細(xì)節(jié)。如果沒(méi)有提供回調(diào)函數(shù),那么 send 函數(shù)阻塞,一直等到服務(wù)器響應(yīng)到達(dá),或者引發(fā) HTTP 錯(cuò)誤。 清單 4 中的單頁(yè)面瀏覽器應(yīng)用程序代碼生成一個(gè)簡(jiǎn)單的瀏覽器界面,銀行出納員使用這個(gè)界面執(zhí)行銀行操作,比如存款、取款和計(jì)算投資組合的價(jià)值。代碼提供三個(gè)表單。這三個(gè)表單都提供一個(gè) HTML 下拉框,這個(gè)框中填充帳戶持有人的姓名。然后,提供一個(gè)編輯框,用戶可以在其中輸入存款或取款的數(shù)量。銀行出納員可以使用三個(gè)操作按鈕執(zhí)行所需的銀行函數(shù)。這個(gè)應(yīng)用程序中的三個(gè)按鈕訪問(wèn)一個(gè)中間層 REST 服務(wù),我們將在第 3 部分中開(kāi)發(fā)這個(gè)服務(wù)。在開(kāi)發(fā)這個(gè) PHP 模塊之前,這些銀行出納員函數(shù)無(wú)法發(fā)揮作用。另外,在第 3 部分中還要開(kāi)發(fā) JavaScript 文件(BankTeller.js )。 在開(kāi)發(fā)這個(gè)銀行場(chǎng)景時(shí),使用了許多新的 Ajax 技術(shù)。如果您是剛?cè)腴T的 Web 開(kāi)發(fā)人員,可能不熟悉這里討論的一些領(lǐng)域。您應(yīng)該全面地分析這些不完整的瀏覽器代碼和中間層代碼,從而充分地理解它們。如果您是有經(jīng)驗(yàn)的 Web 開(kāi)發(fā)人員,可以嘗試自己完成余下的任務(wù)。如果您無(wú)法完成余下的任務(wù),也不用擔(dān)心 —— 本系列的第 3 部分將幫助您完成這些任務(wù)。 結(jié)束語(yǔ) 到目前為止,我們已經(jīng)構(gòu)建了銀行場(chǎng)景的主要部分。盡管我們只討論了端到端 Ajax 應(yīng)用程序場(chǎng)景的一部分,但是也學(xué)到了不少東西,包括數(shù)據(jù)庫(kù)、PHP 模塊中的核心業(yè)務(wù)邏輯以及用 Ajax 技術(shù)構(gòu)建的 Web 用戶界面。看看 第 1 部分 中的銀行場(chǎng)景圖就會(huì)發(fā)現(xiàn),還需要完成以下任務(wù)才能完成整個(gè)場(chǎng)景: - 開(kāi)發(fā)一個(gè) JavaScript 模塊,它包含客戶端邏輯以及與服務(wù)器進(jìn)行數(shù)據(jù)交換的異步通信邏輯。
- 開(kāi)發(fā)一個(gè) PHP 模塊,它包含一個(gè)遠(yuǎn)程股票報(bào)價(jià) Web 服務(wù)的 SOAP 客戶機(jī)代碼。
- 開(kāi)發(fā)一個(gè) PHP 模塊,它作為 REST 服務(wù),將調(diào)用轉(zhuǎn)發(fā)給其他 PHP 模塊中的業(yè)務(wù)邏輯。
- 將所有組件集成起來(lái)。
- 部署和測(cè)試這個(gè)端到端 Ajax-PHP 場(chǎng)景。
- 使用客戶端和服務(wù)器端調(diào)試器進(jìn)行調(diào)試。
學(xué)習(xí)了 第 1 部分 和第 2 部分中的內(nèi)容之后,您現(xiàn)在應(yīng)該已經(jīng)體會(huì)了在三個(gè)應(yīng)用程序?qū)由祥_(kāi)放源碼技術(shù)提供的強(qiáng)大功能,還了解了 Eclipse Ajax-PHP 開(kāi)發(fā)工具的作用。本文提供的 下載文件 包含創(chuàng)建數(shù)據(jù)庫(kù)的 SQL 代碼、用 PHP 編寫(xiě)的服務(wù)器端銀行邏輯以及銀行門戶代碼(XHTML、CSS 和 XHR)。請(qǐng)等待本系列的第 3 部分,也就是最后一部分。在此之前,請(qǐng)復(fù)習(xí)銀行場(chǎng)景中目前已經(jīng)應(yīng)用的核心 Ajax 和 PHP 概念以及開(kāi)發(fā)技術(shù),盡可能熟悉這些知識(shí)。
下載 名字 | 大小 | 下載方法 | wa-aj-end2end2.zip | 14KB | HTTP |
參考資料 學(xué)習(xí) 獲得產(chǎn)品和技術(shù)
關(guān)于作者 | | | Senthil Nathan 是位于紐約 Hawthorne 的 IBM T.J. Watson Research Center 的一位高級(jí)軟件工程師。在為不同類型的企業(yè)應(yīng)用程序構(gòu)建軟件方面,他有 22 年經(jīng)驗(yàn)。他當(dāng)前感興趣的領(lǐng)域包括 SOA、Web 服務(wù)、Java 2 Platform, Enterprise Edition(J2EE)、PHP、Ruby On Rails、Web 2.0 和 Ajax 開(kāi)發(fā)。 | |