使用 Dojo 和 DB2 开发 Ajax Web 应用程序


跟随本案例分析,使用 IBM? DB2? 9 for Linux?, UNIX?, and Windows?、XQuery、PHP 和 Dojo JavaScript Framework 快速创建一个优雅的、能够做出响应的 Web 应用程序。本案例学习建立在 IOD Planner 2006 基础上,这是一个用于 IBM Information On Demand 2006 Global Conference 的 Web 应用程序。学习 Ajax 方法的优点并了解如何逐步实现这种应用程序。

  开始之前

  关于本教程

  本教程的目的有二 —— 展示使用 Ajax/Web 2.0 方法进行 Web 开发的优点,以及展示 DB2 9 pureXML? 的强大功能。根据这两个目的,我结合 Information On Demand 2006 Global Conference 编写了 Information On Demand 2006 Scheduler 应用程序。这是一个简单的可用来规划会议日程的应用程序。它利用了 Ajax —— Asynchronous JavaScript and XML —— 方法来进行 Web 编程,从而实现直观简单的用户交互。它还利用了 IBM DB2 9 的快速 pureXML 功能。

  图 1 演示了 IOD Planner 应用程序的开发。在后台进行了全部的加载以避免刷新延迟,并对表单字段、对话框和下拉菜单使用了丰富的部件。

  图 1. IOD Planner 2006

  

  先决条件

  您应该对 Web 开发和数据库有适当的了解。教程 “DB2 XML evaluation guide”(developerWorks,2006 年 6 月)对 DB2 9 进行了很好的介绍,而 “Query DB2 XML Data with XQuery”(developerWorks,2006 年 4 月)则介绍了 XQuery。

  系统需求

  要部署源代码,需要 Windows 或 Linux 操作系统。在其上,需安装 Zend Core for IBM(参见 参考资料 中的下载链接)。Zend Core 很方便地捆绑了 DB2 9 Express-C 数据服务器、Apache2 Web 服务器、PHP 脚本语言、DB2 的 PHP 扩展和一个功能强大的管理控制台。

  为什么使用 Ajax?

  图 2. 传统的 Web 模型和 Ajax 模型

  

  Ajax 基本原理的关键原则是避免加载新的 Web 页面。相反,可以在后台加载新的内容并在现有页面中显示它。这样就避免了重新加载基本框架的浪费,而且也不需要花时间等待浏览器呈现相同的导航和设计。您的用户不会看到加载中的空页面。最终的结果是一个迅速的、响应性很好的应用程序。

  使用 XMLHTTPRequest 对象向服务器发送请求,并在响应到达时对其进行异步处理。和其名字相反,该对象并不只限制于 XML。任何空白的文本格式,从 JavaScript (JSON) 到 CSV 再到预先生成的 HTML,都可以使用这种机制接收。我们选择后者 —— 在服务器中生成的 HTML。

  Ajax 风格的 Web 应用程序主要包括 Bloglines、Del.icio.us、Digg、Flickr、Google Map、Gmail 和 Reddit。

  为什么使用 Dojo JavaScript Framework?

  在过去两年里,作为一种语言和平台,JavaScript 获得了很大程度的发展。这种语言没有正式的库、框架或 API,所以出现了一些业内支持的开源的替代选择。它们提供了方便的 Aax、丰富的小部件、数据结构、辅助函数、效果和布局帮助。Dojo 是其中一种工具箱。

  Dojo 是一个开源的 JavaScript 框架,IBM、Sun、AOL、JotSpot 等都对其提供支持。Ajax for IBM WebSphere? 使用 Dojo 0.4.1。Dojo 对任何服务器端工具箱都没有限制 —— 这意味着它可与 PHP、Java、servlets、.NET 或其他任何后端结合使用。Dojo 有一个活跃的用户和开发人员社区,在 dojo 的邮件列表里面有大量的日常消息。

  Dojo 具有模块化设计,根据需要可以加载额外的功能。

  图 3. JavaScript 框架采用(Ajaxian.com, 2006)

  

  图 3 所示的竞争者份额中,Dojo 是一块新的领域,并正在迅速壮大。在过去一年里,Dojo 和 Script.aculo.us 都获得了巨大市场份额增长。其中,Dojo 具有更健壮的架构和更好的业内支持。

  为什么使用 DB2 9 pureXML?

  需要使用一种关系数据库作为 Web 应用程序的后端。我们选择使用 DB2 Express-C,它是 DB2 9 数据服务器的一个可以免费下载、免费部署、免费发布的版本。它还具有原生 XML 功能,这种功能需要进行细化。

  图 4. 组合 XML 和关系数据库的传统方法

  

  任何足够大的企业应用程序都希望使用 XML。通常,XML 是表示数据的最自然的形式。这意味着 XML 可以存储在文件中,或者大量存储在数据库 CLOB 中,或者解析为分离的列。

  但是,在关系数据库中混合分离的 XML 文件将很难进行扩展;CLOB 中 XML 很难进行查询,而且速度缓慢;将 XML 解析为关系列意味着关系模式必须与 XML 模式同步 —— 即每一次对 XML 结构做出更改将触发对整个数据库的回归测试,这种代价是非常昂贵的。

  图 5. DB2 中的原生 XML

  

  DB2 9 解决了这种问题。使用 SQL 和 W3C 标准的 XQuery 对其原生 XML 数据类型进行查询非常简单。它可随数据库进行扩展,并且特定于 XML 的优化使得它具有比 CLOB 更好的性能。最后,它将 XML 和关系模式分离 —— 即当只改变 XML 结构时不需要对所有内容进行回归测试。

  为何选择 XQuery?

  图 6. XQuery 和 DB2

  

  XQuery 是从 XML 文件中查询数据的 W3C 标准。XQuery 中的 WHERE 子句与 SQL 类似,FOR 循环对于初级的程序员来说比较直观。XQuery 代码与其等价的 XSL 相比,编写起来更加快捷,更易于阅读,并且通常更友好。

  FLWOR(“flower”)经常会在 XQuery 上下文中提到。它指的是 for、LET、ORDER BY、WHERE 和 RETURN 语句,这些语句是 XQuery 的基础。

  为何选择 PHP 和 XSL?

  PHP 是一种流行的并易于掌握的服务器端脚本语言。与 Java servlets 或 .NET 相比较,它具有较低的学习曲线,但同样为资深专业人员提供了功能强大的高级特性。作为一种解释性的脚本语言,它非常适用于重复开发 —— 不需要在做出个别更改后重新编译,并且能够实时看到更改代码后的效果。

  无需多言,您应该将开发、测试和生产环境分离。不推荐进行即时编辑。

  我们之所以选择 XSL,是因为其强大的转换功能。用过之后发现,它其实只依赖于 XQuery 。它的工具也非常优秀,代码变得更加简短也更加优雅。XQuery 是一种更加友好的、无任何副作用的语言,专门用于将一种 XML 结构转换为另一种。我们已经使用 XQuery 生成了格式良好的 XML,所以可以完全避免进行第二次转换。

  Ajax 和 Dojo JavaScript 工具箱

  这一节讨论了发生所有 Ajax 操作的客户端。JavaScript 在后台与服务器建立联系,来回发送数据并管理其表现形式。

  中途,您可能会希望跳过这些极其详尽的细节,转到有关服务器端的那一小节,该节处理的是数据库问题。

  图 7. 客户机-服务器端的 Ajax

  

  我们可以将 Dojo 用于对话框、更好的表单部件、下拉菜单、突出显示的淡入淡出效果以及 XMLHTTPRequest。我们来研究一下对话框。

  Figure 8. 一个简单的对话框

  

  使用 JavaScript 初始化请求

  清单 1. 生成 HTTP 请求

// my load event handler
function myLoadFunction(type, data, evt) {
  ...
}
// configure the request
var args = {
 url: "search.php",
 formNode: "searchFormId",
 load: myLoadFunction
};
// send to server
var request = dojo.io.bind(args);

  myLoadFunction() 将成为事件处理器。可以通过在 args.load 中给出它的名称(无需引号)而指定它。这些代码还说明了我们希望将 “searchFormId” 表单的内容发送到 “search.php” 页面。

  将这些参数和请求绑定起来,并将 myLoadFunction() 有效地绑定到加载事件。这些操作将在后台进行异步处理。只要服务器发回内容,就会调用 myLoadFunction(),而发回的内容就在这个函数的第二个参数 data 内。

  选项

  如果没有 Dojo 或与之相似的框架,您必须亲自编写大量的代码。这需要考虑到 HTTP 问题。Internet Explorer 和多数浏览器的内部实现不同,所以需要付出大量的努力,以实现跨浏览器的兼容性。编写、调试和维护这样的代码也非常费心。

  只有在成功的情况下 —— HTTP 200,才会调用 Dojo 加载函数。对于除此之外的所有其他内容,需要创建一个错误处理函数,并为它分配错误参数。这将让您可以轻松地编写复杂的缓存和重发机制。

  该请求的可能参数包括:

  url

  method - GET 或 POST

  mimetype - text/plain、text/html 等

  GET 和 POST 是 HTTP 中最基本的、广泛实现的两个部分。 GET 通过 URL 发送参数,而 POST 通过 HTTP 消息发送参数。 Internet Explorer 将 URL 限制为 2000 字符,所以大型表单和消息需要使用 POST。

  mimetype 参数用来确定 Dojo 发送到加载处理器的数据的形式。如果将它设置为 text/XML,将获得一个 DOM 对象。如果将它设置为 text/plain,将获得一个字符串。其他的文件类型则会直观地加以处理。

  要向服务器发送数据,可以指定下列参数中的任意一个:

  formNode - HTML 表单的 id

  content - 具有键值对的 JavaScript 对象/散列(JSON)

  postContent - 原始的 name=value&name=value 字符串

  可以指定这些事件处理器:

  load - 一种函数

  error - 一种函数

  timeout - 一种函数

  这些附加的参数提供了管理 back 按钮行为的方法:

  changeURL - 布尔

  backButton - 一种函数

  forwardButton - 一种函数

  接下来的一节将深入讨论技术细节。您也许希望跳过这些内容进入下一节。

  启用 back 按钮

  过去,Ajax 应用程序在 back 和 forward 按钮上存在问题。浏览器将忽视 XMLHTTPRequest,所以当用户尝试后退时,将得到预期之外的结果。Dojo 使用事件处理器巧妙地解决了这一问题。

  Dojo 注意到页面内的链接/定位点 —— 附加在 URL 上的 “#gohere” 字符串 —— 同样会影响浏览器的历史。只要初始化 XMLHTTPRequest,Dojo 就会在当前页面 URL 上附加任意的定位点。然后观察历史堆栈,当其发生变化时就触发 backButton 或 forwardButton 事件处理器。

  要启用这种功能,需要向请求参数添加三项内容:

  清单 2. 复杂请求

// prepare the request
var requestArgs = {
 url:   url,
 mimetype: "text/html",
 method:  "post",
 formNode: 'searchFormId',
 load: myLoadFunction,
 
 changeURL:   true, // enable back/forward buttons
 backButton:  myBackFunction,
 forwardButton: myForwardFunction
};

  通过设置 changeURL 标志,将 Dojo 设置为向较大的页面附加一个惟一的 #gohere。它随后会观察历史记录,一旦用户返回到一个早期的页面内定位点,它就会调用 backButton 或 forwardButton 处理函数。

  但是难道不需要编写这些函数吗?确实需要。所幸的是,这种逻辑很简单。

  用户加载一个新的页面:

  将当前内容缓存到 back 堆栈

  清除 forward 堆栈

  显示接收的数据

  用户后退:

  将当前内容缓存到 forward 堆栈

  从 back 堆栈取出先前的内容

  显示取出的数据

  用户前进:

  将当前内容缓存到 back 堆栈

  从 forward 堆栈取出数据

  显示取出的数据

  Dojo 提供了一个现成的 dojo.collections.Stack 对象,不需要再牢记抽象的数据结构理论:

  清单 3. 堆栈示例

var backStack = new dojo.collections.Stack();
var forwardStack = new dojo.collections.Stack();
function myLoadFunction(t, data, e) {
 var current = ...
 backStack.push(current);
  
 forwardStack.clear();
  
 myDisplayFunction(data);
}
function myBackFunction() {
 var current = ...
 forwardStack.push(current);
 
 var previous = backStack.pop();
 
 myDisplayFunction(previous);
}
function myForwardFunction() {
 var current = ...
 backStack.push(current);
 
 var next = forwardStack.pop();
 
 myDisplayFunction(next);
}

  JavaScript 中的散列和函数

  清单 4. 不同的表达方式

// One way
var args = {
 url: "search.php",
 formNode: 'searchFormId',
 load: myLoadFunction
};
// Another way
var args = {};
args['url'] = "search.php";
args['formNode'] = "searchFormId",
args['load'] = myLoadFunction;
// Yet another way
var args = {};
args.url = "search.php";
args.formNode = 'searchFormId',
args.load = myLoadFunction;

  按照技术解释,JavaScript 对象的使用方法,与 Java 中的联合数组、Perl 中的散列以及 Python 中的字典相同。每一个对象的变量实际上是一个键。可以通过 args['url'] 或 args.url 语法访问它们。

  同样,函数是 JavaScript 中的一类对象。可以为它们分配并传递任何其他变量。比如,var byId = document.getElementById; 使您能够通过简单的 byId(); 调用后者。

  在 JavaScript 中处理响应

  清单 5. Ajax 加载事件处理器

// my load event handler
function myLoadFunction(type, data, evt) {
  // get the main display section
  var main = document.getElementById(DISPLAY_SECTION);
  
  // cache current in back stack
  if (main) backStack.push(main.innerHTML);
  // clear forward stack
  forwardStack.clear();
  
  // display received data
  if (main) main.innerHTML = data;
}

  收到的内容是简单的 text/html。显示它的最简单方法就是通过 innerHTML 属性。还有很多更复杂的方法。

  如果 mimetype 在请求中被设置为 text/xml,data 就应该是 DOM 对象而非字符串。Dojo 透明地对此进行处理。

  DB2 9 pureXML

  这一节讨论服务器端内容,在这里将发生所有的 SQL、XQuery 和 PHP 操作。创建一个数据库,使用不同方法查询,最后使用 PHP 将所有方法绑定起来。

  图 9. 客户机-服务器透视图

  

  创建数据库

  Activities 存在于 Tracks,而 Tracks 又由 Program 组织。您希望 Activities 表列出所有的活动。Schedule 表用来记录用户当前预定的 Activities。用户可以粗略记下参加了的活动。这些记录也将放入到 Schedule 表中。

  图 10. 理想的数据库模式

  

  要创建数据库,运行 data/setup.bat 脚本。该文件中惟一的命令是 db2cmd /c /w /i "db2 -tf setup.sql"。让我们看一下 setup.sql 做了什么:

  清单 6. 数据库创建

CREATE DATABASE infodema USING CODESET utf-8 TERRITORY ca;
CONNECT TO infodema USER student USING password;
SET SCHEMA=iod;

  要使 DB2 数据库支持 XML,需使用 UTF-8 代码集创建此 DB2 数据库。这通常都是一个很好的实践,因为在启用简单的国际化时不会对数据库的规模造成影响。

  清单 7. 表创建

CREATE TABLE activities
(
 activityNumber char(10) NOT NULL PRIMARY KEY,
 track varchar(60) REFERENCES tracks,
 activity XML
);

  activity 是一个 XML 列。与 CLOB 不同,XML 列针对高性能查询和搜索进行了优化。推荐将 XML 数据始终存储在 XML 列中,而不要把它解析或放置到 CLOB 中。

  填充数据库

  清单 8. 表填充

IMPORT FROM 'iod_import/iod_import.del'
OF del
XML FROM 'iod_import'
INSERT INTO activities;

  清单 9. 逗号分隔的数据文件:摘录

"BOD-1110A","Create the On Demand Enterprise",<XDS FIL='activity0.xml' />
"BOD-1247B","Create the On Demand Enterprise",<XDS FIL='activity1.xml' />

  iod_import.del 是一个使用逗号分隔的文件,我们将使用它来填充表。它指定了 char(10) activityNumber 的值、varchar(60) track 和对 XML 文件的引用。如果您具有一个希望导入到 DB2 中的 XML 文件的目录,利用 IMPORT FROM 语句将是一个可行的解决方法。

  使用 SQL 对 XML 列进行检索

  现在让我们看一下使用 SQL 和 XQuery 从数据库中检索 XML 数据。

  清单 10. SQL 查询

SELECT XMLSERIALIZE(activity AS CLOB)
FROM activities
WHERE activityNumber = 'TDA-2737A'

  activity 是一个 XML 列。其中的所有数据都是格式良好的 XML。我们将它视作简单的字符串处理,所以使用 XMLSERIALIZE() 转换它。这个查询只能在一条记录中检索一列 —— TDA-2737A 的活动数据。

  使用 XQuery 检索 XML

  图 11 显示了 Activity 列中的示例 XML 数据。请注意根元素是 <iod>,它的一个子元素是 <sessions>,后者包含两个 <session> 元素。

  图 11. Activities 表中 Activity 列的示例 XML

  

  清单 11. XQuery 代码

values(XMLSERIALIZE(
XMLQUERY('
<iod_activities>
{
  for $root in db2-fn:xmlcolumn("ACTIVITIES.ACTIVITY")/iod
    where matches($root, "ajax", "i")
    order by
      $root/sessions/session[1],
      $root/title
      
  return $root
}
</iod_activities>
' RETURNING SEQUENCE) AS CLOB))

  这个查询遍历 activities 表的每一行。对于每一个匹配的 activity,将创建一个主 XML 文档,将 iod_activities 作为根元素。

  要在 PHP 中使用 XQuery,需要将其封装在 XMLQUERY() SQL 函数。

  任何非语句的内容将作为静态字符串处理,返回给用户。例如 <iod_activities> 和 </iod_activities>。将括弧(“{” 和 “}”)嵌入到字符串中,强制 XQuery 处理语句。

  activity 列包含具有根元素 <iod> 的 XML 文档。对于每一行,将根文档放在 $root 变量中。

  这仅限于元素包含字符串 "ajax" 的那些地方。通过指定 "i" 标志,可以使匹配不区分大小写。

  $root/sessions 是 $root 中元素的 <sessions> 子元素的 XPath。$root/sessions/session[1] 引用 <sessions> 的第一个 <session> 子元素。根据后面元素的内容进行排序,然后再根据 IOD 的 <title> 子元素内容排序。

  使用 SQL 将两个表连接为一个 XML 文件

  清单 12. SQL 中专门的 XML 函数

SELECT xmlserialize
  (
    xmlelement
    (
      name "activity",
      xmlforest
      (
        session as "session",
        activity as "iod",
        note as "note"
      )
    )
  AS clob)
FROM schedule s, activities a
WHERE s.activityNumber = a.activityNumber
ORDER BY s.session

  您希望获得一个包含所有预定活动的 XML 文档。这个 SQL 将生成一个结果集,其中每个活动都具有一个单独的 XML 字符串,可以在 PHP 中将其编译为一个主文档。

  xmlserialize() 将优化了的 XML 转换为一个 CLOB 字符串。 xmlelement() 声明了一个 XML 包装器元素及其内容。xmlforest() 将列中的内容放入了连续的 XML 元素中。

  在 PHP 中查询 DB2

  Zend Core for IBM 绑定了 DB2 对 PHP 的扩展。这使得能够在代码的任何地方都可以使用数据库访问函数。

  清单 13. 一条简单的语句

// connect to the database
$conn = db2_connect("database", "student", "passw0rd");
// define the statement
$stmtString = <<<TEXT
 SELECT activityNumber
 FROM schedule
 WHERE session = ?
TEXT;
$stmt = db2_prepare($conn, $stmtString);
// query the database, setting each ? token
$result = db2_execute($schemaStmt, array($sessionNumber));
// fetch results
while ($row = db2_fetch_array($result)) {
 ...
}
// close the connection
db2_close($conn);

  db2_connect() 返回连接对象,db2_prepare() 返回编译了的语句,db2_execute() 使用参数运行该语句。

  ? 在编译过的语句中表示参数。建议您始终编译语句,而不是使用 db2_exec() 运行原始的 SQL。使用后一种方法的 SQL 容易发生错误并受到注入攻击。

  清单 14. 带错误检查的语句

// connect to the database
$conn = db2_connect("database", "student", "passw0rd");
if (!$conn) {
 throw new ErrorException(db2_conn_errormsg());
}
// define the statement
$stmtString = <<<TEXT
 SELECT activityNumber
 FROM schedule
 WHERE session = ?
TEXT;
$stmt = db2_prepare($conn, $stmtString);
// query the database, setting each ? token
$result = db2_execute($schemaStmt, array($sessionNumber));
if (!$result)
{
 throw new ErrorException(db2_stmt_errormsg());
}
// fetch results
if ($row = db2_fetch_array($result))
{
 ...
}
// close the connection
db2_close($conn);

  始终检查错误是非常好的习惯。 db2_conn_errormsg() 和 db2_stmt_errormsg() 给出了相关的错误消息。

  示例应用程序

  IOD 2006 Personal Planner 是一款所有会议参与者都可以使用的优秀工具,使用 IBM 最新的 pureXML 数据服务器演示强大的 Web 应用程序技术。Planner 使用 Ajax 提供了丰富的体验,使终端用户能够完全访问会议中的所有会话。

  可以将 planner 部署到您的笔记本电脑,从而更加方便和灵活,它可以执行以下功能:

  搜索会议细节

  构建会议的个人备忘录(显示、打印或者两种皆有)

  为每次会话做好记录,以简化办公用的出差报告

  第 1 步:安装 Zend Core for IBM

  Zend Core 是 PHP Web 应用程序的集成开发和部署环境。它包括 DB2 Express-C、一个集成的 Apache HTTP Server、DB2 和 Informix 客户机、有用的 PHP 语言扩展以及一个易于使用的、基于 Web 的管理控制台。可以从 Zend 免费下载(参见 参考资料):

  选择不安装 Cloudscape。

  设置并记住管理控制台密码。

  选择 No 跳过 Zend Network Registration/Support 屏幕。

  第 2 步:启用 PHP 的 XSL 扩展

  启动 Zend Core Administration Console(从开始程序启动)。

  输入安装过程中使用的管理控制台密码。

  选择 Configuration > Extensions,然后下拉到 xsl。

  单击右边的红色图标启用扩展(图标颜色由红变绿)

  保存设置。

  如果出现问题,则将 “extension=php_xsl.dll” 添加到 php.ini。

  运行 C:Program FilesZendApache2binApacheMonitor.exe 将 Apache 放入系统托盘。

  从 Windows 系统托盘中的 Apache 图标重新启动 Apache。

  第 3 步:为 ZendCon Planner 创建新的 Windows 用户

  转到 Control Panel > User Accounts。

  创建名为 “STUDENT” 的帐户。

  在列表中单击它,并将密码修改为 “passw0rd”。

  第 4 步:创建 DB2 数据库

  启动 DB2 Control Center。

  创建一个标准的新数据库。

  将其命名为 “ZENDCON”。

  为 XML 启用它。

  从 ZendConPlanner2006.zip 解压缩 data_image 文件夹。

  右键单击 ZENDCON 数据库,从上下文菜单中选择 Restore。

  打开 data_image 文件夹。

  将日期和时间设置为 2006 年 10 月 27 日 10:49:45。

  选择复选框将数据库设置为静态。

  第 5 步:部署 ZendCon Personal Planner

  将 ZendConPlanner2006.zip 解压缩到 C:Program FilesZendApachehtdocs。

  在 Web 浏览器中打开 http://localhost/php/index.php。

  如果使用的是定制端口,则打开 http://localhost:##/php/index.php。

  下载

  

描述 名字 大小 下载方法
Personal Planner 的代码和数据库文件 IODPlanner2006.zip 4.25MB HTTP

本文作者:
« 
» 
快速导航

Copyright © 2016 phpStudy | 豫ICP备2021030365号-3