AJAX 和 PHP: 构建响应式 Web 应用程序: 第 5 章: AJAX 聊天和 JSON






4.63/5 (19投票s)
在本章中,我们将实现自己的在线聊天应用程序。我们将借此机会学习 JSON(JavaScript 对象表示法),它是 XML 在 Web 浏览器和 Web 服务器之间交换数据的一种替代表示方法。
|
引言
在 AJAX 出现之前,在线聊天解决方案就已非常流行。这种流行有诸多原因,如果您曾经使用过 Internet Relay Chat (IRC) 客户端、即时通讯 (IM) 程序或 Java 聊天小程序,您可能对它们很熟悉。
AJAX 通过简化实现其他技术难以或棘手的功能,进一步推动了在线聊天解决方案的发展。首先,AJAX 聊天应用程序继承了所有典型的 AJAX 优势,例如与现有浏览器功能的集成,以及(如果编写得当)跨平台兼容性。
另一个优点是,AJAX 聊天应用程序避免了其他技术常见的连接问题,因为许多防火墙会阻止其使用的通信端口。另一方面,AJAX 仅使用 HTTP 与服务器进行通信。
目前最令人印象深刻的 AJAX 聊天应用程序可能是 Meebo。我们很有把握你们中的一些人听说过它,如果您还没有听说过,我们建议您去看看。Meebo 的第一个也是最重要的功能是它允许您仅通过 Web 界面登录您喜欢的 IM 系统。在撰写本文时,Meebo 允许您连接到 AIM 或 ICQ、Yahoo! Messenger、Jabber、GTalk 和 MSN。您可以在一个用户友好界面的网页上访问所有这些服务,而无需弹出窗口或 Java 小程序。
Meebo 并不是唯一提供聊天功能的 Web 应用程序。即使 AJAX 还很年轻,快速搜索一下 Google 的“AJAX Chat”也会发现其他几个应用程序。
是时候开始工作了。在本章的其余部分,我们将实现自己的在线聊天应用程序。我们将借此机会学习 **JSON(JavaScript 对象表示法)**,它是 XML 在 Web 浏览器和 Web 服务器之间交换数据的一种替代表示方法。
JSON 简介
JSON 是一种数据格式,您可以在 JavaScript 客户端和 PHP 服务器脚本之间交换信息时使用它来代替 XML。有趣的是,JSON 的流行度随着 AJAX 现象的出现而增加,尽管 AJAX 首字母缩略词暗示了使用 XML。
由于 XML 是一种更流行、支持更广泛的格式,因此我们选择在本本书的所有示例中使用 XML,除了这个示例。但是,如果您愿意,可以很容易地更新其他代码示例以使用 JSON 而不是 XML。(如您所知,这个 AJAX Chat 章节还有一个使用 XML 的版本,您可以比较它们以查看差异。)
也许对 JSON 最好的简短描述是 其官方网站提出的:“**JSON**(JavaScript 对象表示法)是一种轻量级的数据交换格式。它易于人类阅读和编写。易于机器解析和生成”。
如果您是 JSON 新手,您可能会问一个公平的问题:为什么还需要另一种数据交换格式? JSON 和 XML 一样,是一种基于文本的格式,对人类和计算机都易于编写和理解。上述定义中的关键词是“轻量级”。JSON 数据结构占用的带宽比其 XML 版本少。
为了让您对 JSON 与 XML 的比较有一个概念,让我们采用相同的数据结构,看看如何使用这两种标准来表示它。在您编写的聊天应用程序中,服务器为客户端组合的消息,如果用 XML 编写,会像这样:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<response>
<clear>false</clear>
<messages>
<message>
<id>1</id>
<color>#000000</color>
<time>2006-01-17 09:07:31</time>
<name>Guest550</name>
<text>Hello there! What's up?</text>
</message>
<message>
<id>2</id>
<color>#000000</color>
<time>2006-01-17 09:21:34</time>
<name>Guest499</name>
<text>This is a test message</text>
</message>
</messages>
</response>
同一条消息,这次用 JSON 编写,看起来像这样:
[
{"clear":"false"},
"messages":
[
{"message":
{"id":"1",
"color":"#000000",
"time":"2006-01-17 09:07:31",
"name":"Guest550",
"text":"Hello there! What's up?"}
},
{"message":
{"id":"2",
"color":"#000000",
"time":"2006-01-17 09:21:34",
"name":"Guest499",
"text":"This is a test message"}
}
]
}
]
正如您所见,它们差别不大。如果我们忽略为提高可读性而添加的额外格式化空格,XML 消息占用 396 字节,而 JSON 消息只有 274 字节。
JSON 被称为 JavaScript 的子集,因为它包含两种基本结构:**对象**和**数组**。对象是键/值对的无序集合,定义形式如下:
{ name1:value1, name2:value2, ... }
数组是值的有序列表,定义形式如下:
[ value1, value2, ... ]
值的类型可以是对象、字符串、数字、数组、true、false 或 null。字符串是包含在双引号中的 Unicode 字符集合。对于转义,我们使用反斜杠。
有关更详细的信息,请参阅 www.json.org,它对理论进行了精彩的阐述。
安装 JSON
显而易见,当您计划使用 JSON 时,您需要能够在 JavaScript 和 PHP 中解析和生成 JSON 结构。JSON 库可用于当今大多数编程语言:ActionScript、C、C++、C#、Delphi、E、Erlang、Java、JavaScript、Lisp、Lua、ML 和 Ruby、Objective CAML、OpenLazslo、Perl、PHP、Python、Rebol、Ruby 和 Squeak。
对于 JavaScript,我们将使用 此处列出的库。小型 JSON 库的直接链接是 这个。整个安装过程包括将此文件复制到您的应用程序文件夹,并在需要其功能的那些文件中引用它。
我们用于 PHP 的 JSON 解决方案是 Michal Migurski 开发的库,可以从 此处下载。安装该库只需下载 PHP 类文件,然后将其复制到应用程序文件夹中。接下来,我们需要使用 `require_once` 指令将其引用到我们的应用程序中,如下所示:
require_once('JSON.php');
为了有效地开始使用它,我们需要实例化该类:
$json= new Services_JSON();
然后您就可以使用了。encode 和 decode 方法允许我们将 PHP 对象编码为 JSON 格式,并将 JSON 字符串解码为 PHP 对象。
将 JSON 与 JavaScript 和 PHP 结合使用
在 JavaScript 中构建 JSON 结构的典型方法:
var myMessages =
{'messages' : [
{'username': 'Guest0740',
'message': 'This is a JSON message',
'color': '#eeeeee'},
{'username': 'Guest0740',
'message': 'This is another JSON message',
'color': '#eeeeee'} ]};
alert(myMessages.toJSONString());
第一个命令定义了一个名为 `myMessages` 的对象,该对象包含一个名为 `messages` 的成员,该成员又包含一个数组,该数组包含两个对象,每个对象包含三个成员(用户名、消息和颜色)。要访问“This is another JSON message”文本,可以使用以下语法:
myMessages[0].messages[1].message
以下两个代码片段展示了在 JavaScript 中读取 JSON 结构的典型方法:
myMessagesJSON = myMessages.toJSONString();
myMessagesFromJSON = myMessagesJSON.parseJSON();
或者
myMessagesFromJSON = eval ('(' + myMessagesJSON + ') ');
`eval` 函数非常快,但它允许执行任何 JavaScript 代码,因此使用 `parseJSON` 方法通常更安全。
在 PHP 中,您可以使用 `encode` 和 `decode` 方法来编码和解码 JSON 消息。让我们以之前的示例为例,在 PHP 中做一些类似的事情。在 PHP 中构建 JSON 结构的典型方法:
require_once('JSON.php');
$json = new Services_JSON();
$myMessages = array ('messages' =>
array(
array ('username' => 'Guest0740',
'message' => 'This is a JSON message',
'color' => '#eeeeee'),
array('username' => 'Guest0740',
'message' => 'This is another JSON message',
'color' => '#eeeeee')));
echo $json->encode($myMessages);
在 PHP 中读取 JSON 结构的典型方法是:
require_once('JSON.php');
$json = new Services_JSON();
$myMessagesFromJSON = $json->decode($myMessagesJSON);
实现 AJAX 聊天
现在,是时候实现 AJAX 聊天应用程序了。我们将保持应用程序的简单性、模块化和可扩展性。我们不会实现登录模块、聊天室支持、在线用户列表等。通过保持简单,我们试图关注本章的目标:在不导致页面重新加载的情况下发布和检索消息。我们还将允许用户选择其消息的颜色,因为这涉及一个 AJAX 机制,这将是另一个很好的练习。
该聊天应用程序可以在 线路上测试,如图 5.1 所示。
图 5.1:AJAX 聊天
本章的新颖之处在于,您将有两个 `XMLHttpRequest` 对象。第一个对象将处理聊天窗口的更新,第二个对象将处理颜色选择器(当您单击图像时,坐标会发送到服务器,服务器会回复颜色代码)。
AJAX 聊天的消息保存在一个队列(FIFO 结构)中,其功能已在第 4 章中介绍,因此即使服务器速度慢,消息也不会丢失,并且它们始终以发送的顺序到达服务器。与当今 Internet 上可以找到的其他模式不同,我们还确保在当前请求完成之前不向服务器发送更多请求。
为了使此示例正常工作,您需要 GD 库。附录 A 中的安装说明包括对 GD 库的支持。
动手实践 — AJAX 聊天
- 连接到 AJAX 数据库,并使用以下代码创建一个名为 *chat* 的表:
CREATE TABLE chat ( chat_id int(11) NOT NULL auto_increment, posted_on datetime NOT NULL, user_name varchar(255) NOT NULL, message text NOT NULL, color char(7) default '#000000', PRIMARY KEY (chat_id) );
- 在您的 *ajax* 文件夹中,创建一个名为 *chat* 的新文件夹。
- 将 *palette.png* 文件从代码下载复制到 *chat* 文件夹。
- 我们将从服务器功能开始创建应用程序。在 chat 文件夹中,创建一个名为 *config.php* 的文件,并在此文件中添加数据库配置代码(请更改这些值以匹配您的配置):
<?php // defines database connection data define('DB_HOST', 'localhost'); define('DB_USER', 'ajaxuser'); define('DB_PASSWORD', 'practical'); define('DB_DATABASE', 'ajax'); ?>
- 现在,添加标准的错误处理文件 *error_handler.php*:
<?php // set the user error handler method to be error_handler set_error_handler('error_handler', E_ALL); // error handler function function error_handler($errNo, $errStr, $errFile, $errLine) { // clear any output that has already been generated if(ob_get_length()) ob_clean(); // output the error message $error_message = 'ERRNO: ' . $errNo . chr(10) . 'TEXT: ' . $errStr . chr(10) . 'LOCATION: ' . $errFile . ', line ' . $errLine; echo $error_message; // prevent processing any more PHP scripts exit; } ?>
- 创建另一个名为 *chat.php* 的文件,并添加以下代码:
<?php // reference the file containing the Chat class require_once('chat.class.php'); // reference the file containing the JSON class require_once('JSON.php'); // create an instance of the JSON class $json=new Services_JSON(); // retrieve the operation to be performed $mode = $_POST['mode']; // default the last id to 0 $id = 0; // create a new Chat instance $chat = new Chat(); // if the operation is SendAndRetrieve if($mode == 'SendAndRetrieveNew') { // retrieve the action parameters used to add a new message $name = $_POST['name']; $message = $_POST['message']; $color = $_POST['color']; $id = $_POST['id']; // check if we have valid values if ($name != '' && $message != '' && $color != '') { // post the message to the database $chat->postMessage($name, $message, $color); } } // if the operation is DeleteAndRetrieve elseif($mode == 'DeleteAndRetrieveNew') { // delete all existing messages $chat->deleteMessages(); } // if the operation is Retrieve elseif($mode == 'RetrieveNew') { // get the id of the last message retrieved by the client $id = $_POST['id']; } // Clear the output if(ob_get_length()) ob_clean(); // Headers are sent to prevent browsers from caching header('Expires: Mon, 26 Jul 1997 05:00:00 GMT'); header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . 'GMT'); header('Cache-Control: no-cache, must-revalidate'); header('Pragma: no-cache'); header('Content-Type: text/javascript'); // retrieve new messages from the server and send them in JSON format echo $json->encode($chat->retrieveNewMessages($id)); ?>
- 创建另一个名为 *chat.class.php* 的文件,并添加以下代码:
<?php // load configuration file require_once('config.php'); // load error handling module require_once('error_handler.php'); // class that contains server-side chat functionality class Chat { // database handler private $mMysqli; // constructor opens database connection function __construct() { // connect to the database $this->mMysqli = new mysqli(DB_HOST, DB_USER, DB_PASSWORD, DB_DATABASE); } // destructor closes database connection public function __destruct() { $this->mMysqli->close(); } // truncates the table containing the messages public function deleteMessages() { // build the SQL query that adds a new message to the server $query = 'TRUNCATE TABLE chat'; // execute the SQL query $result = $this->mMysqli->query($query); } /* The postMessages method inserts a message into the database - $name represents the name of the user that posted the message - $messsage is the posted message - $color contains the color chosen by the user */ public function postMessage($name, $message, $color) { // escape the variable data for safely adding them to the database $name = $this->mMysqli->real_escape_string($name); $message = $this->mMysqli->real_escape_string($message); $color = $this->mMysqli->real_escape_string($color); // build the SQL query that adds a new message to the server $query = 'INSERT INTO chat(posted_on, user_name, message, color) ' . 'VALUES (NOW(), "' . $name . '" , "' . $message . '","' . $color . '")'; // execute the SQL query $result = $this->mMysqli->query($query); } /* The retrieveNewMessages method retrieves the new messages that have been posted to the server. - the $id parameter is sent by the client and it represents the id of the last message received by the client. Messages more recent by $id will be fetched from the database and returned to the client in JSON format. */ public function retrieveNewMessages($id=0) { // escape the variable data $id = $this->mMysqli->real_escape_string($id); // compose the SQL query that retrieves new messages if($id>0) { // retrieve messages newer than $id $query = 'SELECT chat_id, user_name, message, color, ' . ' DATE_FORMAT(posted_on, "%Y-%m-%d %H:%i:%s") ' . ' AS posted_on ' . ' FROM chat WHERE chat_id > ' . $id . ' ORDER BY chat_id ASC'; } else { // on the first load only retrieve the last 50 messages from server $query = ' SELECT chat_id, user_name, message, color, posted_on FROM ' . ' (SELECT chat_id, user_name, message, color, ' . ' DATE_FORMAT(posted_on, "%Y-%m-%d %H:%i:%s") AS posted_on ' . ' FROM chat ' . ' ORDER BY chat_id DESC ' . ' LIMIT 50) AS Last50' . ' ORDER BY chat_id ASC'; } // execute the query $result = $this->mMysqli->query($query); // initialize the response array $response = array(); // add the clear flag to the response array_push( $response,array('clear'=> $this->isDatabaseCleared($id))); // initialize the results array $results=array(); // check to see if we have any results if($result->num_rows) { // loop through all the fetched messages to build the results array while ($row = $result->fetch_array(MYSQLI_ASSOC)) { $id = htmlspecialchars($row['chat_id']); $color = htmlspecialchars($row['color']); $userName = htmlspecialchars($row['user_name']); $time = htmlspecialchars($row['posted_on']); $message = htmlspecialchars($row['message']); array_push($results,array('id' => $id , 'color' => $color , 'time' => $time , 'name' => $userName , 'message' => $message )); } // close the database connection as soon as possible $result->close(); } // add the results to the response array_push($response, array('results' => $results)); return $response; } /* The isDatabaseCleared method checks to see if the database has been cleared since last call to the server - the $id parameter contains the id of the last message received by the client */ private function isDatabaseCleared($id) { if($id>0) { // by checking the number of rows with ids smaller than the client's // last id we check to see if a truncate operation was performed in // the meantime $check_clear = 'SELECT count(*) old FROM chat where chat_id<=' . $id; $result = $this->mMysqli->query($check_clear); $row = $result->fetch_array(MYSQLI_ASSOC); // if a truncate operation occured the whiteboard needs to be reset if($row['old']==0) return 'true'; } return 'false'; } } ?>
- 创建另一个名为 *get_color.php* 的文件,并添加以下代码:
<?php // the name of the image file $imgfile='palette.png'; // load the image file $img=imagecreatefrompng($imgfile); // obtain the coordinates of the point clicked by the user $offsetx=$_GET['offsetx']; $offsety=$_GET['offsety']; // get the clicked color $rgb = ImageColorAt($img, $offsetx, $offsety); $r = ($rgb >> 16) & 0xFF; $g = ($rgb >> 8) & 0xFF; $b = $rgb & 0xFF; // return the color code printf('#%02s%02s%02s', dechex($r), dechex($g), dechex($b)); ?>
- 现在来处理客户端。首先创建 *chat.css* 并添加以下代码:
body { font-family: Tahoma, Helvetica, sans-serif; margin: 1px; font-size: 12px; text-align: left } #content { border: DarkGreen 1px solid; margin-bottom: 10px } input { border: #999 1px solid; font-size: 10px } #scroll { position: relative; width: 340px; height: 270px; overflow: auto } .item { margin-bottom: 6px } #colorpicker { text-align:center }
- 创建一个名为 *index.html* 的新文件,并添加以下代码:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> <head> <title>AJAX Chat</title> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <link href="chat.css" rel="stylesheet" type="text/css" /> <script type="text/javascript" src="json.js" ></script> <script type="text/javascript" src="chat.js" ></script> </head> <body onload="init();"> <noscript> Your browser does not support JavaScript!! </noscript> <table id="content"> <tr> <td> <div id="scroll"> </div> </td> <td id="colorpicker"> <img src="palette.png" id="palette" alt="Color Palette" border="1" onclick="getColor(event);"/> <br /> <input id="color" type="hidden" readonly="true" value="#000000" /> <span id="sampleText"> (text will look like this) </span> </td> </tr> </table> <div> <input type="text" id="userName" maxlength="10" size="10" onblur="checkUsername();"/> <input type="text" id="messageBox" maxlength="2000" size="50" onkeydown="handleKey(event)"/> <input type="button" value="Send" onclick="sendMessage();" /> <input type="button" value="Delete All" onclick="deleteMessages();" /> </div> </body> </html>
- 创建另一个名为 *chat.js* 的文件,并添加以下代码:
/* chatURL - URL for updating chat messages */ var chatURL = "chat.php"; /* getColorURL - URL for retrieving the chosen RGB color */ var getColorURL = "get_color.php"; /* create XMLHttpRequest objects for updating the chat messages and getting the selected color */ var xmlHttpGetMessages = createXmlHttpRequestObject(); var xmlHttpGetColor = createXmlHttpRequestObject(); /* variables that establish how often to access the server */ // how many miliseconds to wait to get new message var updateInterval = 1000; // when set to true, display detailed error messages var debugMode = true; /* initialize the messages cache */ var cache = new Array(); /* lastMessageID - the ID of the most recent chat message */ var lastMessageID = -1; /* mouseX, mouseY - the event's mouse coordinates */ var mouseX,mouseY; /* creates an XMLHttpRequest instance */ function createXmlHttpRequestObject() { // will store the reference to the XMLHttpRequest object var xmlHttp; // this should work for all browsers except IE6 and older try { // try to create XMLHttpRequest object xmlHttp = new XMLHttpRequest(); } catch(e) { // assume IE6 or older var XmlHttpVersions = new Array("MSXML2.XMLHTTP.6.0", "MSXML2.XMLHTTP.5.0", "MSXML2.XMLHTTP.4.0", "MSXML2.XMLHTTP.3.0", "MSXML2.XMLHTTP", "Microsoft.XMLHTTP"); // try every prog id until one works for (var i=0; i<XmlHttpVersions.length && !xmlHttp; i++) { try { // try to create XMLHttpRequest object xmlHttp = new ActiveXObject(XmlHttpVersions[i]); } catch (e) {} } } // return the created object or display an error message if (!xmlHttp) alert("Error creating the XMLHttpRequest object."); else return xmlHttp; } /* this function initiates the chat; it executes when the chat page loads */ function init() { // get a reference to the text box // where the user writes new messages var oMessageBox = document.getElementById("messageBox"); // prevents the autofill function from starting oMessageBox.setAttribute("autocomplete", "off"); // references the "Text will look like this" message var oSampleText = document.getElementById("sampleText"); // set the default color to black oSampleText.style.color = "black"; // ensures our user has a default random name when the form loads checkUsername(); // initiates updating the chat window requestNewMessages(); } // function that ensures that the username is never empty and if so // a random name is generated function checkUsername() { // ensures our user has a default random name when the form loads var oUser=document.getElementById("userName"); if(oUser.value == "") oUser.value = "Guest" + Math.floor(Math.random() * 1000); } /* function called when the Send button is pressed */ function sendMessage() { // save the message to a local variable and clear the text box var oCurrentMessage = document.getElementById("messageBox"); var currentUser = document.getElementById("userName").value; var currentColor = document.getElementById("color").value; // don't send void messages if (trim(oCurrentMessage.value) != "" && trim(currentUser) != "" && trim (currentColor) != "") { // if we need to send and retrieve messages params = "mode=SendAndRetrieveNew" + "&id=" + encodeURIComponent(lastMessageID) + "&color=" + encodeURIComponent(currentColor) + "&name=" + encodeURIComponent(currentUser) + "&message=" + encodeURIComponent(oCurrentMessage.value); // add the message to the queue cache.push(params); // clear the text box oCurrentMessage.value = ""; } } /* function called when the Delete Messages button is pressed */ function deleteMessages() { // set the flag that specifies we're deleting the messages params = "mode=DeleteAndRetrieveNew"; // add the message to the queue cache.push(params); } /* makes asynchronous request to retrieve new messages, post new messages, delete messages */ function requestNewMessages() { // retrieve the username and color from the page var currentUser = document.getElementById("userName").value; var currentColor = document.getElementById("color").value; // only continue if xmlHttpGetMessages isn't void if(xmlHttpGetMessages) { try { // don't start another server operation if such an operation // is already in progress if (xmlHttpGetMessages.readyState == 4 || xmlHttpGetMessages.readyState == 0) { // we will store the parameters used to make the server request var params = ""; // if there are requests stored in queue, take the oldest one if (cache.length>0) params = cache.shift(); // if the cache is empty, just retrieve new messages else params = "mode=RetrieveNew" + "&id=" +lastMessageID; // call the server page to execute the server-side operation xmlHttpGetMessages.open("POST", chatURL, true); xmlHttpGetMessages.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); xmlHttpGetMessages.onreadystatechange = handleReceivingMessages; xmlHttpGetMessages.send(params); } else { // we will check again for new messages setTimeout("requestNewMessages();", updateInterval); } } catch(e) { displayError(e.toString()); } } } /* function that handles the http response when updating messages */ function handleReceivingMessages() { // continue if the process is completed if (xmlHttpGetMessages.readyState == 4) { // continue only if HTTP status is "OK" if (xmlHttpGetMessages.status == 200) { try { // process the server's response readMessages(); } catch(e) { // display the error message displayError(e.toString()); } } else { // display the error message displayError(xmlHttpGetMessages.statusText); } } } /* function that processes the server's response when updating messages */ function readMessages() { // retrieve the server's response var response = xmlHttpGetMessages.responseText; // server error? if (response.indexOf("ERRNO") >= 0 || response.indexOf("error:") >= 0 || response.length == 0) throw(response.length == 0 ? "Void server response." : response); // retrieve the JSON object correspondig to the responseText element responseJSON = xmlHttpGetMessages.responseText.parseJSON(); // retrieve the flag that says if the chat window has been cleared clearChat = responseJSON[0].clear; // if the clear flag is set, we need to clear the chat window if(clearChat == "true") { // clear chat window and reset the id document.getElementById("scroll").innerHTML = ""; lastMessageID = -1; } // initialize the arrays idArray = new Array(); colorArray = new Array(); nameArray = new Array(); timeArray = new Array(); messageArray = new Array(); // retrieve the arrays for(i=0;i<responseJSON[1].results.length;i++) { // retrieve the arrays from the server's response idArray[i] = responseJSON[1].results[i].id; colorArray[i] = responseJSON[1].results[i].color; nameArray[i] = responseJSON[1].results[i].name; timeArray[i] = responseJSON[1].results[i].time; messageArray[i] = responseJSON[1].results[i].message; } // add the new messages to the chat window displayMessages(idArray, colorArray, nameArray, timeArray, messageArray); // the ID of the last received message is stored locally if(idArray.length>0) lastMessageID = idArray[idArray.length - 1]; // restart sequence setTimeout("requestNewMessages();", updateInterval); } /* function that appends the new messages to the chat list */ function displayMessages(idArray, colorArray, nameArray, timeArray, messageArray) { // each loop adds a new message for(var i=0; i<idArray.length; i++) { // get the message details var color = colorArray[i]; var time = timeArray[i]; var name = nameArray[i]; var message = messageArray[i]; // compose the HTML code that displays the message var htmlMessage = ""; htmlMessage += "<div class=\"item\" style=\"color:" + color + "\">"; htmlMessage += "[" + time + "] " + name + " said: <br/>"; htmlMessage += message.toString(); htmlMessage += "</div>"; // display the message displayMessage (htmlMessage); } } // displays a message function displayMessage(message) { // get the scroll object var oScroll = document.getElementById("scroll"); // check if the scroll is down var scrollDown = (oScroll.scrollHeight - oScroll.scrollTop <= oScroll.offsetHeight ); // display the message oScroll.innerHTML += message; // scroll down the scrollbar oScroll.scrollTop = scrollDown ? oScroll.scrollHeight : oScroll.scrollTop; } // function that displays an error message function displayError(message) { // display error message, with more technical details if debugMode is true displayMessage("Error accessing the server! "+ (debugMode ? "<br/>" + message : "")); } /* handles keydown to detect when enter is pressed */ function handleKey(e) { // get the event e = (!e) ? window.event : e; // get the code of the character that has been pressed code = (e.charCode) ? e.charCode : ((e.keyCode) ? e.keyCode : ((e.which) ? e.which : 0)); // handle the keydown event if (e.type == "keydown") { // if enter (code 13) is pressed if(code == 13) { // send the current message sendMessage(); } } } /* removes leading and trailing spaces from the string */ function trim(s) { return s.replace(/(^\s+)|(\s+$)/g, "") } /* function that computes the mouse's coordinates in page */ function getMouseXY(e) { // browser specific if(window.ActiveXObject) { mouseX = window.event.x + document.body.scrollLeft; mouseY = window.event.y + document.body.scrollTop; } else { mouseX = e.pageX; mouseY = e.pageY; } } /* makes a server call to get the RGB code of the chosen color */ function getColor(e) { getMouseXY(e); // don't do anything if the XMLHttpRequest object is null if(xmlHttpGetColor) { // initialize the offset position with the mouse current position var offsetX = mouseX; var offsetY = mouseY; // get references var oPalette = document.getElementById("palette"); var oTd = document.getElementById("colorpicker"); // compute the offset position in our window if(window.ActiveXObject) { offsetX = window.event.offsetX; offsetY = window.event.offsetY; } else { offsetX -= oPalette.offsetLeft + oTd.offsetLeft; offsetY -= oPalette.offsetTop + oTd.offsetTop; } // call server asynchronously to find out the clicked color try { if (xmlHttpGetColor.readyState == 4 || xmlHttpGetColor.readyState == 0) { params = "?offsetx=" + offsetX + "&offsety=" + offsetY; xmlHttpGetColor.open("GET", getColorURL+params, true); xmlHttpGetColor.onreadystatechange = handleGettingColor; xmlHttpGetColor.send(null); } } catch(e) { // display error message displayError(xmlHttp.statusText); } } } /* function that handles the http response */ function handleGettingColor() { // if the process is completed, decide to do with the returned data if (xmlHttpGetColor.readyState == 4) { // only if HTTP status is "OK" if (xmlHttpGetColor.status == 200) { try { //change the color changeColor(); } catch(e) { // display error message displayError(xmlHttpGetColor.statusText); } } else { // display error message displayError(xmlHttpGetColor.statusText); } } } /* function that changes the color used for displaying messages */ function changeColor() { response=xmlHttpGetColor.responseText; // server error? if (response.indexOf("ERRNO") >= 0 || response.indexOf("error:") >= 0 || response.length == 0) throw(response.length == 0 ? "Can't change color!" : response); // change color var oColor = document.getElementById("color"); var oSampleText = document.getElementById("sampleText"); oColor.value = response; oSampleText.style.color = response; }
- 如果您还没有这样做,请将 json.js 复制到您的项目文件夹中。
刚刚发生了什么?
首先,请确保应用程序工作正常。使用 Web 浏览器加载 https:///ajax/chat/index.html,您应该会看到一个页面,其外观如图 5.1 所示。
从技术上讲,该应用程序分为两个较小的应用程序,共同构成最终解决方案:
- 聊天应用程序:在这里,我们使用 AJAX 在客户端和服务器之间传递消息。聊天窗口会定期联系服务器,以发送用户键入的任何消息,并从服务器检索新发布的消息。
- 选择颜色的代码:在这里,我们使用 AJAX 调用可以告诉我们用户在颜色图像上单击的颜色的 PHP 脚本。我们使用包含整个颜色范围的调色板,允许用户选择其编写文本的颜色。单击调色板时,鼠标坐标会发送到服务器,服务器会获取颜色代码。
如果您仔细分析一下代码,细节就会变得清晰。一切都从 *index.html* 开始。*index.html* 中真正有趣的部分是可以在 DHTML 中实现的滚动区域。有关滚动的少量信息可以在 此处找到。
基本上,拥有一个带有滚动条的页面部分的想法是使用嵌套层。在我们的示例中,`div
` scroll 及其内部层可以起到作用。外部层是 scroll。它具有固定的宽度和高度,其中最有用的属性是 overflow。通常,块级框的内容被限制在框的内容边缘。在某些情况下,一个框可能会溢出,这意味着其内容部分或全部位于框外部。在 CSS 中,此属性指定当元素溢出其区域时会发生什么。有关更多详细信息,请参阅 overflow 的规范。
chat.js 脚本包含我们应用程序的 JavaScript 部分。该文件可以分为两部分:一部分处理颜色选择,另一部分处理检索和发送聊天消息。
我们将从颜色选择功能开始。这部分起初可能看起来相当困难,但事实证明很容易实现。让我们对整个过程进行全景视图。
我们有一个调色板图像,其中包含整个可见颜色光谱。PHP 有两个函数可以帮助我们查找所选颜色的 RGB 代码:`imagecreatefrompng` 和 `imagecolorat`。这两个函数允许我们根据图像中的 x 和 y 位置获取像素的 RGB 代码。像素位置在 JavaScript 代码的 `getMouseXY` 函数中检索。
`getColor` 函数检索用户单击调色板图像时选择的颜色的 RGB 代码。首先,它从事件中检索鼠标坐标。然后,它计算点击事件在图像内产生的坐标作为相对值。利用此信息,`getColor` 会启动一个异步请求到 `get_color.php` 脚本,该脚本应返回与单击坐标关联的颜色代码。当请求状态发生变化时,将执行回调函数 `handleGettingColor`。
当请求完成并且没有发生错误时,`handleGettingColor` 函数会检查。然后调用 `changeColor` 函数。此函数使用给定的代码更改示例文本“text will look like this”的颜色。
现在,让我们看看聊天是如何工作的。默认情况下,当页面初始化并且 `OnBlur` 事件发生时,会调用 `checkUsername` 函数。此函数通过生成任意用户名来确保用户名的非空。
按下发送按钮时,会调用 `sendMessage` 函数。此函数将当前消息添加到要发送到服务器的消息队列中。在将其添加到队列之前,该函数通过调用 `trim` 函数来修剪消息,并通过调用 `encodeURIComponent` 对消息进行编码,以确保其成功传输。
每当发生 `keydown` 事件时,都会调用 `handleKey` 函数。按下 Enter 键时,会调用 `sendMessage` 函数,因此按下发送按钮和在 `MessageBox` 控件中按 Enter 键具有相同的效果。
`deleteMessages` 函数会将删除消息添加到要发送到服务器的消息中。
`requestNewMessages` 函数负责发送聊天消息。它从队列中检索消息并将其发送到服务器。HTTP 请求对象的更改状态由 `handleReceivingMessages` 函数处理。
`handleReceivingMessages` 检查请求是否已完成,如果没有发生错误,则调用 `readMessages` 函数。
`readMessages` 函数检查是否有人删除了所有聊天消息,如果是,则客户端的聊天窗口也会被清空。为了将新消息追加到聊天中,我们调用 `displayMessages` 函数。此函数将与新消息对应的数组作为参数。它将新消息组合为 HTML,并通过调用 `displayMessage` 函数将它们追加到已有的聊天消息中。开始时,`displayMessage` 函数会检查滚动条是否位于消息列表的底部。这对于在函数末尾将其重新定位以便将焦点放在最后的新消息上是必要的。
最后一个函数是 `init` 函数。它的作用是检索聊天消息,确保用户名不为 null,将文本颜色设置为黑色,并关闭自动完成功能。
对于错误处理部分,我们使用 `displayError` 函数,该函数依次调用 `displayMessage` 函数,并将错误消息作为参数。
让我们通过首先介绍 *chat.php* 文件来转向应用程序的服务器端。服务器处理客户端的请求,如下所示:
- 检索客户端参数。
- 识别需要执行的操作。
- 执行必要的操作。
- 将结果发送回客户端。
请求包括 `mode` 参数,该参数指定服务器要执行的以下操作之一:
- `SendAndRetrieve`:首先,将新消息插入数据库,然后检索所有新消息并将其发送回客户端。
- `DeleteAndRetrieve`:删除所有消息,然后检索可能存在的任何新消息并将其发送回客户端。
- `Retrieve`:检索新消息并将其发送回客户端。
chat.php 背后的业务逻辑位于 *chat.class.php* 脚本中,该脚本包含 `Chat` 类。
`deleteMessages` 方法截断数据表,删除所有信息。
`postMessages` 方法将所有新消息插入数据库。
`isDatabaseCleared` 方法检查是否已删除所有消息。基本上,通过提供从服务器检索的最后一条消息的 ID,并检查它是否存在,我们可以检测是否已删除所有消息。
`retrieveNewMessages` 方法获取自上次请求以来(如果存在上次请求;否则是所有消息)检索的最后一条消息以来的所有新消息,并调用 `isDatabaseCleared` 方法检查数据库是否已清空。此函数组合给客户端的响应并将其发送。
config.php 文件包含数据库配置参数,*error_handler.php* 文件包含错误处理模块。
摘要
在本章开头,我们探讨了为什么在 Internet 上以动态方式与他人交流时会遇到问题。我们看到了这些问题的解决方案以及 AJAX 聊天解决方案如何带来新的、有用的、符合人体工程学的特性。在看到一些其他 AJAX 聊天实现后,我们开始构建自己的解决方案。一步一步,我们实现了我们的 AJAX 聊天解决方案,使其保持简单、易于扩展且模块化。
阅读完本章后,您可以尝试改进解决方案,添加新功能,例如:
- 聊天室
- 简单的命令行(加入/离开聊天室,在聊天室之间切换)
- 私聊
AJAX 与 PHP:构建响应式 Web 应用程序
AJAX 是一种复杂的现象,对不同的人意味着不同的东西。计算机用户欣赏他们喜欢的网站现在更加友好,并且响应更灵敏。Web 开发人员学习新技能,使他们能够轻松创建简洁的 Web 应用程序。确实,AJAX 的一切听起来都不错!
AJAX 的根源是各种技术的混合,它使您摆脱了讨厌的页面重新加载,页面重新加载是在页面之间导航时出现的死寂时间。消除页面重新加载只是启用网站上更复杂功能的一步之遥,例如实时数据验证、拖放以及传统上不与 Web 应用程序相关联的其他任务。尽管 AJAX 的要素已经成熟(`XMLHttpRequest` 对象是 AJAX 的核心,由 Microsoft 于 1999 年创建),但它们在新一轮 Web 趋势中的新作用还很年轻,我们将见证许多变化,然后才能正确地利用这些技术为最终用户带来最大的利益。在撰写本书时,“AJAX”这个名字大约只有一岁。
AJAX 当然不是 Web 所有问题的答案,就像围绕它的当前炒作所暗示的那样。与任何其他技术一样,AJAX 也可能被过度使用或用错。AJAX 也带来自己的问题:您需要与浏览器不兼容性作斗争,没有 JavaScript 的浏览器不支持 AJAX 特定的页面,用户无法轻松地将其加入书签,搜索引擎也不总是知道如何解析它们。此外,并非所有人都喜欢 AJAX。有些人正在使用 JavaScript 开发企业架构,而另一些人则根本不想使用它。当炒作结束后,大多数人可能会同意,对于大多数场景来说,折衷的方法是最明智的选择。
在《AJAX 与 PHP:构建响应式 Web 应用程序》一书中,我们采取了一种务实而安全的方法,教授了我们认为任何 Web 开发人员迟早都需要掌握的相关模式和最佳实践。我们教您如何避免常见陷阱,如何编写高效的 AJAX 代码,以及如何实现易于集成到当前和未来 Web 应用程序的功能,而无需您围绕 AJAX 重建整个解决方案。您将能够立即将从本书中学到的知识应用到您的 PHP Web 应用程序中。