AJAX in CakePHP
Cake 中的 AJAX 实质
CakePHP(以下简称“Cake”,本文使用的版本是 0.10.7.1856 RC3)对 AJAX 的支持是建立在 prototype 与 script.aculo.us 之上的,其自身并没有包含服务器端、客户端的 AJAX 实现,所以想要在 Cake 中熟练地使用 AJAX,必须首先熟悉 prototype 和 script.aculo.us,而本文的重点并不是这两个出众的 JavaScript 库。说白了,Cake 实际上只是简化了繁琐的 AJAX JavaScript 代码。
Cake 中的 AJAX 相关文件
在 Cake 中,与 AJAX 相关的文件只有两个:
- cake/cake/libs/view/templates/layouts/ajax.thtml
- cake/cake/libs/view/helpers/ajax.php
ajax.thtml 文件是执行 AJAX 动作之后用于输出的布局,和一般的布局文件不同的是它是一个空的布局视图文件,没有 header/footer 等等内容。ajax.php 文件则是用于 Cake 视图文件的 AJAX 辅助类 AjaxHelper,此类中包含了很多 AJAX 动作的相关方法,详细 API 请参考 http://api.cakephp.org/class_ajax_helper.html。
除了上述的两个文件外,我们还需要 Prototype 的 prototype.js 和 script.aculo.us 中的 *.js 文件,这些文件可在官方网站上下载到,将这些 *.js 文件放置 cake/app/webroot/js/ 目录下即可。对于这两个库,本文用的版本分别是 1.4.0 和 1.5.1。
Hello, AJAX world!
现在我们使用一个简单的示例简单演示一下如何在 Cake 中使用 AJAX。这个示例将实现点击链接之后,在页面上加载服务器端输出的“Hello, AJAX world!”信息。为了简单起见,示例中不使用任何数据库,也就是不用 Cake 的模型(Model),而只用控制器(Controller)和视图(View)。首先创建一个布局视图文件 cake/app/views/layouts/demo.thtml,用于自定义布局以及加载需要用到的 JavaScript 文件,此文件内容如下:
<!DOCTYPEhtmlPUBLIC”-//W3C//DTDXHTML1.0Transitional//EN” ”http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd”> <htmlxmlns=”http://www.w3.org/1999/xhtml”> <head> <title>CakePHPAJAXDemo::<?phpecho$title_for_layout?></title> <?phpecho$html->charsetTag(‘UTF-8′)?> <?phpecho$javascript->link(‘prototype’)?> <?phpecho$javascript->link(’scriptaculous’)?> </head> <body> <divid=”container”> <center><h2>CakePHPAJAXDemo</h2></center> <divid=”content”><?phpecho$content_for_layout?></div> </div> </body> </html>
重点在于 $javascript->link() 的那两行,包含了需要用到的 JavaScript 文件 prototype.js 和 scriptaculous.js,而 script.aculo.us 库中的其他 JavaScript 文件都由 scriptaculous.js 统一进行包含,不用我们书写额外的代码,如果并不想使用 script.aculo.us 的所有 js 文件,可以使用 load 参数进行指定包含,比如我们只用到 effects.js,可以这样(多个则用英文逗号“,”分割开):
<?phpecho$javascript->link(’scriptaculous.js?load=effects’)?>
接下来是创建一个控制器文件 cake/app/controllers/demos_controller.php:
<?php classDemosControllerextendsAppController { var$name=‘Demos’;//兼容PHP4 var$layout=‘demo’;//指定视图使用的布局为demo.thtml var$helpers=array(‘Html’,‘Javascript’,‘Ajax’);//需要用到的视图辅助类 functionindex() { } functionhello() { sleep(1);//本地测试时,为了更好地看到效果,模拟延迟状态 $this->layout=‘ajax’;//此方法为AJAX动作,所以布局需要使用ajax.thtml } }///:~ ?>
此控制器的重点有三个:
- 设置默认布局为 demo.thtml,这样才能在 index 视图中使用 prototype.js 等
- 在视图辅助类数组 $helpers 中加上“Ajax”辅助类
- 对于 AJAX 动作方法 hello() 需要将其当前布局设置为“ajax”
对应于控制器中的两个方法(Action),我们需要在 cake/app/views/demos/ 目录下创建两个视图文件:index.thtml 和 hello.thtml。
index.thtml:
<divid=”loading”style=”display:none;padding:4px;color:black; background-color:#FAD163;width:100px”><strong>Loading…</strong></div> <divid=”view”style=”display:none;background-color:#E8EEF7; padding:4px;border:1pxsolidsilver;width:300px”></div> <p> <?php //设置AJAX选项 $options=array( //设置加载成功之后需要进行更新的元素为view ‘update’=>‘view’, //加载过程中隐藏view元素,显示“Loading…”字样 ‘loading’=>“Element.hide(’view’);Element.show(’loading’)”, //加载成功之后隐藏loading,同时显示view元素 ‘complete’=>“Element.hide(’loading’);Effect.Appear(’view’)” ); //使用AjaxHelper创建AJAX动作链接 echo$ajax->link(‘Clickhere!’,‘/demos/hello’,$options); ?> </p>
这个视图中有三个元素:loading、view 与 AJAX 链接。初始状态下 loading 与 view 是隐藏的(display:none),只有点击了 AJAX 链接之后,在加载状态中显示 loading,加载完成之后将其隐藏,然后显示 view,“Hello, AJAX world!”即显示在 view 中。此视图的重点在于 $ajax->link(),$ajax 是 AjaxHelper 的对象实例,link() 方法的第一个参数是链接显示的文本,第二个参数是 Cake 的 URL,这里的 URL 为 /demos/hello,指向了 AJAX 动作方法 hello(),此动作最终输出视图 hello.thtml,第三个参数为 AJAX 选项,Cake 会自动根据选项生成链接中使用到的 JavaScript 代码。对于一个简单的 AJAX 动作,主要就是设置三个选项:update、loading 与 complete,这几个选项的意义在视图代码的注释中都有了详细说明。
最后就是 hello.thtml 视图文件了,只是一行简单的文本:
<center>Hello,AJAXworld!</center>
OK,通过尽量少的编码,我们完成了这个示例,可以通过 http://www.somesite.com/cake/demos/ 浏览最终效果。
Live Search
Live Search 指的是即时查询,通常是用户在文本框中输入想要查询的关键字,由客户端 JavaScript 对文本框进行观察,监测到用户输入之后即时提交到服务器,并显示服务器返回的结果。接下来我们将使用 Cake 的 AJAX 实现 Live Search,从一个数组中获取符合查询关键字的数据,然后即时更新到页面中。
首先修改我们的控制器 demos_controller.php,增加一个方法 search():
<?php functionsearch() { $langs=array( ‘C’,‘C++’,‘C#’, ‘Java’,‘JavaScript’, ‘PHP’,‘Perl’,‘Python’, ‘Ruby’,‘Delphi’); $this->layout=‘ajax’; if(empty($this->params[‘form’][‘livesearch’])){//未提交任何数据 $result=$langs; }else{//根据提交的关键字进行查询 $word=$this->params[‘form’][‘livesearch’]; $result=array(); foreach($langsas$lang) if(stristr($lang,$word)!==false) $result[]=$lang; } $this->set(‘result’,$result); } ?>
在 index.thtml 视图文件中,加入 Live Search 的表单代码:
<formonsubmit=”returnfalse”> <p> <b>LiveSearch:</b> <inputtype=”text”name=”livesearch”id=”livesearch”/> </p> </form> <?php //设置AJAX选项 $options=array( ‘update’=>‘view’, //处理查询的URL,对应于控制器中的search()方法 ‘url’=>‘/demos/search’, //观察频率,单位为“秒” ‘frequency’=>1, ‘loading’=>“Element.hide(’view’);Element.show(’loading’)”, ‘complete’=>“Element.hide(’loading’);Effect.Appear(’view’)” ); echo$ajax->observeField(‘livesearch’,$options); ?>
这里用到了 AjaxHelper 中的另一个方法 observeField(),此方法用于观察某个元素的数据是否发生变化,发生变化时则调用相应的 AJAX 操作。方法的第一个参数为需要观察的元素 id,这里是“livesearch”文本框,第二个参数为 AJAX 选项,和 link() 方法中的相似,只不过这里的选项设置多了 url 与 frequency,url 即 Cake 的 URL,对应于控制器中的方法,frequency 则是观察的间隔时间,单位为“秒”,即每隔多少秒查看一下对应的元素是否发生了改变。
对应于 search() 方法,创建视图文件 cake/app/views/demos/search.thtml:
<?phpif(count($result)>0):?> <ul> <?phpforeach($resultas$lang):?> <li><?phpecho$lang?></li> <?phpendforeach?> </ul> <?phpelse:?> <fontcolor=”gray”>Foundnothing!</font> <?phpendif?>
好了,这个 Live Search 已经完成了,是不是很简单 现在只要在文本框中输入字符,就会在 $langs 数组中查找,只要包含了查询关键字的结果都会被返回并更新到 view 元素中。
其他应用
Cake 的 AjaxHelper 中还提供了很多方法,如用于拖曳的 drag()、drop() 及 dropRemote();用于排序的 sortable();用于自动完成的 autoComplete() 等等,由于涉及到的讲解篇幅比较大,暂时就不深入了,有时间的话我再一一道来。drag/drop 可以在我写的 Cake Framework AJAX Demo 中看到在线演示,并可下载到源代码。
一些问题
Cake 当前版本(0.10.7.1856 RC3)的一些方法还不是非常完善,对于最新版本的 script.aculo.us 库中的一些参数还不支持,相信很快会得到更新。实际应用中可能会碰到中文乱码的问题,那是因为 XMLHttpRequest 获取到的数据都是以 UTF-8 编码的,所以解决的办法有两个,一个是所有页面、数据库数据均使用 UTF-8 编码,这样可以省去很多麻烦,另外一个就是通过 PHP 的 iconv() 函数进行转码,但这需要用到 iconv 扩展,所以比较麻烦一些,而且无形中为服务器增加了额外的负担。