AJAX in CakePHP (2) - Sortable
肖理达 (KrazyNio AT hotmail.com), 2006.11.26, 转载请注明出处
一、前言
在 2006 年年初的时候,曾经写过一篇关于 CakePHP 的 AJAX 应用文章,现在已经改名为《AJAX in CakePHP - Getting Started》,在之后的十个月里,基本没怎么用过 Cake,直到我到了新公司,由于项目需要,重拾起这个框架。我们的项目要求改善用户体验,所以加入了很多 AJAX,包括即时搜索(live search)、拖曳(drag & drop)、拖曳排序(sortable)等等。在上一篇文章中已经讲解了 live search,这次讲一下 sortable。
二、准备工作
如同第一篇文章所说的,我们需要 CakePHP、prototype 和 script.aculo.us,只不过这次所用的都是目前最新的版本,分别是 CakePHP 1.1.10.3825、prototype 1.5.0_rc1 与 scriptaculous 1.6.5。将 prototype 和 scriptaculous 放到 app/wwwroot/js 目录下,当然最好是给它们分目录存放,方便管理维护。
三、Sortable 实现
和通常的 CakePHP 教程一样,我们从 Model 开始。假设有一个类别表,表名为 categories,有三个字段:id、name 和 order_id,id 是自增关键字,name 是类别名,而 order_id 则用于类别显示排序的顺序序号。表的结构及初识数据如下:
CREATE TABLE `categories` (
`id` int(11) NOT NULL auto_increment,
`name` varchar(100) NOT NULL,
`order_id` int(11) NOT NULL,
PRIMARY KEY (`id`)
) TYPE=MyISAM;
INSERT INTO `categories` (`id`, `name`, `order_id`) VALUES
(1, 'PHP', 0),
(2, 'Java', 1),
(3, 'Python', 3),
(4, 'Perl', 4),
(5, 'C++', 2);
在 app/models 目录下创建 Model 文件 category.php,内容如下:
<?php
class Category extends AppModel
{
var $name = 'Category';
}
?>
接下来是 Controller,在 app/controllers 目录下创建 Controller 文件 categories_controller.php,我们需要在 Controller 中加入两个方法,一个是 index(),另一个是 order(),关键在于 order() 方法,用于保存重新排序之后的结果,并且将其输出。文件内容如下:
<?php
class CategoriesController extends AppController
{
var $name = 'Categories';
var $helpers = array('Html', 'Javascript', 'Ajax'); //我们需要用到的 Helper,特别注意不要忘了 AjaxHelper。
function index()
{
//通过 Object::requestAction() 获取到排序之后的类别列表。
$this->set('item_list', $this->requestAction('/categories/order', array('return'=>true)));
}
function order()
{
$this->layout = 'ajax'; //注意:layout 需要设置成 'ajax'
if (!empty($this->params['form']['list'])) {
//如果 post 提交过来的 $this->params['form']['list'] 非空,则重新设置排序。
$lists = $this->params['form']['list'];
/**
* 提交过来的数据为数组,类似于::
* Array
* (
* [0] => Array
* (
* [id] => 1
* )
* [1] => Array
* (
* [id] => 5
* )
* ….
* )
* key 就是 order_id,而 value 则又是一个数组,其中包含了与 order_id 对应的类别 id。
*/
foreach ($lists as $order_id => $data) {
$data['order_id'] = $order_id;
if (!empty($data['id']))
$this->Category->save($data); //保存新的 order_id
}
}
//获取排序之后的类别列表。
$this->set('cats', $this->Category->findAll(null, null, 'order_id ASC, id ASC'));
}
}
?>
好了,剩下的就是 View 文件了,在 app/views 下创建目录 categories,然后在此目录下创建文件 index.thtml 和 order.thtml。如果你觉得手动创建文件太麻烦的话,不妨试试 WebBaker,可以自动帮你创建好相应的 MVC 文件。
index.thtml:
<?php
//使用 prototype 和 scriptaculous 下相应的 js 文件。
e($javascript->link('prototype/prototype'));
e($javascript->link('scriptaculous/scriptaculous.js?load=effects,dragdrop'));
?>
<style type="text/css">
#loading {
padding: 4px;
width: 100px;
color: #FF6600;
margin: 8px 0;
}
#list {
margin: 0;
margin-top: 10px;
padding: 0;
list-style-type: none;
width: 250px;
}
#list li {
margin: 0;
margin-bottom: 4px;
padding: 5px;
border: 1px solid #888;
cursor: move;
}
</style>
<h3>Sortable Demo - AJAX in CakePHP</h3>
<div id="loading" style="display:none">Loading ….</div>
<div id="list-box">
<?php e($item_list); //输出已排序的类别列表。 ?>
</div>
order.thtml:
<ul id="list">
<?php foreach ($cats as $cat): ?>
<li id="item_<?php e($cat['Category']['id']); ?>"><?php e($cat['Category']['name'])?></li>
<?php endforeach; ?>
</ul>
<?php
//由于目前 Cake 中的 AjaxHelper 的 $sortOptions 还不支持 "tree" 选项,所以我们需要手动 hack 一下 ;)
$ajax->sortOptions[] = 'tree';
//输出 sortable 的 JavaScript 代码:
e($ajax->sortable('list', array(
'tree' => 'true', //Sortable.serialize() 的时候返回所有的排序结果。
'url' => '/categories/order', //拖曳排序操作时 AJAX 调用服务器的 Action 路径,此处指向 CategoriesController::order()。
'update' => 'list-box', //服务器操作完成之后更新的 HTML 元素的 DOM ID
'loading' => "Element.show('loading');", //加载服务器数据过程中显示 "loading" 元素。
'complete' => "Element.hide('loading');new Effect.Highlight('list')"))); //加载完成之后隐藏 "loading" 元素,并加亮 "list" 元素。
?>
在 order.thtml 中,首先构建一个使用 ul/li 的排序列表,此时的 ul 为容器,li 为排序项,两者都需要有 id 属性,称为 DOM ID,ul 的 id 可随意取,这里用了“list”,li 则需要将类别 id 作为 DOM ID 的一部分,并且用下划线“_”与 id 前缀隔开,前缀可以自由命名,这里我们用的前缀为“item”,组合之后的 li DOM ID 为:item_1, item_2 … item_n。接着我们使用 $ajax->sortable() 输出了 sortable 的 JavaScript 代码。AjaxHelper::sortable() 方法有两个参数,第一个参数为排序列表的容器 DOM ID,即是我们这里的 ul 的 id“list”;第二个参数为相关选项,经常用到的是 tree, url, update, loading, complete 等等,其中 url, update, loading, complete 都是 AjaxHelper::remoteFunction() 所用的选项,用于声成 Ajax.Updater() 的 JavaScript 代码。如果你用于排序的标签不是 ul/li,而是其它的,比如 div, img 等,那么只需要多设置一个“tag”选项即可,如:加上 'tag' => 'div' 或 'tag' => 'img'。
好了,到目前为止,我们已经完成了拖曳排序,你可以通过 http://www.somesite.com/cake/categories/ 来访问一下(这里的地址,路径取决于你的 CakePHP 安装),拖曳某一项到别的项上方或下方,看看是什么效果,同时看看数据库记录中的 order_id 是否相应地发生了改变。
四、相关资源