AJAX in CakePHP (2) - Sortable

Posted on 26th November 2006 by Nio in 程序人生

肖理达 (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、prototypescript.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(nullnull'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 是否相应地发生了改变。

四、相关资源

No Comments »

No comments yet.

Leave a comment