本书第五篇所介绍的任何项目都不是支持Ajax的。这些项目都包含了一系列的表单提交以及页面的重新载入。尽管页面包含动态元素,但是没有一个提供了Web 2.0应用带来的流畅的用户体验。
当然,在这些项目中添加Ajax元素可能会将重点从使用PHP和MySQL创建Web应用转移到Ajax。换句话说,必须先学会走,才能开始学跑。但是,既然已经知道如何跑(或者能蹦一点)可以开始考虑修改这些应用的元素来引入Ajax,或者开始考虑在未来将要创建的新应用引入Ajax元素。
Ajax开发人员的思考过程大概是这样的:什么是独有的用户操作,什么样的页面动作将调用这些操作?例如,是否期望用户通过点击一个按钮元素提交一个表单,然后再继续下一步操作,或者是否可以通过改变一个表单元素(文本输入框,单选框或复选框)的焦点来调用一个发送给服务器的异步请求?在确定了需要包括的动作类型后,可以编写这些调用PHP脚本的JavaScript函数,而PHP脚本将处理这些发送给服务器的请求或来自服务器的结果。
接下来的内容将介绍如何在本书前面章节创建的脚本中添加Ajax元素。
在PHPBookmark应用中添加Ajax元素
在第27章中,创建了一个名为PHPbookmark应用。该应用要求用户在保存书签并查看其他用户所保存的书签推荐之前进行用户注册和登录。
由于这个应用已经创建并且由一些联系紧密的PHP脚本和函数库组成,要做的第一步就是考虑如何在已有结构中添加额外的文件——是否某些文件是样式单、JavaScript函数或者处理服务器端动作的PHP脚本。回答是非常简单的:为样式单创建一个单独文件,为所有的JavaScript函数创建一个单独文件。然后,在第27章的已有PHP脚本添加一些代码来引入这两个外部文件,如果必要,也可以直接加入JavaScript函数本身的调用。任何新创建的PHP脚本将区别于已有的应用文件,单独存在。
在确定如何管理新创建的文件后,可以开始考虑哪些用户函数需要支持Ajax。尽管该应用的用户注册和登录是支持Ajax的主要部分,但是这里,主要将围绕添加和编辑用户保存的书签部分转换成支持Ajax的部分进行介绍。
此外,还需要对已有的应用文件进行修改。将第27章的应用文件复制到一个新目录是必要的,这样可以方便本章的使用。本章所做的任何修改都将上传到这个目录,而不是PHPBookmark应用的工作目录中。
注意 如果要将应用的用户注册部分支持Ajax,可以通过也能够调用PHP脚本的JavaScript函数来验证用户的电子邮件以及用户名称是否已经存在于系统中。如果已经存在,该函数将显示一个错误信息,因此在这些错误被修改之前,不会继续注册的提交过程。
1.创建额外的文件
正如前面提到的,我们将在已有的应用结构中添加新的文件。尽管在接下来的内容会逐步将新加文件添加到应用中,但记住这一点也是有必要的。
假设至少有两个新文件:一个样式单和一个JavaScript函数库。现在先创建两个名为new_ss.css和new_ajax.js的新文件。由于新样式单还没有定义任何新样式,因此该文件为空。而new_ajax.js文件包含了本章前面所创建的getXMLHTTPRequest函数,该函数可以在所有浏览器中创建一个XMLHTTPRequest对象实例。虽然还要在该文件添加更多的代码,但是现在就可以将该脚本上传到Web服务器。
接下来需要在PHPBookmark应用的某一个已有的显示函数中添加这两个文件的链接。这样就可以像JavaScript函数库中的函数一样,确保样式单中的样式定义是可以使用的。如果回忆第27章内容,控制head元素输出的函数名为do_html_header,它位于output_fns.php文件中。该函数的新版本如程序清单34-4所示。
程序清单34-4 修改后的do_htmp_header函数包含了指向新样式单和JavaScript函数库的链接
function do_html_header($title){
//print an HTML header
?>
<html>
<head>
<title><?php echo$title;?></title>
<style>
body{font-family:Arial,Helvetica,sans-serif;font-size:13px;}
li,td{font-family:Arial,Helvetica,sans-serif;font-size:13px;}
hr{color:#3333cc;}
a{color:#000000;}
</style>
<link rel="stylesheet"type="text/css"href="new_ss.css"/>
<script src="new_ajax.js"type="text/javascript"></script>
</head>
<body>
<img src="bookmark.gif"border="0"
v/>
<h1>PHPbookmark</h1>
<hr/>
<?php
if($title){
do_html_heading($title);
}
}
在上载了新的样式单文件,JavaScript函数库,output_fns.php文件以及在PHPBookmark系统中打开任何页面,这些新文件都应该被正确引入,没有任何错误。接下来,将在这两个文件分别添加新的样式单和脚本,并创建一些Ajax功能。
2.以Ajax方式添加书签
目前,添加书签的操作出现在用户输入书签URL并点击表单提交按钮。点击表单提交按钮的动作将调用另一个PHP脚本,该脚本将添加书签,返回用户的书签列表,并且显示新添加的书签。换句话说,页面将被重新载入。而Ajax方式是给出一个添加书签的表单,通过后台调用一个JavaScript函数来调用PHP脚本,从而将该书签添加在数据库中并且将响应返回给用户,它不需要重新载入页面的表单提交按钮————所有这些操作都不需要离开已经被载入的页面。这个新函数将请求output_fns.php脚本中的display_add_bm_form函数。
程序清单34-5所示的是修改后的函数。这里,删除了表单动作,添加了一个id为文本输入框的元素,以及修改了按钮元素的属性。此外,还添加了对getXMLHTTReuquest函数的调用。
程序清单34-5 修改后的display_add_bm_form函数
function display_add_bm_form{
//display the form for people to enter a new bookmark in
?>
<script type="text/javascript">
var myReq=getXMLHTTPRequest;
</script>
<form>
<table cellpadding="2"cellspacing="0"bgcolor="#cccccc">
<tr><td>New BM:</td>
<td><input type="text"name="new_url"name="new_url"
size="30"maxlength="255"/></td></tr>
<tr><td colspan="2">
<input type="button"
onClick="javascript:addNewBookmark;"/></td></tr>
</table>
</form>
<?php
}
请仔细查看按钮元素,如下所示:
<input type="button"
onClick="javascript:addNewBookmark;"/>
当该按钮被点击,onClick事件处理器将调用addNewBookmark函数。该函数将向服务器发送一个请求,具体的,它将向尝试在数据库里插入一条记录的PHP脚本发送请求。该函数代码如程序清单34-6所示。
程序清单34-6 addNewBookmark函数
function addNewBookmark{
var url="add_bms.php";
var params="new_url="+encodeURI(document.getElementById('new_url').value);
myReq.open("POST",url,true);
myReq.setRequestHeader("Content-type","application/x-www-form-urlencoded");
myReq.setRequestHeader("Content-length",params.length);
myReq.setRequestHeader("Connection","close");
myReq.onreadystatechange=addBMResponse;
myReq.send(params);
}
以上函数类似于本章前面使用过的getServerTime函数。其逻辑非常类似:创建变量,向PHP脚本发送数据以及调用函数处理服务器端的响应。
如下所示代码根据表单域的名称和用户输入值创建了一个名称/值对:
var params="new_url="+encodeURI(document.getElementById('new_url').value);
函数的最后一行代码将params参数值发送给后台的PHP脚本,如下所示:
myReq.send(params);
在发送之前,还将发送3个请求报头,这样服务器端就知道如何处理POST请求中的数据,如下所示:
myReq.setRequestHeader("Content-type","application/x-www-form-urlencoded");
myReq.setRequestHeader("Content-length",params.length);
myReq.setRequestHeader("Connection","close");
接下来就是创建JavaScript函数来处理服务器响应;这里定义为addBMResponse。
myReq.onreadystatechange=addBMResponse;
同样的,以上代码类似于本章前面介绍过的theHTTPResponse函数。程序清单34-7所示的是该函数代码。
程序清单34-7 addBMResponse函数代码
function addBMResponse{
if(myReq.readyState==4){
if(myReq.status==200){
result=myReq.responseText;
document.getElementById('displayresult').innerHTML=result;
}else{
alert('There was a problem with the request.');
}
}
}
该函数将首先检查对象的状态;如果请求已经完成,将检查来自服务器端的响应码是否是200(OK)。如果响应码不是200,函数将给出一个警告:“There was a problem with the request(该请求存在问题)”。任何其他的响应都将来自add_bms.php脚本的执行,并且将现在id值为displayresult的p元素中。这里,displayresult定义在new_ss.css样式单中,如下所示(白色背景):
#displayresult{
background:#fff;
}
在PHP函数display_add_bm_form的form标记结束前,添加了如下所示的代码,该代码将显示用户将看到的服务器端结果:
<p></p>
接下来,将对已有代码add_bms.php进行修改。
3.对已有代码进行修改
如果希望通过不修改add_bms.php脚本来添加书签,检查用户权限和添加书签的操作就足够了。然而,这样操作所返回的结果可能会不好理解,如图34-4所示。图中的标题和logo以及脚注链接都出现了两次,同时该应用的显示也出现一些问题。
图 34-4 在修改add_bms.php脚本前添加书签在PHPBookmark应用的非Ajax版本中,该表单只是一个页面,提交后的返回结果是另一个页面,而且所有页面元素都将实时的重新载入。然而,在支持Ajax的环境中,添加新书签,获得服务器结果以及是否继续添加新书签都不需要重新载入任何页面元素。这个新功能需要对add_bms.php代码进行一定的修改。最初的代码如程序清单34-8所示。
程序清单34-8 最初的add_bms.php代码
<?php
require_once('bookmark_fns.php');
session_start;
//create short variable name
$new_url=$_POST['new_url'];
do_html_header('Adding bookmarks');
try{
check_valid_user;
if(!filled_out($_POST)){
throw new Exception('Form not completely filled out.');
}
//check URL format
if(strstr($new_url,'http://')===false){
$new_url='http://'.$new_url;
}
//check URL is valid
if(!(@fopen($new_url,'r'))){
throw new Exception('Not a valid URL.');
}
//try to add bm
add_bm($new_url);
echo'Bookmark added.';
//get the bookmarks this user has saved
if($url_array=get_user_urls($_SESSION['valid_user'])){
display_user_urls($url_array);
}
}
catch(Exception$e){
echo$e->getMessage;
}
display_user_menu;
do_html_footer;
?>
该代码的第一行引入了所需的bookmark_fns.php文件。如果查看bookmark_fns.php文件代码,该文件还将调用其他文件:
<?php
//We can include this file in all our files
//this way,every file will contain all our functions and exceptions
require_once('data_valid_fns.php');
require_once('db_fns.php');
require_once('user_auth_fns.php');
require_once('output_fns.php');
require_once('url_fns.php');
?>
在支持Ajax的环境中,添加书签的操作可能不需要(也可能需要)这些所有引入文件,但就像该文件头的注释提到——每一个文件都包含了所有注释和异常。在这种情况下,由于应用架构由一系列的动态页面转移到支持Ajax的功能环境,在确认不需要某些代码功能之前,最好还是保留这些额外的元素。请保留add_bms.php的第一行代码。
第二行代码将开始或者继续一个用户会话,同样应该保留;即使是在支持Ajax的环境中,这个动作对安全的完整性是有意义的。同样的,第三行代码也可以保留。它给出了在请求发送中POST参数值,如下所示:
$new_url=$_POST['new_url'];
最后,可以删除如下所示的代码行:
do_html_header('Adding bookmarks');
由于已经正在浏览包含HTML报头信息的页面(add_bm_form.php),就不需要重复载入该页面了——不需要转移到其他页面。这种重复将产生两套页面标题和Logo图像,正如图34-4所示。类似的原因,也可以删除add_bm_form.php最后两行的代码,如下所示:
display_user_menu;
do_html_footer;
如果删除这些元素,上载文件到服务器以及添加其他书签,其结果都会是期望的,当然还是需要修改一些脚本。图34-5所示的就是对脚本修改后的应用运行的结果。
图 34-5 修改add_bms.php脚本后添加书签的运行结果在结果页面上,用户登录状态信息仍然是重复的,但这个问题已经没有以前明显了。接下来要删除这些重复的信息并且修改一些与异常处理相关的功能,这些功能在支持Ajax的环境中才有真正的意义。
要删除用户登录的重复信息,可以删除add_bms.php脚本中的如下代码行:
check_valid_user;
有效用户的检查已经在add_bms_form.php页面载入时执行;当进入这个已经调用了Ajax函数的页面时,就不用再担心用户是否有效。
接下来删除try语句块和异常处理。删除它们的原因是希望脚本结束运行时能够将已经保存的URL列表显示给用户。这就意味着某些修改和调整可能会引入一些新的错误信息文本。程序清单34-9所示的是修改后的add_bms.php代码。
程序清单34-9 修改后的add_bms.php代码
<?php
require_once('bookmark_fns.php');
session_start;
//create short variable name
$new_url=$_POST['new_url'];
//check that form has been completed
if(!filled_out($_POST)){
//has not
echo"<p class=/"warn/">Form not completely filled out.</p>";
}else{
//has;check and fix URL format if necessary
if(strstr($new_url,'http://')===false){
$new_url='http://'.$new_url;
}
//continue on to check URL is valid
if(!(@fopen($new_url,'r'))){
echo"<p class=/"warn/">Not a valid URL.</p>";
}else{
//it is valid,so continue to add it
add_bm($new_url);
echo"<p>Bookmark added.</p>";
}
}
//regardless of the status of the current request
//get the bookmarks this user has already saved
if($url_array=get_user_urls($_SESSION['valid_user'])){
display_user_urls($url_array);
}
?>
以上代码仍然延续了可能事件的逻辑顺序,但是能够显示适当的错误信息,不会显示任何重复的页面元素。
以上代码首先将检查表单本身是否已填写。如果没有完全填写,将在书签添加表单和用户已有书签列表之间显示错误信息。如图34-6所示的就是这个响应。
图 34-6 尝试添加一个空值其次,以上代码将检查URL格式是否正确;如果不正确,该URL字符串将被转换成正确的URL格式并继续执行。接下来,将打开一个Socket测试该URL是否有效。如果测试失败,将在书签添加表单和用户已有书签列表之间显示错误信息。然而,如果URL是有效的,它将被添加到用户已有书签列表。如图34-7所示的是添加一个无效URL时,应用给出的响应信息。
图 34-7 尝试添加一个无效URL最后,无论添加URL至用户已有书签列表是否存在错误,都将显示用户的已有书签列表。如图34-8所示的是该结果。
图 34-8 成功——添加了一个有效的URL尽管添加书签这个核心功能已经成功地支持Ajax,还需要对一些元素进行修改。例如,在新系统中,ulr_fns.php脚本的add_bm函数包含的一些异常处理可能会导致错误信息。程序清单34-10所示的是已有的add_bm函数。
程序清单34-10 ulr_fns.php脚本中已有的add_bm函数代码
function add_bm($new_url){
//Add new bookmark to the database
echo"Attempting to add".htmlspecialchars($new_url)."<br/>";
$valid_user=$_SESSION['valid_user'];
$conn=db_connect;
//check not a repeat bookmark
$result=$conn->query("select*from bookmark
where username='$valid_user'
and bm_URL='".$new_url."'");
if($result&&($result->num_rows>0)){
throw new Exception('Bookmark already exists.');
}
//insert the new bookmark
if(!$conn->query("insert into bookmark values
('".$valid_user."','".$new_url."')")){
throw new Exception('Bookmark could not be inserted.');
}
return true;
}
在这里,我们希望能够将异常转换成错误信息,并且继续代码的执行(显示)。这可以通过修改两个if语句块实现:
if($result&&($result->num_rows>0)){
echo"<p class=/"warn/">Bookmark already exists.</p>";
}else{
//attempt to add
if(!$conn->query("insert into bookmark values
('".$valid_user."','".$new_url."')")){
echo"<p class=/"warn/">Bookmark could not be inserted.</p>";
}else{
echo"<p>Bookmark added.</p>";
}
}
以上代码仍然延续了可能事件的逻辑顺序,但是能够显示适当的错误信息。在检查要添加的URL是否已经存在于用户已有书签列表后,或者在书签添加表单和用户已有书签列表之间给出一个错误信息,或者脚本尝试添加这个URL。
如果不能添加这个书签,在书签添加表单和用户已有书签列表之间还将显示一个错误信息。但是,如果成功添加该URL后,将显示“Bookmark Added(已添加书签)”信息。从add_bm.php脚本删除的echo语句被添加到add_bm函数中,否则用户还将看到一个错误信息,例如,在“Bookmark could not be inserted(不能插入书签)”消息之后还将显示“Bookmark Added(已添加书签)”的成功消息,而事实的输出确实是不正确的。
如图34-9所示的就是这些修改的结果。
图 34-9 尝试添加一个已存在的书签4.对PHPBookmark应用的额外修改
将书签添加功能修改为支持Ajax的用户界面只是要对这个应用进行的许多修改之一。接下来的选择可能就是书签删除功能。书签删除的操作如下所示:
■从页面脚注删除“Delete BM(删除书签)”链接。
■当用户选中某个书签左边的“Delete?(是否删除)”复选框时,调用一个JavaScript函数。
■修改delete_bm.php脚本,这样新的JavaScript函数就可以调用这个脚本完成删除操作并向用户返回信息。
■对功能进行必要的修改,确保操作能够合理完成,并且在新的用户界面显示正确的信息。
有了修改的结构,就可以根据本章所介绍的内容实现这些修改。接下来的内容将提供一些资源链接,这些链接包含了关于创建支持Ajax站点的更多更详细信息。
请记住,Ajax是一系列技术的组合,用于创建更流畅的用户体验;在了解了Ajax这些组成部分及其功能后,这也让重新思考一个应用的架构成为必要。