AJAX in CakePHP (1) - Getting Started
肖理达 (KrazyNio AT hotmail.com), 2006.01.24, 转载请注明出处
一、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 文件,此文件内容如下:
<!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">
<head>
<title>CakePHP AJAX Demo :: <?php echo $title_for_layout?></title>
<?php echo $html->charsetTag('UTF-8')?>
<?php echo $javascript->link('prototype')?>
<?php echo $javascript->link('scriptaculous')?>
</head>
<body>
<div id="container">
<center><h2>CakePHP AJAX Demo</h2></center>
<div id="content"><?php echo $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,可以这样(多个则用英文逗号“,”分割开):
<?php echo $javascript->link('scriptaculous.js?load=effects')?>
接下来是创建一个控制器文件 cake/app/controllers/demos_controller.php:
<?php
class DemosController extends AppController
{
var $name = 'Demos'; //兼容 PHP4
var $layout = 'demo'; //指定视图使用的布局为 demo.thtml
var $helpers = array('Html', 'Javascript', 'Ajax'); //需要用到的视图辅助类
function index()
{
}
function hello()
{
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:
<div id="loading" style="display:none;padding:4px;color:black;
background-color:#FAD163;width:100px"><strong>Loading...</strong></div>
<div id="view" style="display:none;background-color:#E8EEF7;
padding:4px;border:1px solid silver;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('Click here!', '/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, AJAX world!</center>
OK,通过尽量少的编码,我们完成了这个示例,可以通过 http://www.somesite.com/cake/demos/ 浏览最终效果。
四、Live Search
Live Search 指的是即时查询,通常是用户在文本框中输入想要查询的关键字,由客户端 JavaScript 对文本框进行观察,监测到用户输入之后即时提交到服务器,并显示服务器返回的结果。接下来我们将使用 Cake 的 AJAX 实现 Live Search,从一个数组中获取符合查询关键字的数据,然后即时更新到页面中。
首先修改我们的控制器 demos_controller.php,增加一个方法 search():
<?php
function search()
{
$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 ($langs as $lang)
if (stristr($lang, $word) !== false)
$result[] = $lang;
}
$this->set('result', $result);
}
?>
在 index.thtml 视图文件中,加入 Live Search 的表单代码:
<form onsubmit="return false">
<p>
<b>Live Search:</b>
<input type="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:
<?php if (count($result) > 0): ?>
<ul>
<?php foreach ($result as $lang): ?>
<li><?php echo $lang ?></li>
<?php endforeach ?>
</ul>
<?php else: ?>
<font color="gray">Found nothing!</font>
<?php endif ?>
好了,这个 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 扩展,所以比较麻烦一些,而且无形中为服务器增加了额外的负担。
七、相关资源
- Prototype
- script.aculo.us
- Cake AjaxHelper Api
- Cake Framework AJAX Demo
- CakePHP Ajax Helper
- Using Ajax (and company) with Cake
- AJAX in CakePHP 示例源代码
Update@2006.11.26: 有兴趣的同学可以继续阅读本序列文章的第二篇《AJAX in CakePHP (2) - Sortable》。

Do you intend to do an english translation of this tutorial?
Comment by Daniel Hofstetter — January 25, 2006 @ 6:43 pm
Hi Nio,
Do you have any english translation ?
Comment by ozly — January 26, 2006 @ 9:24 am
Here is a way to translate this
– goto http://babelfish.altavista.com/tr. enter nio’s weblog url and then choose chinese-simp english and finally click translate
i just hope you can understand ‘baroque english’ LOL..
hope an english version coming soon..
Comment by dane — May 5, 2006 @ 8:07 pm
Hola Nio, excellente ejemplo… aunque lo leí con babelfish, en “inglés barroco”, pero estuvo muy bien explicado. Gracias por esta ayuda… fue de mucha ayuda para mí para comprender cómo utilizar ajax con cakephp. Saludos desde Perú.
Hi Nio, excellent tutorial… even though I read it using babelfish, in ‘baroque english’, but it was very well explained. Thanks for this resource… it was really helpfull to me for understanding how to use ajax with cakephp. Regards from Perú.
Comment by Lucho — September 11, 2006 @ 4:05 am
Thank you, Lucho.
Comment by Nio — September 11, 2006 @ 10:20 am
不好意思 打扰下 我用新版本cake运行你实例的代码后(还未添加live search) 修改了controller,添加var $uses = array(); .运行后出现一js错误,显示
“错误:例外被抛出且未被接住”
源代码为:
CakePHP AJAX Demo :: Demos
CakePHP AJAX Demo
LOading…
jaja
Click here!Event.observe(’link20708′, ‘click’, function(event){ new Ajax.Updater(’view’,'/cakeblog/demos/hello’, {onLoading:function(request){Element.hide(’view’);Element.show(’loading’)}, onComplete:function(request){Element.hide(’loading’);Effect.Appear(’view’)}, asynchronous:true, evalScripts:true, requestHeaders:['X-Update', 'view']}) }, false);
请教下是什么问题?谢谢了
Comment by mdriven — October 19, 2006 @ 8:18 pm
不好意思,上面只显示了js部分的代码,未能显示完整
Comment by mdriven — October 19, 2006 @ 8:21 pm
請問一下,我測試一下你的範例,如果不設定model
就會出現
No Database table for model Demo (expected “demos”), create it first.
請問cakephp如何設定不使用model??
Comment by winson — November 9, 2006 @ 11:41 am
貌似可以在 controller 中设置 var $uses = array();
Comment by Nio — November 11, 2006 @ 3:01 pm
Hi,多谢你的教程!我有个问题就是,我想在Live Search的表单中加入一些额外的项-比如一组用于限定搜索范围的radio,当用户选择了不同的radio值,搜索时的条件也不会同。我尝试了observeForm()函数,可这样使得原有的搜索都失效了,在cakephp的group上查阅了一遍,没有找到相关的资料,你能告诉我该怎么弄吗?
Comment by Felix — December 19, 2006 @ 9:29 am
Excellent tutorial. It’s very clear.
Comment by Kimi Ge — April 19, 2007 @ 4:59 pm
非常感谢Nio的貌似。
var $uses = array();
Comment by bankerwee — September 20, 2007 @ 12:08 pm
演示好象有错误呀!
Missing Database Table
No Database table for model Post (expected “posts”), create it first.
Notice: this error is being rendered by the app/views/errors/missing_table.thtml view file, a user-customizable error page.
Comment by bankerwee — September 20, 2007 @ 3:04 pm
这个错误表示你的数据库中没有表“posts”。
Comment by Nio — September 23, 2007 @ 11:29 pm
I’ll use a part in my CMS! Thanks!
Comment by Maroon — March 24, 2008 @ 2:15 pm
Thanks for a nice solutions. However, I have made some small modifications to make it work with some of my Wp projects. The idea is great!
Comment by vivienty — December 17, 2008 @ 1:49 am