Cake Framework AJAX Demo 实践解释

 author: 大金 07/2006

本文修正更新并解释 "Cake Framwork AJAX Demo" 演示的代码。

我对 Ajax 的描述

Ajax 重点在 XHR 和 Javascript 两处。XHR 在 CakePHP 中经过包装后使用时几乎碰不到 XHR 代码了。而 Javascript 主要在客户端创造动态,配上 XHR 能异步同服务端通讯,就好像导弹本来发射出去后轨迹大体已固定,通过 Javascript 能灵敏地局部修饰导弹原来大体固定了的轨迹。画时 UML 时序图的话,可在客户端部分加入 Ajax 对象,围绕 Ajax 动作之前之后之中有丰富的行为,看看 $Ajax 的 $options 就知道了,不过很难(也不必)把 Ajax 的所有行为和状态画进去。

准备

使用当前最新的 Cake_1.1.6.3264 及它的 Bake 还有 scriptaculous-js-1.6.1 来实践。解压好 scriptaculous 的所有 .js 放入 Cake 的 js 目录;用 CakeAjaxDemo.src.zip 的 schema.sql 建立好 DB 后用 Bake 建立起可运行的 BLOG 例子代码。

首先修改 Bake 自动建立的控制器

  • 确认控制器 posts_controller.php 中已修改一个变量代码为 var $helpers = array('Html', 'Ajax', 'Javascript', 'Time');
  • 控制器中再加入布局变量 var $layout = 'ajax_blog'; 的代码,当然要复制 ajax_blog.thtml 布局进对应的目录。于是总布局不用默认的了。总布局里有链接 .js 库和大家都要的自定义 js 代码还有 css
  • 但有时动作是 Ajax 引起的仅改变部分页面,只要干净空白的 ajax.thtml 布局。如:view(), edit() 等里面要重新定布局,( Ajax 动作需要再定布局的两种方法之一)代码 $this->layout = 'ajax'; 为此而来。还有 render 递送出来的模板也有改变。这些下面等会再细说...

现在该说 index.thtml 视图模板和其他功能的模板

  • 这里 index.thtml 设计成基本模板,posts 的所有动作都要用它,或者说所有动作显示的页面都在它上面显示。不同于原先 Bake 建立的每个动作有各自独立完整的视图模板。(不就为了 Ajax 能改动局部视图减少传输?)
  • (原先 Bake 建立的 index.thtml 可要可不要,用两横线隔开留着看看也不妨。)把 Demo 的 index.thtml 中 post_table 部分先复制过来(注意到 <div id='post_table'> 没有?)(还有上面的 Demo 文字说明也可拿过来)。其中的传输变量 $data 修改为 $posts 传给 post_table 部分要重复用到的(element 目录里的)ajax_post_list.thtml 模板(注意是用视图的成员函数 $this->renderElement('ajax_post_list', $params); 递送重复模板)。复制过来此重复模板,并参照(API) Manual 修改其中的三个超链接为 $ajax->link( ) 及其参数。
  • 相当于原先 Bake 的 index.thtml 部分已好。其他管理功能的部分 post_content, edit_post, add_post 也复制过来不必改动。注意到这三部分显示是隐藏(display:none)的。查询部分,wastebin 部分及相关脚本也复制过来。接下来把每个功能动起来...

View 功能

  • 刚才说 post_content 部分是隐藏的,要部分显示需要用 Ajax 了。post_table 里的 ajax_post_list 中的 Ajax 超链接<?php echo $ajax->link('View', '#view', array('update'=>"post_content", 'url'=>"/posts/view/{$row['Post']['id']}", 'complete'=>"new Effect.Appear('post_content');")) ?>说:请求 view() 动作,用动作结果 update 更新 post_content 部分, complete 完了运行脚本 Appear 显示 post_content 部分。
  • 控制器动作 view() 结果是带着 view() 的传送数据默认递送 view.thtml 模板。好吧,把 Demo 的 view.thtml 文件复制过来,里面要修改传输变量 $data 为 $posts 代码。提醒你 view()  重新设定用了干净空白的 ajax 布局,供部分更新 index 视图。看到结果了吗?

Edit 功能

  • 同样看 edit_post 部分,开始 ajax_post_list 中的 Ajax 超链接<?php echo $ajax->link('Edit', '#edit', array('url'=>"/posts/edit/{$row['Post']['id']}", 'update'=>"edit_post", 'complete'=>"new Effect.Appear('edit_post');")) ?>说:请求 edit() 动作,用动作结果 update 更新 edit_post 部分, complete 完了运行脚本 Appear 显示 edit_post 部分。
  • 控制器动作 edit() 第一次结果是提取需要编辑的内容,并带着此内容默认递送 edit.thtml 模板。好把 edit.thtml 文件复制过来。
  • 而 edit.thtml 模板需要用重复模板(element 目录里的)ajax_post_edit.thtml,修改添加此模板中表单的 $html 几个输入项(如 input, textarea, hidden)的值 'value'=>$data['Post']['...'] 以及表单提交地址 'url'=>$act.$id 带上编辑内容的 id 号。注意此重复模板在 Add 功能还要用到,所以此 id 号代码部分是条件显示以适应 add() 动作时无 id 号。还是提醒你 edit() 重新设定用了干净空白的 ajax 布局,供部分更新 index 视图。看到 edit_post 部分的表单了吗?
  • 第二次控制器动作 edit() 是 ajax_post_edit 中的表单发起,按照表单 $ajax->submit('Save', $options) 的 $options 要求用动作结果 update 更新  post_table 部分,隐藏  edit_post 部分。动作 edit() 保存表单提交的内容。成功的话就不需要递送原来默认的 edit.thtml  而要递送 refresh_list.thtml 以刷新一个重复视图 ajax_post_list.thtml 以供更新。这里修改原 Demo 的 add.thtml 为 refresh_list.thtml 以便于理解(其中取数据的代码移入控制器为 $this->index();),并修改控制器动作 edit() 相应传输变量和递送的部分为 $this->render('fresh_list', 'ajax');
  • 另外注意以后拖放时要用的,ajax_post_list 和 ajax_post_edit 都运行了 initDrag() 脚本。

Add 功能

  • add_post 部分的内容在 index.thtml 上本来就有但没显示,那好啊,直接按超链接 <a href="#add" onclick="javascript:new Effect.Appear('add_post');">Add New Post</a> 的 javascript 脚本来显示打开空白表单,注意现在此表单早在第一次递送 index.thtml 时就已递送过来,不信去掉 add_post 部分的 display:none 试试。
  • 借用 ajax_post_edit 的表单的 $ajax 提交内容请求 add,并要求动作结果 update 更新  post_table 部分,隐藏  add_post 部分。 (注意动作单词 add 也是条件产生的)
  • 动作 add() 保存表单内容成功(同 edit() 动作一样,修改也一样)递送 refresh_list.thtml 视图供更新 post_table 部分。注意递送函数 render() 能含布局参数而 renderElement() 不能。(奇怪,好像没碰到提交空表问题?有 bug  哦)

Search 功能

  • $ajax->observeField('livesearch', array('update'=>'post_table', 'url'=>"/posts/search", 'frequency'=>1, 'loading'=>"...", 'complete'=>"...")) 观察 livesearch 输入项,如有变化,要求 search() 动作,动作结果更新 post_table 部分,loading 执行中和 compelete 执行完了都要运行一段脚本。 
  • 控制器中增加的 search() 动作要查询数据库,我把此代码从原先 Demo 的 search.thtml 模板中移入 search() 动作中为 $data = $this->Post->findAll("title LIKE '%".mysql_real_escape_string($this->params['form']['livesearch'])."%'", null, 'id');
    $this->set('posts', $data);
    ,这样就只需要用 refresh_list.thtml 专心显示数据够了。提醒你这里用 render() 含布局,刷新 refresh_list.thtml 供更新 post_table 部分。

Delete 功能

  • 标准的 delete() 动作删除纪录并重新提取数据库记录,以更新 post_table 部分,蛮简单了。
  • 关于 Wastebin 功能,留待改进。index.thtml 中 $Ajax 产生的脚本还不能用,直接的脚本能用,但 delete() 动作需要如 Demo 中的做法武断 arbitrary 地截取 id 不太好(最好在视图里解决),再说添加 id=null 虽然照顾了传统的做法却也多了漏 id 的风险。 

小结

经过修改,充分利用了 Bake 产生的控制器,精简了视图模板,并限制视图模板仅显示数据(而不提取产生数据),与新版 Cake_1.1.6.3264 兼容。欢迎下载指教此修改过的 Demo 代码原 Nio 的代码