// 
//     Licensed to the Apache Software Foundation (ASF) under one
//     or more contributor license agreements.  See the NOTICE file
//     distributed with this work for additional information
//     regarding copyright ownership.  The ASF licenses this file
//     to you under the Apache License, Version 2.0 (the
//     "License"); you may not use this file except in compliance
//     with the License.  You may obtain a copy of the License at
// 
//       http://www.apache.org/licenses/LICENSE-2.0
// 
//     Unless required by applicable law or agreed to in writing,
//     software distributed under the License is distributed on an
//     "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
//     KIND, either express or implied.  See the License for the
//     specific language governing permissions and limitations
//     under the License.
//

= 用于 PHP Web 应用程序的 Ajax 简介
:jbake-type: tutorial
:jbake-tags: tutorials 
:jbake-status: published
:icons: font
:syntax: true
:source-highlighter: pygments
:toc: left
:toc-title:
:description: 用于 PHP Web 应用程序的 Ajax 简介 - Apache NetBeans
:keywords: Apache NetBeans, Tutorials, 用于 PHP Web 应用程序的 Ajax 简介

本文档介绍了 Ajax，并展示了 NetBeans IDE 中的一些功能，这些功能使您在使用 Ajax 相关技术编程时更加快捷高效。在学习 Ajax 初级功能时，您将构建一个用于自动完成文本字段的简单应用程序。此处的内容改编自 Greg Murray 的文章，样例应用程序取自link:http://weblogs.java.net/blog/gmurray71/archive/2005/12/using_ajax_with_1.html[+将 Ajax 与 Java 技术一起使用+]。

Ajax 代表异步 JavaScript 和 XML。在本质上，Ajax 是一种 Web 应用程序用来处理用户与 Web 页间交互的有效方法，它不需要在每次用户交互时都进行页面刷新或重新加载完整页面。这样就能在使用浏览器时提供丰富的行为（与桌面应用程序或基于插件的 Web 应用程序的行为类似）。Ajax 交互在后台异步处理。同时，用户可以继续使用页面。Ajax 交互由 JavaScript 代码启动。Ajax 交互完成后，JavaScript 会更新页面的 HTML 源代码。这些更改会立即进行，不需要刷新页面。Ajax 交互可用于执行一些操作，如使用服务器端逻辑验证窗体条目（当用户输入窗体条目时）、检索服务器中的详细数据、动态更新页面上的数据，以及从页面提交部分窗体。


image::images/netbeans-stamp-80-74-73.png[title="此页上的内容适用于 NetBeans IDE 7.2、7.3、7.4 和 8.0"]


*要学完本教程，您需要具备以下软件和资源。*

|===
|软件或资源 |要求的版本 

|link:https://netbeans.org/downloads/index.html[+NetBeans IDE，PHP 包+] |7.2, 7.3, 7.4, 8.0 

|link:http://www.oracle.com/technetwork/java/javase/downloads/index.html[+Java 开发工具包 (JDK)+] |7 或 8 

|link:http://www.php.net/downloads.php[+PHP 引擎+] |PHP 5 

|link:http://httpd.apache.org/download.cgi[+Apache Web 服务器+] |2.2 
|===

[NOTE]
====
* PHP 开发环境通常使用 *AMP 包进行配置，具体取决于您的操作系统。该环境包括 PHP 引擎和 Apache Web 服务器。有关配置环境的说明，请参见 link:../../trails/php.html[+PHP 学习资源+]。
* 本教程假设您已经具备它所使用的各种技术（即 HTML、CSS、JavaScript 和 PHP）的应用知识。它尝试以代码的形式提供功能的概述，但_不_解释每行代码的含义。
* 如果需要将项目与工作解决方案进行比较，可以link:https://netbeans.org/projects/samples/downloads/download/Samples%252FPHP%252FMyAjaxApp.zip[+下载样例应用程序+]。使用 IDE 的新建项目向导（Ctrl-Shift-N 组合键；在 Mac 上为 ⌘-Shift-N 组合键），并选择 "PHP with Existing Sources"（基于现有源代码的 PHP 应用程序）项目类型。在该向导中，指向计算机上已下载的源文件。
====



[[overview]]
== 应用程序概述

设想一个用户可以在其中搜索作曲家信息的 Web 页。该页面包含一个字段，用户可以在其中输入作曲家的名字。在示例应用程序中，输入字段具有自动完成功能。换句话说，用户可以键入作曲家的部分名字，然后 Web 应用程序即可以列出其名字或姓氏以所输入字符开头的所有作曲家，尝试完成整个名字。自动完成功能使用户不必记住作曲家的完整名字，并且可以更加直观和直接的搜索热门信息。

image::images/sample-app.png[title="浏览器中显示的样例应用程序"]

在搜索字段中实现自动完成功能可以使用 Ajax 来完成。Ajax 的工作原理是使用 `XMLHttpRequest` 对象在客户端和服务器之间异步传递请求和响应。下图说明了客户端和服务器之间进行通信的过程流。

image::images/ajax-process-flow.png[title="Ajax 过程流图"]


该图中的过程流可以按以下步骤进行解释：

1. 用户触发一个事件，例如，在键入姓名时释放一个按键。这会引发调用初始化 `XMLHttpRequest` 对象的 JavaScript 函数。
2. `XMLHttpRequest` 对象将使用一个请求参数进行配置，该参数将包含触发该事件的组件 ID 以及用户所输入的任何值。然后，`XMLHttpRequest` 对象将向 Web 服务器发出异步请求。
3. 在 Web 服务器上，诸如 servlet 或监听程序之类的对象会处理该请求。从数据存储检索数据，并且准备一个响应，其中包含 XML 文档形式的数据。
4. 最后，`XMLHttpRequest` 对象使用回调函数接收 XML 数据并对其进行处理，然后更新 HTML DOM（文档对象模型）以显示包含新数据的页面。

本教程说明了如何根据上图所示的过程流构建自动完成方案。首先，为生成 `XMLHttpRequest` 对象所需的表示和功能创建客户端文件。然后，通过使用基于 PHP 的技术创建数据存储和业务逻辑来设置服务器端。最后，返回客户端，并实现 `callback()` 和其他 JavaScript 功能以更新 HTML DOM。



[[client1]]
== 客户端编程：第 1 部分

首先，在 IDE 中创建一个新的 PHP 应用程序项目。

1. 选择 "File"（文件）> "New Project"（新建项目）。在 "Categories"（类别）下，选择 *PHP*。在 "Projects"（项目）下，选择 *PHP Application*（PHP 应用程序），然后单击 *Next*（下一步）。
2. 在“步骤 2：Name and Location（名称和位置）”中，将项目命名为 `MyAjaxApp`。使用 "Sources Folder"（源文件夹）字段可以在计算机上指定项目的位置。将其他选项保留为默认设置，然后单击 *Next*（下一步）。 

image::images/php-name-location.png[title="新建 PHP 项目向导 - "Name and Location"（名称和位置）面板"]



. 在步骤 3 "Run Configuration"（运行配置）中，指定您要部署应用程序的方式。如果您已通过配置 *AMP 包设置了 PHP 开发环境，则应该从下拉列表中选择 *Local Web Site*（本地 Web 站点），并指定项目的 URL（将显示在浏览器中）。


. 选中 "Copy files from Sources Folder to another location"（将源文件夹中的文件复制到其他位置）选项。然后，在 "Copy to Folder "（复制到文件夹）字段中，键入到服务器上部署位置的路径。（在 Apache 上，部署位置是默认的 `htdocs` 目录。） 

[.feature]
--
image::images/php-run-config.png[role="left", link="images/php-run-config.png"]
--



. 单击 *Finish*（完成）。IDE 在您的文件系统中创建项目文件夹，项目在 IDE 中打开。 

您也可以使用项目向导  将框架支持添加到项目中（可在向导的步骤 4 中执行该操作）。

生成默认的 `index.php` 索引页并在 IDE 的编辑器中将其打开。您的项目还会显示在 "Projects"（项目）窗口中。

image::images/php-proj-win.png[title=""Projects"（项目）窗口将显示 MyAjaxApp 项目"]



. 在开始编码前，请立即尝试运行该应用程序以确保正确设置了 IDE、您的服务器和浏览器之间的配置。

在 IDE 的编辑器中，向索引页中添加一个 `echo` 语句：

[source,php]
----

<?php
    // put your code here
    *echo "<h2>Hello World!</h2>";*
?>

----


. 在 "Projects"（项目）窗口中，右键单击项目节点，然后选择 "Run"（运行）。IDE 会打开您的默认浏览器，并显示您刚在 `index.php` 中创建的 Hello World 消息。 

NOTE: 如果您在设置项目或在 IDE、服务器和浏览器之间建立通信时遇到困难，请参见link:project-setup.html[+设置 PHP 项目+]以获取更详细的说明。link:../../trails/php.html[+PHP 学习资源+]可以提供有关配置环境的更多信息。


[[html]]
=== 使用 HTML 编辑器

image::images/palette.png[title="显示 HTML 元素的 "Palette"（组件面板）"] 

现在，您已确定正确设置了环境，接下来首先开发由用户查看的自动完成界面。由于我们将创建的索引页不需要任何服务器端脚本元素，因此将先创建一个 HTML 页，并将其设置为应用程序的入口点。

使用 IDE 的一个好处是：您所使用的编辑器通常可以为您提供代码完成功能，如果在编写代码时学会应用此功能，可以快速提高效率。一般来说，IDE 编辑器会适应您所使用的技术，因此，如果您正在处理 HTML 页，按下代码完成组合键（Ctrl-空格键）会产生有关 HTML 标记和属性的建议。下面您还将了解到，IDE 编辑器也适用其他技术（如 CSS 和 JavaScript）。

您可以使用的第二个功能是 IDE 的 "Palette"（组件面板）。"Palette"（组件面板）为您编写代码所采用的技术中的常用元素提供了易于使用的模板。您只需单击某一项，然后将其拖至源代码编辑器所打开的文件中的某个位置。

您可以查看大图标（如此处显示），方法是右键单击组件面板，然后选择 "Show Big Icons"（显示大图标）。


1. 在 "Projects"（项目）窗口中，右键单击 `MyAjaxApp` 项目节点，然后选择 "New"（新建）> "HTML File"（HTML 文件）。
2. 在 HTML 文件向导中，将文件命名为 `index`，然后单击 *Finish*（完成）。新的 `index.html` 文件在编辑器中打开。
3. 按照下面所示，替换该文件的现有内容。

[source,xml]
----

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
    "http://www.w3.org/TR/html4/loose.dtd">

<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <title>Auto-Completion using AJAX</title>
    </head>
    <body>
        <h1>Auto-Completion using AJAX</h1>
    </body>
</html>

----


. 添加一些说明性文本以介绍文本字段的用途。您可以复制以下文本并将其粘贴在 `<h1>` 标记下方的某一位置：

[source,html]
----

<p>This example shows how you can do real time auto-completion using Asynchronous
    JavaScript and XML (Ajax) interactions.</p>

<p>In the form below enter a name. Possible names that will be completed are displayed
    below the form. For example, try typing in "Bach," "Mozart," or "Stravinsky,"
    then click on one of the selections to see composer details.</p>

----


. 向该页面中添加一个 HTML 窗体。可以利用 IDE "Palette"（组件面板）中列出的元素执行此操作。如果组件面板没有打开，请从主菜单中选择 "Window"（窗口）> "Palette"（组件面板）。然后，在 "HTML Forms"（HTML 窗体）下，单击某个窗体元素，并将其拖至该页面中您刚添加的 `<p>` 标记下的某一位置。此时将打开 "Insert Form"（插入窗体）对话框。指定以下内容： 

* "Action"（操作）：autocomplete.php
* "Method"（方法）：GET
* "Name"（名称）：autofillform

image::images/php-insert-form.png[title=""Insert form"（插入窗体）对话框"]

单击 "OK"（确定）。HTML `<form>` 标记已插入到包含您所指定的属性的页面中。（GET 在默认情况下应用，因此没有进行显式声明。）



. 向该页面添加一个 HTML 表格。在 "Palette"（组件面板）中的 "HTML" 类别下，单击某个表格元素，并将其拖至 `<form>` 标记之间的位置。"Insert Table"（插入表格）对话框即打开。指定以下内容： 

* "Rows"（行）：2
* "Columns"（列）：2
* "Border Size"（边框大小）：0
* "Width"（宽度）：0
* "Cell Spacing"（单元格间距）：0
* "Cell Padding"（单元格边距）：5

image::images/insert-table.png[title=""Insert table"（插入表格）对话框"]



. 在源代码编辑器中单击鼠标右键，然后选择 "Format"（格式化代码）。此操作将对代码进行整理。现在，您的窗体应该如下显示：

[source,xml]
----

<form name="autofillform" action="autocomplete.php">
  <table border="0" cellpadding="5">
    <thead>
      <tr>
        <th></th>
        <th></th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td></td>
        <td></td>
      </tr>
      <tr>
        <td></td>
        <td></td>
      </tr>
    </tbody>
  </table>
</form>

----


. 将以下文本键入到此表格第一行的第一列中（更改的内容以*粗体*显示）：

[source,xml]
----

<td>*<strong>Composer Name:</strong>*</td>
----


. 在第一行的第二列中，手动键入以下代码（而不从 "Palette"（组件面板）中拖动一个文本输入字段）。

[source,java]
----

<input type="text"
    size="40"
    id="complete-field"
    onkeyup="doCompletion();">

----
在键入时，请尝试使用 IDE 内置的代码完成支持。例如，键入 `<i`，然后按 Ctrl-空格组合键。建议的选项列表会显示在光标下方，并且选中元素的说明会显示在上方的框中。事实上，在源代码编辑器中编写代码时，可以随时按 Ctrl-空格键调出可能的选项。而且，如果只有一个可用的选项，按 Ctrl-空格键将自动完成元素名称。
image:images/code-completion.png[title="在源代码编辑器中按 Ctrl-空格键可以触发代码完成"] 
您在上文中键入的 `onkeyup` 属性指向名为 `doCompletion()` 的 JavaScript 函数。每次在窗体文本字段中按下一个键时，此函数都会被调用，并映射到以上 Ajax <<flow-diagram,流程图>>中所描述的 JavaScript 调用。


. 在转为使用 JavaScript 编辑器之前，请将应用程序的入口点由 `index.php` 文件替换为新的 `index.html` 文件。

要执行此操作，请右键单击 "Projects"（项目）窗口中的项目节点，然后选择 "Properties"（属性）。选择 *Run Configuration*（运行配置）类别，然后在 "Index File"（索引文件）字段中输入 `index.html`。image:images/php-entry-point.png[title="在 "Project Properties"（项目属性）窗口中指定应用程序的入口点"]


. 单击 "OK"（确定）以保存更改，然后退出 "Project Properties"（项目属性）窗口。


. 运行该项目以查看其在浏览器中的外观。单击 "Run Project"（运行项目）(image:images/run-project-btn.png[]) 按钮。在默认浏览器中显示 `index.html` 文件。 

image::images/index-page.png[title="运行项目以在浏览器中查看其当前状态"]


[[javascript]]
=== 使用 JavaScript 编辑器

IDE 的 JavaScript 编辑器提供了许多高级编辑功能，如智能代码完成、语义突出显示、即时重命名和重构功能等。有关 IDE 中的 JavaScript 编辑功能的更多信息，请参见《link:http://www.oracle.com/pls/topic/lookup?ctx=nb8000&id=NBDAG[+使用 NetBeans IDE 开发应用程序用户指南+]》中的link:http://docs.oracle.com/cd/E50453_01/doc.80/e50452/dev_html_apps.htm#BACFIFIG[+创建 JavaScript 文件+]。有关详细规范，请参见 link:http://wiki.netbeans.org/JavaScript[+http://wiki.netbeans.org/JavaScript+]。

当您使用其他技术（即 HTML、RHTML、JSP、PHP）在 `.js` 文件以及 `<script>` 标记中编码时，将自动提供 JavaScript 代码完成功能。当使用 JavaScript 编辑器时，IDE 会为您提供浏览器兼容性信息，具体取决于您在 JavaScript 选项面板中指定的浏览器类型和版本。打开 JavaScript 选项面板，方法是先选择 "Tools"（工具）> "Options"（选项）（在 Mac 上为 "NetBeans" > "Preferences"（首选项）），然后选择 "Miscellaneous"（其他）> "JavaScript"。

image::images/php-javascript-options.png[title=""JavaScript Options"（JavaScript 选项）面板"]

IDE 对 Firefox、Internet Explorer、Safari 和 Opera 提供了快捷支持。在 JavaScript 选项面板中，您还可以指定代码完成功能适用的 JavaScript 引擎版本。

将 JavaScript 文件添加到此应用程序，然后开始实现 `doCompletion()`。

1. 在 "Projects"（项目）窗口中，右键单击项目节点并选择 "New"（新建）> "JavaScript file"（JavaScript 文件）。（如果 "JavaScript file"（JavaScript 文件）未列出，请选择 "Other"（其他）。然后，从新建文件向导的 "Other"（其他）类别中选择 "JavaScript file"（JavaScript 文件）。）
2. 将文件命名为 `javascript`，然后单击 "Finish"（完成）。新的 JavaScript 文件会显示在 "Projects"（项目）窗口中，并在编辑器中打开。
3. 将以下代码键入 `javascript.js`。

[source,java]
----

var req;
var isIE;

function init() {
    completeField = document.getElementById("complete-field");
}

function doCompletion() {
        var url = "autocomplete.php?action=complete&amp;id=" + escape(completeField.value);
        req = initRequest();
        req.open("GET", url, true);
        req.onreadystatechange = callback;
        req.send(null);
}

function initRequest() {
    if (window.XMLHttpRequest) {
        if (navigator.userAgent.indexOf('MSIE') != -1) {
            isIE = true;
        }
        return new XMLHttpRequest();
    } else if (window.ActiveXObject) {
        isIE = true;
        return new ActiveXObject("Microsoft.XMLHTTP");
    }
}

----

以上代码将对 Firefox 3 以及 Internet Explorer 版本 6 和 7 执行简单的浏览器兼容性检查。如果希望包含更多强健的代码以处理兼容性问题，建议您使用 link:http://www.quirksmode.org[+http://www.quirksmode.org+] 中的link:http://www.quirksmode.org/js/detect.html[+浏览器检测脚本+]。



. 切换回 `index.html`，然后在 `<head>` 标记之间添加对 JavaScript 文件的引用。

[source,xml]
----

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

----

您可以按 Ctrl-Tab 组合键在编辑器中打开的页面之间快速切换。



. 在开始标记 `<body>` 中，插入对 `init()` 的调用。

[source,java]
----

<body *onload="init()"*>

----
这可以确保每次加载页面时，都会调用 `init()`。

`doCompletion()` 的作用是：

* 创建一个 URL，其中包含可由服务器端使用的数据，
* 初始化 `XMLHttpRequest` 对象，并
* 提示 `XMLHttpRequest` 对象向服务器发送一个异步请求。

`XMLHttpRequest` 对象是 Ajax 的核心，并已经成为通过 HTTP 异步传递 XML 数据的实际标准。_异步_交互意味着在发送请求之后浏览器可以继续处理页面中的事件。数据在后台进行传递，并且可以自动加载到页面中，无需进行页面刷新。

请注意，`XMLHttpRequest` 对象实际上是由 `initRequest()`（由 `doCompletion()` 调用）创建的。此函数用于检查浏览器是否可以识别 `XMLHttpRequest`；如果可以，即创建 `XMLHttpRequest` 对象。否则，它将对 `ActiveXObject`（相当于 Internet Explorer 6 的 `XMLHttpRequest`）执行检查，并创建 `ActiveXObject`（如果被识别）。

当您创建 `XMLHttpRequest` 对象时会指定三个参数：URL、HTTP 方法（`GET` 或 `POST`），以及此交互是否为异步交互。以上示例中的参数有：

* URL `autocomplete.php` 和用户输入 `complete-field` 中的文本：

[source,java]
----

var url = "autocomplete.php?action=complete&amp;id=" + escape(completeField.value);
----
* `GET`，表示 HTTP 交互使用 `GET` 方法，以及
* `true`，表示此交互是异步交互：

[source,java]
----

req.open("GET", url, true);
----

如果此交互设为异步交互，则必须指定回调函数。此交互的回调函数是使用以下语句进行设置的：


[source,java]
----

req.onreadystatechange = callback;
----

并且<<callback,稍后必须定义>> `callback()` 函数。HTTP 交互在调用 `XMLHttpRequest.send()` 时开始。在以上<<flow-diagram,流程图>>中，此操作映射到发送给 Web 服务器的 HTTP 请求。



[[serverside]]
== 服务器端编程

NetBeans IDE 对使用 PHP 进行 Web 开发提供了全面支持。您可以使用 *AMP 包设置您的开发环境，以便从 IDE 进行快速而有效地编辑和部署。通过 IDE，您可以在本地服务器配置环境，也可以使用 FTP 或 SFTP 远程配置环境。您还可以从 IDE 的 "PHP" 选项窗口中配置外部调试器（例如 link:http://xdebug.org/[+Xdebug+]）并设置通过 link:http://www.phpunit.de/[+PHPUnit+] 执行单元测试，通过选择 "Tools"（工具）> "Options"（选项）（在 Mac 上为 "NetBeans" > "Preferences"（首选项）），然后选择 "PHP" 标签，可以打开该窗口。PHP 编辑器提供了标准的编辑功能，如代码完成、语法突出显示、标记实例、重构、代码模板、文档弹出式窗口、代码导航、编辑器警告，以及 NetBeans 6.9 中为格式不正确的语法提供的错误标记。有关 PHP 支持的截屏视频，请参见 link:../intro-screencasts.html[+NetBeans 视频教程和演示+]页。

对于需要数据库的应用程序，IDE 提供了对大部分主流数据库（特别是 MySQL）的广泛支持。有关详细信息，请参见 link:../../articles/mysql.html[+NetBeans MySQL 截屏视频+]和link:../../../features/ide/database.html[+数据库集成+]功能。

您要为自动完成应用程序构建的业务逻辑需要处理请求，方法是检索数据存储中的数据，然后准备并发送响应。此操作在此处是使用名为 `autocomplete` 的 PHP 文件实现的。在您对文件进行编码之前，请设置数据存储和此文件访问数据所需的功能。

* <<data,创建数据存储>>
* <<business,创建业务逻辑>>


[[data]]
=== 创建数据存储

对于此简单的应用程序，您创建一个名为 `Composer` 的类，该类使业务逻辑可以从 `composers` 数组包含的条目中检索数据。然后，您创建一个名为 `ComposerData` 的类，该类使用数组保留作曲家数据。

1. 在 "Projects"（项目）窗口中，右键单击 `MyAjaxApp` 项目节点，然后选择 "New"（新建）> "PHP Class"（PHP 类）。
2. 将类命名为 `Composer`，然后单击 "Finish"（完成）。此时会创建该类，并在编辑器中将该类打开。
3. 在该类中粘贴以下代码（更改内容以*粗体*显示）。

[source,php]
----

<?php

class Composer {

    *public $id;
    public $firstName;
    public $lastName;
    public $category;

    function __construct($id, $firstName, $lastName, $category) {
        $this->id = $id;
        $this->firstName = $firstName;
        $this->lastName = $lastName;
        $this->category = $category;
    }*
}

?>
----

创建 `ComposerData` 类。

1. 在 "Projects"（项目）窗口中，右键单击 `MyAjaxApp` 项目节点，然后选择 "New"（新建）> "PHP Class"（PHP 类）。
2. 将类命名为 `ComposerData`，然后单击 "Finish"（完成）。此时会创建该类，并在 IDE 编辑器中将该类打开。
3. 在该类顶部添加 `require` 语句，以指定该类需要刚创建的 `Composer.php` 类（更改内容以*粗体*显示）。

[source,php]
----

<?php

*require "Composer.php";*

class ComposerData {

}
----


. 在编辑器中，将以下代码粘贴到该类（更改内容以*粗体*显示）。

[source,php]
----

<?php

require "Composer.php";

class ComposerData {

    *public $composers;

    function __construct() {
        $this->composers = array(
            new Composer("1", "Johann Sebastian", "Bach", "Baroque"),
            new Composer("2", "Arcangelo", "Corelli", "Baroque"),
            new Composer("3", "George Frideric", "Handel", "Baroque"),
            new Composer("4", "Henry", "Purcell", "Baroque"),
            new Composer("5", "Jean-Philippe", "Rameau", "Baroque"),
            new Composer("6", "Domenico", "Scarlatti", "Baroque"),
            new Composer("7", "Antonio", "Vivaldi", "Baroque"),

            new Composer("8", "Ludwig van", "Beethoven", "Classical"),
            new Composer("9", "Johannes", "Brahms", "Classical"),
            new Composer("10", "Francesco", "Cavalli", "Classical"),
            new Composer("11", "Fryderyk Franciszek", "Chopin", "Classical"),
            new Composer("12", "Antonin", "Dvorak", "Classical"),
            new Composer("13", "Franz Joseph", "Haydn", "Classical"),
            new Composer("14", "Gustav", "Mahler", "Classical"),
            new Composer("15", "Wolfgang Amadeus", "Mozart", "Classical"),
            new Composer("16", "Johann", "Pachelbel", "Classical"),
            new Composer("17", "Gioachino", "Rossini", "Classical"),
            new Composer("18", "Dmitry", "Shostakovich", "Classical"),
            new Composer("19", "Richard", "Wagner", "Classical"),

            new Composer("20", "Louis-Hector", "Berlioz", "Romantic"),
            new Composer("21", "Georges", "Bizet", "Romantic"),
            new Composer("22", "Cesar", "Cui", "Romantic"),
            new Composer("23", "Claude", "Debussy", "Romantic"),
            new Composer("24", "Edward", "Elgar", "Romantic"),
            new Composer("25", "Gabriel", "Faure", "Romantic"),
            new Composer("26", "Cesar", "Franck", "Romantic"),
            new Composer("27", "Edvard", "Grieg", "Romantic"),
            new Composer("28", "Nikolay", "Rimsky-Korsakov", "Romantic"),
            new Composer("29", "Franz Joseph", "Liszt", "Romantic"),

            new Composer("30", "Felix", "Mendelssohn", "Romantic"),
            new Composer("31", "Giacomo", "Puccini", "Romantic"),
            new Composer("32", "Sergei", "Rachmaninoff", "Romantic"),
            new Composer("33", "Camille", "Saint-Saens", "Romantic"),
            new Composer("34", "Franz", "Schubert", "Romantic"),
            new Composer("35", "Robert", "Schumann", "Romantic"),
            new Composer("36", "Jean", "Sibelius", "Romantic"),
            new Composer("37", "Bedrich", "Smetana", "Romantic"),
            new Composer("38", "Richard", "Strauss", "Romantic"),
            new Composer("39", "Pyotr Il'yich", "Tchaikovsky", "Romantic"),
            new Composer("40", "Guiseppe", "Verdi", "Romantic"),

            new Composer("41", "Bela", "Bartok", "Post-Romantic"),
            new Composer("42", "Leonard", "Bernstein", "Post-Romantic"),
            new Composer("43", "Benjamin", "Britten", "Post-Romantic"),
            new Composer("44", "John", "Cage", "Post-Romantic"),
            new Composer("45", "Aaron", "Copland", "Post-Romantic"),
            new Composer("46", "George", "Gershwin", "Post-Romantic"),
            new Composer("47", "Sergey", "Prokofiev", "Post-Romantic"),
            new Composer("48", "Maurice", "Ravel", "Post-Romantic"),
            new Composer("49", "Igor", "Stravinsky", "Post-Romantic"),
            new Composer("50", "Carl", "Orff", "Post-Romantic"),
        );
    }*
}

?>

----


[[business]]
=== 创建业务逻辑

实现用于处理由传入请求接收的 `autocomplete` URL 的逻辑。为此，请修改现有的 `index.php` 文件，而不是使用上一部分所述的文件向导来创建新 PHP 文件。

1. 在 "Projects"（项目）窗口中，单击 `index.php` 文件节点。文件名变为可编辑状态，以便您修改其名称。 

image::images/edit-file-name.png[title="单击文件节点可编辑名称"]



. 将该文件命名为 `autocomplete`，然后单击 Enter 键。双击新的 `autocomplete.php` 文件，使其显示在编辑器中。


. 将该文件的现有代码替换为以下脚本。

[source,php]
----

<?php

require_once("ComposerData.php");

session_start();

$composerData = new ComposerData();
$composers = $composerData->composers;

$results = array();
$namesAdded = false;

// simple matching for start of first or last name, or both
if(isset($_GET['action']) &amp;&amp; $_GET['action'] == "complete") {
    foreach($composers as $composer) {
        if(!is_numeric($_GET['id']) &amp;&amp;

            // if id matches first name
            (stripos($composer->firstName, $_GET['id']) === 0 ||

            // if id matches last name
            stripos($composer->lastName, $_GET['id']) === 0) ||

            // if id matches full name
            stripos($composer->firstName." ".$composer->lastName, $_GET['id']) === 0) {

                $results[] = $composer;
        }
    }

    // prepare xml data
    if(sizeof($results) != 0) {
        header('Content-type: text/xml');
        echo "<composers>";
        foreach($results as $result) {
            echo "<composer>";
            echo "<id>" . $result->id . "</id>";
            echo "<firstName>" . $result->firstName . "</firstName>";
            echo "<lastName>" . $result->lastName . "</lastName>";
            echo "</composer>";
        }
        echo "</composers>";
    }
}

// if user chooses from pop-up box
if(isset($_GET['action']) &amp;&amp; isset($_GET['id']) &amp;&amp; $_GET['action'] == "lookup") {
    foreach($composers as $composer) {
        if($composer->id == $_GET['id']) {
            $_SESSION ["id"] = $composer->id;
            $_SESSION ["firstName"] = $composer->firstName;
            $_SESSION ["lastName"] = $composer->lastName;
            $_SESSION ["category"] = $composer->category;

            header("Location: composerView.php");
        }
    }
}

?>
----

NOTE: 本教程中未描述文件 composerView.php。您可以创建这样的文件来查看搜索的最终结果。link:https://netbeans.org/projects/samples/downloads/download/Samples%252FPHP%252FMyAjaxApp.zip[+样例应用程序+]中包含了此文件的样例。

正如您看到的，编写用于进行 Ajax 处理的服务器端的代码时并没有什么真正的新内容要了解。如果希望交换 XML 文档，则需要将响应内容类型设置为 `text/xml`。通过 Ajax，您还可以交换纯文本，甚至可以交换可在客户端由回调函数计算或执行的 JavaScript 片段。还请注意，有些浏览器可能会缓存结果，因此可能需要将 Cache-Control HTTP 头信息设置为 `no-cache`。

在此示例中，`autocomplete.php` 文件生成了一个 XML 文档，其中包含姓氏或名字以用户所键入的字符开头的所有作曲家。本文档会映射到以上<<flow-diagram,流程图>>中所描述的 XML 数据。以下是返回到 `XMLHttpRequest` 对象的 XML 文档的示例：


[source,xml]
----

<composers>
    <composer>
        <id>12</id>
        <firstName>Antonin</firstName>
        <lastName>Dvorak</lastName>
    </composer>
    <composer>
        <id>45</id>
        <firstName>Aaron</firstName>
        <lastName>Copland</lastName>
    </composer>
    <composer>
        <id>7</id>
        <firstName>Antonio</firstName>
        <lastName>Vivaldi</lastName>
    </composer>
    <composer>
        <id>2</id>
        <firstName>Arcangelo</firstName>
        <lastName>Corelli</lastName>
    </composer>
</composers>

----



[[client2]]
== 客户端编程：第 2 部分

您必须定义回调函数，以处理服务器的响应，同时添加一些必要的功能，以反映用户所查看页面中的更改。这需要修改 HTML DOM。最后，可以使用 IDE 的 CSS 编辑器将简单的样式表添加到该演示。

* <<callback,添加回调功能>>
* <<htmldom,更新 HTML DOM>>
* <<stylesheet,附加样式表>>


[[callback]]
=== 添加回调功能

在 `XMLHttpRequest` 对象的 `readyState` 属性发生更改时，回调函数会在 HTTP 交互过程中的某个特定点被异步调用。在您要构建的应用程序中，回调函数是 `callback()`。您可以回想一下，在 `doCompletion()` 中，`callback` 设置为某个函数的 `XMLHttpRequest.onreadystatechange` 属性。现在，按以下步骤实现回调函数。

1. 在编辑器中打开 `javascript.js`，然后键入以下代码。

[source,java]
----

function callback() {
    if (req.readyState == 4) {
        if (req.status == 200) {
            parseMessages(req.responseXML);
        }
    }
}

----

`readyState` 为 "4" 表示 HTTP 交互完成。`XMLHttpRequest.readState` 的 API 表示可以设置 5 个值。它们是：

|===
|`readyState` 值 |对象状态定义 

|0 |未初始化 

|1 |正在加载 

|2 |已加载 

|3 |交互中 

|4 |完成 
|===

请注意，仅当 `XMLHttpRequest.readyState` 为 "4" 并且 `status`（请求的 HTTP 状态代码定义）为 "200"（表示成功）时，才会调用 `parseMessages()` 函数。您将在下面的<<htmldom,更新 HTML DOM>> 部分中定义 `parseMessages()`。


[[htmldom]]
=== 更新 HTML DOM

`parseMessages()` 函数用于处理传入的 XML 数据。为了实现此功能，它需要依靠若干附属的函数，如 `appendComposer()`、`getElementY()` 和 `clearTable()`。您还必须向此索引页引入新的元素（如用作自动完成框的另一个 HTML 表格），以及元素的 ID，以便它们可以在 `javascript.js` 中引用。最后，创建对应于 `index.php` 中元素 ID 的新变量，并且在之前实现的 `init()` 函数中对其进行初始化，然后添加每次加载 `index.php` 时所需要的一些功能。

NOTE: 您在以下步骤中创建的函数和元素之间存在相互依赖关系。建议您完成此部分，然后在代码全部完成之后检查此代码。

1. 在编辑器中打开 `index.html`，并在您之前创建的 HTML 表格的第二行键入以下代码。

[source,xml]
----

<tr>
    *<td id="auto-row" colspan="2">

    <td/>*
</tr>
----
此新行（可以标识为 "`auto-row`"）用作 JavaScript 代码的处理程序，以便插入一个新 HTML 表，该表将形成自动完成框。


. 在编辑器中打开 `javascript.js`，并在文件顶部添加以下三个变量。

[source,java]
----

var completeField;
var completeTable;
var autoRow;
----


. 将以下行（以*粗体*显示）添加到 `init()` 函数中。

[source,java]
----

function init() {
    completeField = document.getElementById("complete-field");
    *completeTable = document.createElement("table");
    completeTable.setAttribute("class", "popupBox");
    completeTable.setAttribute("style", "display: none");
    autoRow = document.getElementById("auto-row");
    autoRow.appendChild(completeTable);
    completeTable.style.top = getElementY(autoRow) + "px";*
}
----
`init()` 的一个作用是使修改索引页 DOM 的其他函数可以访问 `index.html` 内的元素。综上所述，该脚本创建一个新的 HTML `table`，添加 `popupBox` 类并将元素样式修改为 `display: none`。最终，将获得 `id` 为 `auto-row` 的元素，并插入新的 `table`。换句话说，运行代码时，修改后的 HTML 如下所示。

[source,xml]
----

<tr>
    <td id="auto-row" colspan="2">
        *<table class="popupBox" style="display: none"></table>*
    <td/>
</tr>
----


. 将 `appendComposer()` 添加到 `javascript.js`。

[source,java]
----

function appendComposer(firstName,lastName,composerId) {

    var row;
    var cell;
    var linkElement;

    if (isIE) {
        completeTable.style.display = 'block';
        row = completeTable.insertRow(completeTable.rows.length);
        cell = row.insertCell(0);
    } else {
        completeTable.style.display = 'table';
        row = document.createElement("tr");
        cell = document.createElement("td");
        row.appendChild(cell);
        completeTable.appendChild(row);
    }

    cell.className = "popupCell";

    linkElement = document.createElement("a");
    linkElement.className = "popupItem";
    linkElement.setAttribute("href", "autocomplete.php?action=lookup&amp;id=" + composerId);
    linkElement.appendChild(document.createTextNode(firstName + " " + lastName));
    cell.appendChild(linkElement);
}
----
此函数创建了一个新的表行，并用其所含的三个参数传递的数据将指向作曲家的链接插入此表行中，然后将此行插入索引页的 `complete-table` 元素中。


. 将 `clearTable()` 添加到 `javascript.js`。

[source,java]
----

function clearTable() {
    if (completeTable.getElementsByTagName("tr").length > 0) {
        completeTable.style.display = 'none';
        for (loop = completeTable.childNodes.length -1; loop >= 0 ; loop--) {
            completeTable.removeChild(completeTable.childNodes[loop]);
        }
    }
}
----
此函数用于将 `complete-table` 元素的显示方式设置为 "none"（无）（也就是使其不可见），并删除所有已创建的现有作曲家名字条目。


. 将 `getElementY()` 添加到 `javascript.js`。

[source,java]
----

function getElementY(element){

    var targetTop = 0;

    if (element.offsetParent) {
        while (element.offsetParent) {
            targetTop += element.offsetTop;
            element = element.offsetParent;
        }
    } else if (element.y) {
        targetTop += element.y;
    }
    return targetTop;
}
----
此函数用于查找父元素的垂直位置。这是必要的，因为此元素的实际位置（如果显示）通常根据浏览器的类型和版本而定。请注意，如果 `complete-table` 元素显示包含作曲家名字，则会移动到其所在表格中的右下角。正确的高度定位由 `getElementY()` 确定。 

NOTE: 请在 link:http://www.quirksmode.org/[+http://www.quirksmode.org/+] 上查看 `offset` 的link:http://www.quirksmode.org/js/findpos.html[+说明+]。



. 修改 `callback()` 函数以便每次从服务器接收到新数据时都调用 `clearTable()`。因此，在用新条目填充自动完成框之前其中已存在的任何作曲家条目均会删除。

[source,java]
----

function callback() {

    *clearTable();*

    if (req.readyState == 4) {
        if (req.status == 200) {
            parseMessages(req.responseXML);
        }
    }
}
----


. 将 `parseMessages()` 添加到 `javascript.js`。

[source,java]
----

function parseMessages(responseXML) {

    // no matches returned
    if (responseXML == null) {
        return false;
    } else {

        var composers = responseXML.getElementsByTagName("composers")[0];

        if (composers.childNodes.length > 0) {
            completeTable.setAttribute("bordercolor", "black");
            completeTable.setAttribute("border", "1");

            for (loop = 0; loop < composers.childNodes.length; loop++) {
                var composer = composers.childNodes[loop];
                var firstName = composer.getElementsByTagName("firstName")[0];
                var lastName = composer.getElementsByTagName("lastName")[0];
                var composerId = composer.getElementsByTagName("id")[0];
                appendComposer(firstName.childNodes[0].nodeValue,
                    lastName.childNodes[0].nodeValue,
                    composerId.childNodes[0].nodeValue);
            }
        }
    }
}
----

`parseMessages()` 函数作为参数接收 `autocomplete.php` 文件返回的 XML 文档的对象表示。此函数以编程方式遍历 XML 文档，提取每个条目的 `firstName`、`lastName` 和 `id`，然后将数据传递到 `appendComposer()`。这将导致动态更新 `complete-table` 元素的内容。例如，已生成并插入 `complete-table` 中的条目如下所示：


[source,xml]
----

<tr>
    <td class="popupCell">
        <a class="popupItem" href="autocomplete?action=lookup&amp;id=12">Antonin Dvorak</a>
    </td>
</tr>

----

对 `complete-table` 元素的动态更新是使用 Ajax 进行通信的过程中所产生的通信过程流的最后一步。此更新会映射到正在发送给以上<<flow-diagram,流程图>>中表示的 HTML 和 CSS 数据。


[[stylesheet]]
=== 附加样式表

在此阶段，您已完成了实现此应用程序功能所需的所有代码。要查看您的努力成果，请立即尝试运行此应用程序。

1. 运行该项目以查看其在浏览器中的外观。单击 "Run Project"（运行项目）(image:images/run-project-btn.png[]) 按钮。在浏览器中显示 `index.html` 文件。 
image:images/no-css.png[title="不带样式表的成功部署"]

要在应用程序中附加一个样式表，只需创建 CSS（Cascading Style Sheet，级联样式表）文件，并从表示页面链接到此文件即可。当您使用 CSS 文件时，IDE 会为您提供代码完成支持，以及其他一些有助于生成样式表规则的功能。其中包括：

* *CSS 样式构建器：*此界面旨在让您可以使用所选的控件和窗口部件创建规则。（"Window"（窗口）> "Other"（其他）> "CSS Style Builder"（CSS 样式构建器））
* *CSS 预览：*如果您将光标置于某个规则中，此预览窗口就会显示根据此规则的声明块所呈现的样例文本。（"Window"（窗口）> "Other"（其他）> "CSS Preview"（CSS 预览））
* *样式规则编辑器：*通过此对话框，您可以基于类、ID 和 HTML 元素创建规则，并在文档分层结构中设置其位置。（位于 CSS 编辑器工具栏左上角区域的 "Create Rule"（创建规则）(image:images/style-rule-editor-btn.png[]) 按钮）

NetBeans 6.9 提供了“重命名重构”和“查找使用实例”支持。此支持不仅可用于 css 文件，而且也可用于包含嵌入 CSS 代码的所有文件（例如，HTML、PHP）。在所有项目文件中，均可重构 CSS 类、id 和类型元素。要使用此重构支持，请在给定 CSS 元素上按 Ctrl-R 组合键，然后使用提供的对话框执行重命名操作。在执行重命名操作之前，也可以预览更改。要使用“查找使用实例”支持，请右键单击 CSS 元素，然后选择 "Find Usages"（查找使用实例）。有关更多详细信息，请参见 link:http://wiki.netbeans.org/wiki/index.php?title=NewAndNoteworthy69m1&section=T-25#Web_Languages[+NewAndNoteworthy69m1+]。

请执行下列步骤，在应用程序中附加样式表。

1. 在 "Projects"（项目）窗口中，右键单击项目节点，然后选择 "New"（新建）> "Cascading Style Sheet"（级联样式表）（如果 "Cascading Style Sheet"（级联样式表）未列出，则选择 "Other"（其他）。然后，从新建文件向导的 "Other"（其他）类别中选择 "Cascading Style Sheet"（级联样式表）。）
2. 在 "CSS File Name"（CSS 文件名）文本字段中，键入 `stylesheet`。
3. 单击 "Finish"（完成）。此时新文件会添加到 "Projects"（项目）窗口中，并在 IDE 的编辑器中打开。
4. 在 `stylesheet.css` 中，键入以下规则。您可以使用 IDE 的代码完成支持，方法是：在希望查看建议时按 Ctrl-空格组合键。

[source,java]
----

body {
   font-family: sans-serif;
   font-size: smaller;
   padding: 50px;
   color: #555;
   width: 650px;
}

h1 {
   letter-spacing: 6px;
   font-size: 1.6em;
   color: #be7429;
   font-weight: bold;
}

h2 {
   text-align: left;
   letter-spacing: 6px;
   font-size: 1.4em;
   color: #be7429;
   font-weight: normal;
   width: 450px;
}

table {
   width: 550px;
   padding: 10px;
   background-color: #c5e7e0;
}

td {
   padding: 10px;
}

a {
  color: #be7429;
  text-decoration: none;
}

a:hover {
  text-decoration: underline;
}

.popupBox {
  position: absolute;
  top: 170px;
  left: 140px;
}

.popupCell {
   background-color: #fffafa;
}

.popupCell:hover {
  background-color: #f5ebe9;
}

.popupItem {
  color: #333;
  text-decoration: none;
  font-size: 1.2em;
}
----

对 CSS 代码的有效性执行检查，方法是右键单击 CSS 编辑器，然后选择 "Check CSS"（检查 CSS）。遇到的任何错误都会显示在 "Output"（输出）窗口中（"Windows"（窗口）> "Output"（输出））。



. 切换到编辑器中的 `index.html` 页（按 Ctrl-Tab 组合键），然后在 `<head>` 标记之间添加对样式表的引用。

[source,java]
----

<link rel="stylesheet" type="text/css" href="stylesheet.css">

----


. 再次运行应用程序。将使用您刚创建的样式表在浏览器中显示索引页。每次您键入字符时，都会向服务器发送异步请求，并返回 `autocomplete.php` 准备好的 XML 数据。随着您输入的字符增多，为了反映匹配项的新列表，作曲家名字的数量会越来越少。



[[conclusion]]
== 小结

以下内容对 Ajax 简介进行了小结。希望现在您已经明白了 Ajax 只是在后台通过 HTTP 交换信息，并基于结果动态地更新该页面。

您可能注意到，构建的应用程序存在许多缺点，例如，从自动完成框中选择作曲家姓名时无任何响应！欢迎您link:https://netbeans.org/projects/samples/downloads/download/Samples%252FPHP%252FMyAjaxApp.zip[+下载样例应用程序+]以了解它是如何使用 PHP 技术实现的。此外，您可能想要调查使用户无法请求数据存储中不存在的名字的验证。通过学习 link:../../trails/php.html[+NetBeans PHP 学习资源+]中的其他教程，可以更多地了解这些技术。

link:/about/contact_form.html?to=3&subject=Feedback: Introduction to Ajax (PHP)[+请将您的反馈意见发送给我们+]



[[seeAlso]]
== 另请参见

有关 link:https://netbeans.org/[+netbeans.org+] 上 Ajax 和 PHP 技术的更多信息，请参见下面的资源：

* link:wish-list-tutorial-main-page.html[+使用 PHP 创建梦想清单 CRUD 应用程序+]。该教程包含 9 个步骤，介绍如何使用 IDE 中的 PHP 支持来创建 CRUD 应用程序。
* link:../../docs/web/js-toolkits-jquery.html[+使用 jQuery 改善 Web 页的外观和可用性+]。介绍如何将 jQuery 核心和 UI 库集成到 NetBeans 项目中。
* link:../../docs/web/js-toolkits-dojo.html[+使用 JSON 将 Dojo 树连接至 ArrayList+]。本文档基于 JavaOne 动手实验室，介绍了如何在 Web 页中实现 Dojo 树窗口部件，并使服务器端能够以 JSON 格式响应树请求。
