转载--开发端到端的 Ajax 应用程序,第 2 部分: 实现 Ajax 客户机和服务器层

来源:互联网 发布:剑灵人族御姐捏脸数据 编辑:程序博客网 时间:2024/05/20 05:07
 
开发端到端的 Ajax 应用程序,第 2 部分: 实现 Ajax 客户机和服务器层
隔离应用程序层来产生干净优雅的 Web 应用程序
developerWorks 文档选项 将此页作为电子邮件发送
将此页作为电子邮件发送
样例代码


级别: 中级
Senthil Nathan (sen@us.ibm.com), 高级软件工程师, IBM
2007 年 7 月 02 日
Ajax (Asynchronous JavaScript + XML)正在迅速地成为时髦的技术,它可以为在浏览器中运行的 Web 应用程序提供具有桌面质量的软件特性。这个分三部分的系列讨论如何使用开放源码技术开发端到端 Ajax 应用程序,本文是这个系列的第二篇文章。

在这个分三部分的系列的 第 1 部分 中,了解了 Firefox、Zend Core 和 MySQL 等开放源码技术的重要特性。我们讨论了一个比较复杂的银行场景,这个场景跨越 Ajax 应用程序的所有三层。还设置了开发端到端 Ajax 应用程序所需的数据库服务器、中间层服务器和基于 Eclipse 的 IDE。在本系列的第 2 部分中,将开发银行场景的一些部分。具体地说,将使用 MySQL 数据库创建一个后端数据库。我们将研究几个 MySQL 命令行工具,并使用这些工具连接数据库、在数据库中创建、定义和填充与银行相关的数据。然后,开发一个中间层 PHP 模块来提供银行的业务逻辑,这个模块使用 ODBC 连接 MySQL 数据库。最后,开发一个银行门户,用户可以通过这个简单的浏览器用户界面与这个端到端应用程序进行交互,这个应用程序不久之后就可以在 Zend Core 上运行。

简介
请访问 Ajax 技术资源中心,这是有关 Ajax 编程模型信息的一站式中心,包括很多文档、教程、论坛、blog、wiki 和新闻。任何关于 Ajax 的新信息都能在这里找到。

正如 第 1 部分 所指出的,这个银行场景主要提供银行出纳员执行的基本服务。如果您还不了解这个场景,请阅读 第 1 部分。 客户数据是这个场景的重要部分。对于本系列的场景,所有客户数据将同时填充进数据库表中。在此之后,可以通过 Zend Core 提供的 ODBC MySQL 驱动程序获取和更新存储的客户数据。处理了客户数据之后,重点转移到提供银行出纳功能所需的核心银行逻辑。我们开发一个 PHP 代码模块来提供核心银行逻辑,并使用 ODBC 进行必要的数据库访问。使用 Zend Core 和 PHP 实现银行逻辑的主要优点是,可以利用内置的 MySQL 支持。

在建立数据库并开发 PHP 模块之后,为银行出纳员提供执行四个核心功能的用户界面。我们通过一个瘦客户机访问 PHP 模块中封装的核心银行逻辑。具体地说,这个基于 Web 的瘦客户机是按照 Ajax 风格生成的:XHTML、Cascading Style Sheet(CSS)、JavaScript 和 XMLHttpRequest(XHR)。它为银行出纳员提供执行核心银行功能的简单用户界面。这个浏览器用户界面还演示浏览器客户机逻辑与服务器端 PHP 逻辑进行网络通信的方法。

使 用 Ajax 的目的是设计出对用户交互响应得非常快的浏览器应用程序。Ajax 还会减轻服务器加载页面的沉重负载。在 Ajax 应用程序中,浏览器和服务器之间的交互仅仅是为了交换与应用程序相关的数据,这会优化服务器资源的使用,让服务器资源能够支持更多的用户和更多的应用程 序。本系列讲解一些基本的 Ajax 技术,介绍构建单页面浏览器应用程序所涉及的概念。
到本文结束时,我们会建立数据库、一个提供核心银行逻辑的 PHP 模块以及一个单页面浏览器用户界面,这些都是银行场景的组成部分。
MySQL 数据库

正如在 第 1 部分 中指出的,MySQL 是一种开放源码数据库。在我们的场景中,使用社区服务器版本,这是一个紧凑的数据库服务器,具有许多有用的特性。因为这个银行场景的实现基于开放源码产 品,所以 MySQL 和 Zend Core PHP 是合适的组合。Zend Core 本身支持 MySQL,还有各种支持 MySQL 管理和编程的工具。在我们的场景中,只使用 MySQL 命令行客户机执行 MySQL 的管理。我们将用 MySQL 数据库为这个场景建立银行帐户数据库。

创建并填充银行数据库
在这个场景中,将为给定的客户存储以下帐户信息:
  • AccountHolderName
  • AccountNumber
  • CheckingBalance
  • StockName
  • StockQuantity
  • StockValue
给定客户的帐户信息包括帐户持有人的姓名、帐户号、当前的资产余额、客户拥有的一只股票的编号、拥有的股票总数以及股票投资组合的当前市值。下面几节详细介绍如何创建数据库表,然后用一些虚构的银行客户的帐户信息填充这个表。我们开始吧!
按照以下步骤创建数据库,然后用应用程序相关数据填充这个表:
  1. 如果 Eclipse 还未运行的话,就启动它(c:/eclipse/eclipse.exe)。
  2. 确保在 Eclipse 中启用了 PHP 透视图:
    1. 选择 Window->Open Perspective->Other->PHP 并单击 OK
  3. 在 Eclipse 中,选择 File->New->Project
  4. 选择 General->Project 并单击 Next
  5. 在 project name 字段中输入 BankDB
  6. 单击 Finish
  7. 右击 BankDB project 并选择 New->Other
  8. 选择 General-> File 并单击 Next
  9. 在 File name 字段中,输入 BankDB.sql 并单击 Finish
  10. 输入或粘贴 清单 1 中的代码作为 BankDB.sql 的内容。
  11. 保存并关闭这个文件。
  12. 为了启动 MySQL 命令行客户机,单击 Windows Start Menu->All Programs->MySQL->MySQL Server->MySQL Command Line Client
  13. 在 MySQL 命令行窗口中,输入密码 webtech 并按 Enter。
  14. 在 mysql> 提示下,输入 source c:/eclipse/workspace/BankDB/BankDB.sql 并按 Enter。
  15. 检查 BankDB 数据库是否存在,以此确认前面的命令已经正确地执行了。检查所用的命令如下:
    1. show databases;
    2. use bankdb;
    3. show tables;
    4. describe account;
  16. 在 MySQL 命令行客户机中,输入 exit 关闭它。

清单 1. BankDB.sql 文件的内容
                

-- 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 'account'
--

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 访问 MySQL 数据库

PHP 最受人喜爱的特性之一是,它为访问不同厂商的数据库中的数据提供了简单且出色的支持,包括 MySQL。它提供了一种集成业务逻辑和数据库的简便有效的方法。在过去几年中,PHP 社区完成了几项改进,比如 PHP Data Objects(PDO),PDO 提供一个抽象层,无论使用哪种数据库服务器,这个抽象层都公开同样的 API 函数。有两种风格的 PHP 数据库 API 函数:过程式的和面向对象的。在这个场景中,我们使用针对 MySQL 的直接数据库 API 来获得一个基本的了解。PHP 提供两种访问 MySQL 的方法:

  • MySQL
  • MySQL Improved

常 规的 MySQL 扩展不完全支持 MySQL 4.1.0 的所有功能。这些特性包括存储过程、触发器和视图。MySQL Improved(mysqli)扩展是访问这些功能的最新方式。在 PHP 5.0 或更高版本中,可以使用 mysqli 的扩展。但是,mysqli 在默认情况下没有启用 —— 必须使用 Zend Core 管理工具启用它。在这个系列的 第 1 部分 中,在安装和配置 Zend Core 时已经启用了 mysqli。

PHP 数据库 API 的出色特性之一是,它们可以将数据库操作的结果以许多不同方式映射到 PHP 数据结构。换句话说,PHP 中的数据库 API 可以以 PHP 关联数组的形式返回数据库结果,其中数据库表的列名作为数组的键。在另一种情况下,它可以以 PHP 类对象的形式返回结果,其中数据库表的列名作为对象的属性。因此可以以方便的方式表示数据库查询的结果。

构建 PHP MySQLi 函数块
正如前面提到的,PHP 提供了许多 mysqli 函数。本节只解释那些用来实现银行场景的函数。具体地说,这里讨论连接、读、写和释放连接等数据库函数。更多细节请参考 参考资料 中列出的 PHP 官方文档。
在执行任何数据库操作之前,必须建立到数据库服务器的连接。在 PHP 中,使用以下函数连接数据库:
$link = mysqli_connect("hostname", "user", "password", "dbname");

这个函数尝试用正确的凭证连接位于指定主 机上的指定数据库。这个函数还接受其他可选参数(参见 PHP 文档)。如果成功地连接到数据库,这个函数会返回一个连接对象,后续的数据库读写操作需要使用这个对象。如果无法建立数据库连接,那么这个函数返回 false。如果不希望在代码中以明文形式直接嵌入数据库服务器密码,那么可以考虑使用 MySQL 的密码摘要特性。

与连接操作对应的函数是数据库关闭函数:
$result = mysqli_close($link);

这个函数尝试关闭以前打开的数据库连接。它以 MySQL 数据库连接对象作为参数;如果数据库关闭操作成功,它就返回 true,否则返回 false。
在讨论了连接管理函数之后,我们来看看数据库读/写操作涉及的函数。下面是读/写操作涉及的一些函数:
  • (1) $result = mysqli_query($link, $queryStr);
  • (2) $numberOfRows = mysqli_num_rows($result);
  • (3) $row = mysqli_fetch_assoc($result);
  • (4) $row = mysqli_fetch_object($result);
函数 (1) 对数据库执行一个 SQL 查询。它的参数是连接对象和一个有效的 SQL 查询字符串。它在查询成功时返回 true,在失败时返回 false。但是,对于 SELECT SQL 查询,这个函数返回一个结果对象。
函数 (2) 计算一个 SQL 查询返回的结果集中的行数。它的参数是结果对象。
函数 (3) 以 PHP 关联数组的形式返回结果行。这个数组以数据库列名作为数组键,以数据库字段值作为数组值。例如,如果一个数据库查询返回 “capital” 列的值,那么可以通过引用数组来访问结果:
$stateCapital = $row['capital'];

函数 (4) 以 PHP 对象的形式返回结果行。这个对象以数据库列名作为对象属性,以数据库字段值作为对象属性值。例如,如果一个数据库查询返回 “park” 列的值,那么可以通过引用对象值来访问结果:
$nationalPark = $row->park;

函数 (3) 和 (4) 可以非常简便地将数据库值映射到程序变量。正如前面提到的,PHP 还提供了执行数据库值映射的其他方法。
在我们的场景中,在执行数据库操作之后,要使用另外两个函数进行错误处理。了解数据库操作是否成功执行是非常重要的。下面两个函数可以方便地处理这个问题:
  • $returnCode = mysqli_errno($link);
  • $errorMsg = mysqli_error($link);

mysqli_errno 函数返回最近的函数调用的错误码。它的参数是连接对象,它返回已经执行的函数的返回码。返回码为 0 就说明没有错误。mysqli_error 函数返回最近的错误的字符串描述。这两个函数对于完成数据库查询的整个处理过程是非常重要的。

在 PHP 模块中实现银行业务逻辑
按照以下步骤创建一个 PHP 模块,并实现中间层业务逻辑:
  1. 切换到 Eclipse PHP 透视图:单击 Window->Open Perspective->Other->PHP 并单击 OK
  2. 单击 File->New->PHP Project
    1. Project name 字段中,输入 BankTeller
    2. 单击 Finish
  3. Project Explorer 视图中,右击 BankTeller 项目并选择 New->PHP File
    1. File Name 字段中,输入 BankLogic.php 并单击 Finish
  4. 输入或粘贴 清单 2 中的源代码作为这个文件的内容。
    1. 单击 File->Save 保存文件。
  5. 通过这个文件中的注释了解代码的作用,或者参考下一节中对代码逻辑的解释。
可以看出在 PHP 中执行数据库操作是多么简便。您可能会注意到,在清单 2 中密码是明文形式的。这个密码只用在为本文创建的测试数据库中。可以使用密码摘要替代明文,从而进一步改进这个 PHP 模块。

清单 2. BankLogic.php 文件的内容
                

<?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 文件包含核心银行出纳员函数所需的业务逻辑。具体地说,这些函数包括获取数据库中存储的所有帐户信息、执行存款或取款操作以及计算给定客户的股票投资组合 价值。这些函数都接受某些输入参数,并返回一个包含相关帐户信息的应用程序专有的关联数组。这些函数中的逻辑都需要连接 BankDB MySQL 数据库,并执行读或更新操作。清单 2 的开头定义三个全局范围的 PHP 变量。这些变量存储数据库连接对象 ($link)、SQL 查询结果对象 ($dbResult) 以及要返回给这个 PHP 模块的调用者的最终结果 ($finalResult)。这个 PHP 模块用两个实用程序函数连接 MySQL 数据库和断开连接。在建立连接时,创建一个连接对象并将它存储在 $link 变量中,因为这是一个全局变量,所以可以在整个 PHP 文件中使用它。同样,在执行数据库读或更新时,将一个 SQL 查询结果存储在 $dbResult 变量中,这也是一个全局变量。当需要关闭数据库连接时,使用另一个实用程序函数 (close_connection_to_db) 断开数据库连接。在关闭数据库连接之前,这个实用程序函数确保释放 $dbResult 中的结果集占用的内存。

这个文件中有三个核心函数:
  • 调用者通过调用 getAllAccountInformation 函数获取数据库中存储的所有帐户数据。这里的代码使用 MySQLi 扩展 API 建立到 MySQL 数据库的 ODBC 连接,并获取帐户表中存储的所有帐户信息。在数据库读操作成功之后,它循环遍历结果集并收集帐户数据信息。它将所有帐户数据信息存储在一个 PHP 关联数组中,并将这个数组返回给调用者。
  • 调用者通过调用 accountTransaction 函数执行存款或取款操作。调用这个方法时使用的参数是帐户持有人姓名、交易数量和交易类型(指明是存款还是取款操作)。建立到 MySQL 数据库的 ODBC 连接,从数据库获取给定帐户持有人的当前帐户数据,并将这些信息存储在一个关联数组中。这是这个帐户在交易之前的快照。然后,执行所需的操作(存款或取 款)。用存款或取款操作产生的新的帐户余额更新数据库。这是交易之后的数据,也存储在另一个关联数组中。现在,将交易之前和交易之后的帐户快照存储在一个 常规数组中,并将这个数组作为最终结果返回给调用者。还要注意,只有在交易数量符合交易类型的限制时,代码才会执行交易。
  • 调用者通过调用 portfoliovalue 方法计算给定帐户持有人的股票投资组合价值。调用这个方法时使用的参数是帐户持有人姓名,以及这个帐户持有人拥有的单只股票的当前价格。这个方法获取帐户 数据的交易前快照,并将它存储在一个关联数组中。然后,计算新的股票总价值,并更新 BankDB 数据库中对应的数据库字段。它还将帐户数据的交易后快照存储在另一个关联数组中。它将两个帐户数据快照存储在一个常规数组中,并将这个数组作为最终结果返 回给客户机。

源文件 中的注释可以帮助您理解代码逻辑。

单页面 Ajax 用户界面

为 了提供银行出纳员用户界面,我们要开发一个单页面浏览器应用程序。注意,单页面浏览器应用程序与传统的 “点击并等待” 式 Web 应用程序不同。在 “点击并等待” 式 Web 应用程序中,每当用户到达新的页面时,浏览器会从服务器下载这个 Web 页面的完整内容。另一方面,单页面浏览器应用程序背后的模型完全相反;在这种模型中,应用程序所需的所有表示内容(XHTML、CSS 和 JavaScript)只从服务器下载一次。这种一次性的下载通常在启动时进行,也就是当用户通过浏览器访问应用程序的 URL 时。XHTML 文件定义应用程序所需的所有用户界面元素。CSS 文件提供用户界面不同部分所需的所有样式。特殊的 Document Object Model(DOM)技术用来隐藏和显示用户界面的某些部分,因此无需从服务器获取另一个页面,就可以模拟多页面的效果。在浏览器中运行的与应用程序相关 的 JavaScript 可以处理大部分应用程序逻辑。使用 Ajax 技术的目的是设计出对用户交互响应得非常快的浏览器应用程序。Ajax 还会减轻服务器加载页面的沉重负载。在 Ajax 应用程序中,浏览器和服务器之间的交互仅仅是为了交换与应用程序相关的数据,这会优化服务器资源的使用,让服务器资源能够支持更多的用户和更多的应用程 序。

除了这里提到的好处之外,还有其他体系结构方面的考虑因素,比如数据安全性、数据私密性和业务逻辑的 适当分隔,由于业务逻辑在浏览器客户机上运行,在这些方面也会有好处。另外,当 HTTP 事务被中断时,需要从错误中恢复。这些考虑因素超出了本文的范围,本文只希望实现一个简单的单页面浏览器应用程序。同样,可以组合使用许多 Ajax 技术,为浏览器应用程序提供丰富的图形效果。在本文中,我们只讨论一些基本的技术,介绍构建单页面浏览器应用程序所涉及的概念。

在单页面浏览器应用程序中,另一种用来隐藏和显示不同部分的技术是使用 HTML 标记 <DIV>、<SPAN> 和 <TABLE>。图 1 简要说明了这些标记的作用。<DIV> 标记可以将 HTML 文档分隔成单独的部分。在分隔一个应用程序的许多屏幕时,第一步是将一个屏幕中的用户界面元素放在一个 <DIV> 标记中。在此之后,通过 JavaScript 控制在应用程序的某个执行阶段应该显示哪个屏幕(即 <DIV>)。另一个方便的标记是 <SPAN> 标记,可以使用这个标记对浏览器应用程序中的文本段应用样式。在银行场景中,使用这个标记在屏幕上创建菜单控件。另外,还使用 <TABLE> 标记管理文档的布局。


图 1. <SPAN>、<DIV> 和 <TABLE> 标记
HTML SPAN、DIV 和 TABLE 标记

在 浏览器应用程序中,创建丰富的效果的另一种常用方法是对各个用户界面元素应用样式。CSS 常常用来管理浏览器应用程序的整体外观。CSS 是单独的代码,具有它自己的语法。CSS 很容易设置,因为它不需要任何插件 —— 只需在文本文件中定义样式规则即可。CSS 规则由一个选择符和一个声明块组成。每个规则的开头是选择符,后面是大括号。声明块放在大括号中,由声明组成。声明是属性和值对,声明之间由分号分隔。下 面的 清单 3 给出一个简单的 CSS 规则示例。图 2 显示各种 CSS 选择符。

有其他文章解释了 CSS 选择符的所有特性。要想全面了解 CSS 的内部工作原理,应该花些时间阅读其他 CSS 资料。参考资料 中给出的 CSS Zen Garden 站点是体会 CSS 功能的好地方。
清单 3. 一个简单的 CSS 规则
                
body {
color: maroon;
font-size: medium;
text-align: center;
background:green;
}


图 2. CSS 选择符类型
CSS 选择符

几 个 Ajax 框架为 I/O、网络、图形、数据等方面提供了代码库。基于 Eclipse 的 Aptana 工具将许多 Ajax 框架集成在一个 IDE 中。但是,对 Ajax 框架的讨论超出了本文的范围。银行场景的 Ajax 相关代码是用普通的 JavaScript 实现的,这主要是为了保持简单,并让您能够直接体会应用程序中的代码,而不是通过框架库。在学完本系列之后,您应该研究一下 Ajax 框架,比如 Dojo、Rico、Script.aculo.us 和 Prototype。

Ajax 可以在与服务器交换数据时保持用户界面的响应性,可以避免中断用户的操作。
Ajax HTTP 交互

前 一节讨论了一些 XHTML、CSS 和 DOM 技术,这些都是 Ajax 的组成部分。另外,与中间层服务器的 HTTP 交互方式也是 Ajax 应用程序的核心性质。Ajax 使浏览器应用程序能够以一种新方式与服务器进行异步交互。与传统的浏览器应用程序不同,在 Ajax 应用程序中,当用户单击 HTML 表单上的 Submit 按钮时,用户不必等待页面出现。这种异步的 HTTP 通信由 XHR 支持,XHR 是一个浏览器对象,它使浏览器端 JavaScript 代码可以发出异步的 HTTP 服务器请求。因此,可以完全在后台发出 HTTP 请求、接收响应并更新页面的某些部分,这样就不会中断用户的操作。Ajax 可以在与服务器交换数据时保持用户界面的响应性。W3C 当前还在对 XHR 进行标准化(参见 参考资料)。图 3 显示 XHR 的各个对象方法和属性:


图 3. XML HTTP Request(XHR)对象方法和属性
XHR 对象方法
下面是 XHR 的操作流程:
  1. 创建一个 XMLHttpRequest 对象实例。
  2. 使用 XMLHttpRequest 对象对服务器进行调用,采用的方法是定义一个回调函数,当收到服务器响应时,会自动执行这个函数。
  3. 在回调函数中处理服务器的响应。
  4. 如果需要,再执行第 2 步,与服务器进行下一次异步交互。
后面将讨论在银行场景中使用 XHR 的实现细节。
构建 Ajax 浏览器应用程序的组件
我们的浏览器应用程序由以下部分组成:
  • XHTML 文件
  • CSS 文件
  • XHR JavaScript 文件
  • 用 JavaScript 编写的客户端业务逻辑
  • 需要的实用程序 JavaScript 文件

XHTML 文件是用户启动这个应用程序时浏览器指向的文件。这个文件包含所有用户界面元素,这些元素是用常用的 HTML 标记构建的,比如 <DIV>、<SPAN>、<TABLE> 和 <FORM>。这个文件还使用另外几种浏览器技术提供样式、通信和数据格式支持。

CSS 文件为用户界面元素提供样式规则。因为 CSS 是一个大主题,在这个场景中不可能涉及所有 CSS 特性。但是,我将在这个应用程序的上下文中介绍一些重要的 CSS 基本特性。
XHR 文件解释 XMLHttpRequest 对象的初始化逻辑。它还演示了如何以异步通信模式使用 XHR。我们并不重新设计 XHR 逻辑,而是按照 Web 开发社区广泛采用的一种方式使用 XHR。

在 客户端业务逻辑中,使用 JavaScript 建立银行场景屏幕的 Ajax 操作;这里采用一个事件模型,在这个模型中客户机与服务器的交互只是为了交换数据,而且可以异步地处理服务器响应。对于那些不熟悉 Ajax 概念的读者,这个模块演示了一些关键的概念,这些概念可以应用于任何单页面 Ajax 浏览器应用程序。

另外,这个浏览器应用程序的组件还包括一个用于 JavaScript Object Notation(JSON)数据处理的开放源码实用程序。本系列的第 3 部分将详细讨论这个实用程序。
将银行门户实现为 Ajax 浏览器应用程序

我 们的银行场景需要一个简单的浏览器界面,银行出纳员使用这个界面执行 PHP 模块中实现的核心函数。我们将使用 Aptana Web IDE 构建这个界面。Aptana Web IDE 提供了一种用 XHTML、CSS 和 JavaScript 构建浏览器应用程序的简便方法。Aptana 是一个免费的插件,可以无缝地插入 Eclipse 环境。这个插件仍然在开发阶段,但是目前的版本已经能够满足银行场景的需要了。尽管在银行场景中将使用一般的 JavaScript 实现 Ajax 特性,但是 Aptana 集成了几个开放源码 Ajax 框架库。

按照以下步骤创建单页面 Ajax 浏览器应用程序:
  1. 在 Eclipse 中,切换到 Aptana 透视图:选择 Window->Open Perspective->Other->Aptana 并单击 OK
  2. 在 Aptana 透视图左下方的窗格中,选择 Project 视图。
  3. 右击 BankTeller 项目并选择 New->HTML file
    1. 在 File name 字段中,输入 index.html 并单击 Finish
  4. 输入或粘贴 清单 4 中的源代码来替换 index.html 文件的内容,并单击 File->Save
  5. 右击 BankTeller 项目并选择 New->CSS file
    1. 在 File name 字段中,输入 BankTeller.css 并单击 Finish
  6. 输入或粘贴 清单 5 中的源代码来替换 BankTeller.css 文件的内容,并单击 File->Save
  7. 右击 BankTeller 项目并选择 New->JavaScript file
    1. 在 File name 字段中,输入 xhr.js 并单击 Finish
  8. 输入或粘贴 清单 6 中的源代码来替换这个文件的内容,并单击 File->Save
  9. 通过这些文件中的注释了解代码的作用,或者参考下一节中对代码逻辑的解释。
到目前为止,这个单页面 Ajax 浏览器应用程序所需的组件已经完成了三分之二!在本系列的第 3 部分中,将创建这个浏览器应用程序中余下的部分。

清单 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" _fcksavedurl="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-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.

银行门户浏览器应用程序中的逻辑

我 们已经创建了 XHTML 和 CSS 文件,以及与 XHR 相关的实用程序文件。XHTML 文件(index.html)是这个应用程序的起点。这是银行出纳员应用程序的用户在浏览器的地址栏中能够看到的惟一文件。其他所有文件都从服务器下载到 index.html 文件的浏览器会话。如果看一下 index.html 文件的 <HEAD> 部分,可以看到下面的 HTML 标记:

<script language="JavaScript" type="text/javascript" src="xyz.js"></script>

script 标记定义一个或多个脚本,文档中的元素可以调用这些脚本。在这个示例中,它从外部文件加载 JavaScript 代码。银行出纳员应用程序的 JavaScript 代码是从三个不同的 JavaScript 文件加载的。
<link rel="stylesheet" type="text/css" href="xyz.css" media="all" />

link 标记链接一个外部文件中定义的样式表(CSS)规则。这个标记将指定的 CSS 文件链接进 HTML 文档,因此影响整个 Web 应用程序的样式。

与 银行出纳员应用程序的表示相关联的所有组件只在启动时下载一次。这意味着需要将应用程序中的各个屏幕分隔开,这样才能根据需要向用户显示不同的屏幕。这很 容易实现,只需将一个屏幕的所有用户界面控件包含在一个单独的 <DIV> 元素中。整个应用程序放在一个称为 mainPage 的 <DIV> 元素中。这个浏览器应用程序中有另外六个部分,它们都放在自己的 <DIV> 元素中。这六个 <DIV> 元素嵌套在 mainPage <DIV> 元素中。通过以这种方式组织这些用户界面部分,就更容易通过 DOM 控制它们,本系列的第 3 部分将解释 DOM 操作。

当加载这个应用程序时,在 <body> 元素中设置一个事件处理函数(initOnPageLoad)。 在刚启动时调用这个事件处理函数,将初始屏幕内容设置为可见。与 <DIV> 标记相似,<SPAN> 标记也用来划分文档;在这个示例中,将文本包含在 <SPAN> 标记中,从而在初始屏幕上建立菜单式的结构。注意,这些 <SPAN> 元素上设置了与鼠标操作相关的三个事件处理函数。这些事件处理函数仅仅使用 CSS 样式规则改变菜单项的显示方式。当单击这些菜单文本项时,对应的 onclick 事件处理函数让对应的屏幕出现在浏览器窗口中。这些操作都不需要访问服务器,只需在本地操作浏览器 DOM。另一个有意思的用户界面控件是页脚部分,这个部分出现在每个屏幕的底部。在任何时候,屏幕布局都包含 mainPagepageFooter <DIV> 元素。其他部分(存款、取款、投资组合或银行出纳员操作结果)之一放在这两个 <DIV> 元素之间 —— 这是另一个灵活的 Ajax 设计原则。

CSS 文件(BankTeller.css)包含应用于这个浏览器应用程序的用户界面元素的 CSS 规则。这些 CSS 规则演示了 CSS 的几个特性。尤其是,这个应用程序中大量使用了 HTML 选择符、ID 选择符和类选择符。

例如,在 CSS 文件的顶部,body HTML 选择符重新定义整个浏览器应用程序页面的字体和背景颜色。在 CSS 文件的底部,使用了 portfolioActionButton,这是一个 ID 选择符。这意味着,HTML 文件(index.html)中带有这个 ID 字符串的 HTML 标记将采用 #portfolioActionButton CSS 规则下定义的样式。在 .tellerOptionsLink CSS 规则中使用了类选择符,所以其中的样式应用于分配了 tellerOptionsLink 类的所有 HTML 标记。还要注意组选择符,也就是两个或更多的选择符组合在一起,使用相同的样式声明。BankTeller.css 文件中的许多地方使用了组选择符。在这个 CSS 文件的底部,可以看到三个操作按钮的 ID 组合在一起(#depositAmountActionButton、#debitAmountActionButton 和 #portfolioActionButton),因此它们采用相同的按钮样式属性。这里还演示了另一个高级的 CSS 特性,后代选择符(descendant selector)。通过使用后代选择符,可以根据父元素对后代元素应用样式。例如,对一个表格中的所有列应用相同的单元格填充(padding)。在这个 CSS 文件中搜索 table.depositAmountButtonTable td,就会看到后代选择符的使用方法。可以参考 图 2,了解 HTML、ID 和类选择符的 CSS 语法。

XHR 文件(xhr.js)包含两个函数。一个函数创建一个 XHR 对象,可以使用这个对象与中间层服务交换(发送和接收)应用程序信息。createRequest 函数准备一个 XHR 对象。到编写本文时,不同的浏览器仍然以不同的方式支持 XMLHttpRequest。特别是,两种最流行的浏览器 —— Internet Explorer(IE)和 Mozilla Firefox —— 支持 XHR 的方式是不一致的。
在 IE 的不同版本中,以不同方式实现 XHR。这个函数会尝试使用浏览器特有的技术创建一个 XHR 对象。在非 IE 浏览器中,只需对内置的 XMLHttpRequest 类进行实例化,就可以创建 XHR 对象。在 IE 6.0 和更高版本中,使用 ActiveXObject 和 Msxml2 中的 XMLHTTP。在其他所有 IE 版本中,使用 ActiveXObjectXMLHTTP 的不同实例。Microsoft 正在开发与其他浏览器相似的 XHR 支持。在 Microsoft 完成这项工作之前,仍然需要使用这个函数中的逻辑。另一个函数 sendHttpRequest 用来向中间层服务发送任意数据。它按照 REST 风格的访问机制使用 POST HTTP 方法与中间层服务进行交互。这个函数接受以下四个参数:
  • XHR 对象
  • 回调函数(如果不需要回调,就是 null)
  • 数据需要发送到的目标 URL
  • 数据(发送给中间层服务的应用程序数据)

sendHttpRequest 方法使用 XHR 对象将数据发送给中间层服务。如果提供了回调,那么这个函数将 XHR 对象的 onreadystatechange 属性设置为这个回调函数。然后,它打开一个到中间层服务的 HTTP 连接。它设置所需的 HTTP 头,然后将数据发送给中间层服务。如果提供了回调函数,那么 XHR 对象的 send 方法立即返回,并不等待服务器响应。在这种情况下,当服务器响应到达浏览器时,回调函数会处理服务器响应。第 3 部分将讨论这个过程的细节。如果没有提供回调函数,那么 send 函数阻塞,一直等到服务器响应到达,或者引发 HTTP 错误。

清单 4 中的单页面浏览器应用程序代码生成一个简单的浏览器界面,银行出纳员使用这个界面执行银行操作,比如存款、取款和计算投资组合的价值。代码提供三个表单。 这三个表单都提供一个 HTML 下拉框,这个框中填充帐户持有人的姓名。然后,提供一个编辑框,用户可以在其中输入存款或取款的数量。银行出纳员可以使用三个操作按钮执行所需的银行函 数。这个应用程序中的三个按钮访问一个中间层 REST 服务,我们将在第 3 部分中开发这个服务。在开发这个 PHP 模块之前,这些银行出纳员函数无法发挥作用。另外,在第 3 部分中还要开发 JavaScript 文件(BankTeller.js)。

在 开发这个银行场景时,使用了许多新的 Ajax 技术。如果您是刚入门的 Web 开发人员,可能不熟悉这里讨论的一些领域。您应该全面地分析这些不完整的浏览器代码和中间层代码,从而充分地理解它们。如果您是有经验的 Web 开发人员,可以尝试自己完成余下的任务。如果您无法完成余下的任务,也不用担心 —— 本系列的第 3 部分将帮助您完成这些任务。

结束语
到目前为止,我们已经构建了银行场景的主要部分。尽管我们只讨论了端到端 Ajax 应用程序场景的一部分,但是也学到了不少东西,包括数据库、PHP 模块中的核心业务逻辑以及用 Ajax 技术构建的 Web 用户界面。看看 第 1 部分 中的银行场景图就会发现,还需要完成以下任务才能完成整个场景:
  • 开发一个 JavaScript 模块,它包含客户端逻辑以及与服务器进行数据交换的异步通信逻辑。
  • 开发一个 PHP 模块,它包含一个远程股票报价 Web 服务的 SOAP 客户机代码。
  • 开发一个 PHP 模块,它作为 REST 服务,将调用转发给其他 PHP 模块中的业务逻辑。
  • 将所有组件集成起来。
  • 部署和测试这个端到端 Ajax-PHP 场景。
  • 使用客户端和服务器端调试器进行调试。

学习了 第 1 部分 和第 2 部分中的内容之后,您现在应该已经体会了在三个应用程序层上开放源码技术提供的强大功能,还了解了 Eclipse Ajax-PHP 开发工具的作用。本文提供的 下载文件 包含创建数据库的 SQL 代码、用 PHP 编写的服务器端银行逻辑以及银行门户代码(XHTML、CSS 和 XHR)。请等待本系列的第 3 部分,也就是最后一部分。在此之前,请复习银行场景中目前已经应用的核心 Ajax 和 PHP 概念以及开发技术,尽可能熟悉这些知识。






回页首

下载
名字 大小 下载方法 wa-aj-end2end2.zip 14KB HTTP 关于下载方法的信息

参考资料
学习
  • 您可以参阅本文在 developerWorks 全球站点上的 英文原文 。

  • 阅读 “开发端到端的 Ajax 应用程序” 系列中的其他文章。

  • 阅读 IBM developerWorks Ajax 资源中心 中关于 Ajax 的文章和教程。

  • 阅读 IBM developerWorks PHP 资源中心 中关于 PHP 的文章和教程。

  • 阅读 PHP Manual Web 站点 上的 PHP API 文档。

  • 了解 Eclipse PDT 项目。

  • 了解 Aptana Web IDE。

  • 阅读 Jesse James Garrett 所写的 “Ajax: A New Approach to Web Applications”,他在这篇文章中首创了 Ajax 这个词。

  • 了解 Zend Core。

  • 阅读关于 XMLHttpRequest 的 W3C Working Draft。

  • 阅读 PHP MySQLi API 文档。

  • 通过 CSS Zen Garden 中令人惊异的演示,体会 CSS 的强大功能。

获得产品和技术
  • 下载 Mozilla Firefox。

  • 下载 MySQL。

  • 下载 Zend Core。

  • 从 Zend PDT 下载 Web 站点 下载 Eclipse PDT(PHP Development Tools)。

  • 下载 MySQL PHP 连接器。


转自:http://www.ibm.com/developerworks/cn/web/wa-aj-end2end2/

关于作者
Photo of Senthil Nathan

Senthil Nathan 是位于纽约 Hawthorne 的 IBM T.J. Watson Research Center 的一位高级软件工程师。在为不同类型的企业应用程序构建软件方面,他有 22 年经验。他当前感兴趣的领域包括 SOA、Web 服务、Java 2 Platform, Enterprise Edition(J2EE)、PHP、Ruby On Rails、Web 2.0 和 Ajax 开发。

原创粉丝点击