回顾: 在七个小时之后,askeet应用程序进步的非常好。主页展示了一个问题的清单,还有问题的回答,也有了用户信息的网页,在每页的边栏上也有主题清单。我们的社区增强FAQ也在正确的方向(请看昨天的动作清单),除此之外现在用户还不能改变数据。 如果在网络中数据操作的基础长久以来是表单的话,那么今天AJAX技术和可用性的提高可以改变一个应用程序的实现方法。同样这个也适用于askeet。这个教程将展示你怎样给askeet添加一个增强的AJAX交互。这目标是允许一个注册用户对一个问题宣称他的兴趣。
当异步请求正在处理的时候,一个AJAX网站的用户并没有任何线索关于他行为的处理和即将显示的结果。这就是为什么每个包含AJAX互动的网页应该显示一个行动指示器。 为了这个目的,在全局layout.php的<body>上面添加:
<div id="indicator" style="display: none"></div>
尽管默认是隐藏的,这个<div>将会被显示在AJAX请求正在处理的时候。这是个空的内容,但是main.css样式模版(存储在askeet/web/css/目录下)定义了它的形状和内容:
div#indicator { position: absolute; width: 100px; height: 40px; left: 10px; top: 10px; z-index: 900; background: url(/images/indicator.gif) no-repeat 0 0; }
一个ajax交互有三部分组成:一个呼叫器caller(链接,按钮或是任何用户操作来出发动作的控制),一个服务器动作,和一个在网页中向用户展示结果的区域。
让我们回到显示的问题清单上。如果你记着在第四天中,一个问题可以被在问题清单中显示也可以在问题细节中显示。
这就是为什么问题名称和兴趣区域的代码被重构到一个_interested_user.php片断中。再次打开这个片段,
<?php use_helper('User') ?> <div class="interested_mark" id="mark_<?php echo $question->getId() ?>"> <?php echo $question->getInterestedUsers() ?> </div> <?php echo link_to_user_interested($sf_user, $question) ?>
这个链接做的不只是重新定向到另外一个网页。事实上,如果用户已经声明了他或她关于一个问题的兴趣,他或她是不应该能再次声明。并且,如果用户没有被授权。。。嗯,我们将会在以后看到这种情形。
这个链接被写入了一个助手函数。他被创建在askeet/apps/frontend/lib/helper/UserHelper.php中:
<?php use_helper('Javascript'); function link_to_user_interested($user, $question) { if ($user->isAuthenticated()) { $interested = InterestPeer::retrieveByPk($question->getId(), $user->getSubscriberId()); if ($interested) { // already interested return 'interested!'; } else { // didn't declare interest yet return link_to_remote('interested?', array( 'url' => 'user/interested?id='.$question->getId(), 'update' => array('success' => 'block_'.$question->getId()), 'loading' => "Element.show('indicator')", 'complete' => "Element.hide('indicator');".visual_effect('highlight', 'mark_'.$question->getId()), )); } } else { return link_to('interested?', 'user/login'); } } ?>
link_to_remote()函数是第一个AJAX交互组件:呼叫器。他声明了当用户点击链接时(此处:user/interested),哪个动作将被请求,那片区域将被动作的返回结果(此处:id_XX的元素)更新。两个事件处理器(loading和complete)被添加和连接到prototype javascript函数。Prototype库用简单的函数调用提供了非常容易的javascript工具在网页上应用可视化效果。他仅有的缺点是缺乏文档,但是源代码是直截了当的。 我们选择使用助手来替代片段是因为这个函数包括了比HTML更多的php代码。 不要忘记添加id—— id=”block_<?php echo $question→getId() ?>”到question/_list片段。
<div class="interested_block" id="block_<?php echo $question->getId() ?>"> <?php include_partial('interested_user', array('question' => $question)) ?> </div>
注意: 这个仅仅当你在web服务器配置中正确的定义了sf别名时才能工作。在第一天中有详细解释。
link_to_remote()javascript助手的update属性定义了结果区域。在这个例子里,user/interested动作将替换id为block_XX元素的内容。如果你有些迷惑,看一下在模版中片段整合将要显示的内容:
... <div class="interested_block" id="block_<?php echo $question->getId() ?>"> <!-- between here --> <?php use_helper('User') ?> <div class="interested_mark" id="mark_<?php echo $question->getId() ?>"> <?php echo $question->getInterestedUsers() ?> </div> <?php echo link_to_user_interested($sf_user, $question) ?> <!-- and there --> </div> ...
一个结果区域是在两个组件的部分。动作一旦执行就将替换这个内容。 第二个id(mark_XX)是非常直观的。link_to_remote助手的complete事件处理器加亮了点击过的兴趣的interested_mark <div>。。。在动作返回了一个增加过的兴趣数量之后。
AJAX呼叫器定向到user/interested动作。这个动作必须为当前问题和用户创建一个新的interest表记录。这里是怎么用symfony实现它的方法:
public function executeInterested() { $this->question = QuestionPeer::retrieveByPk($this->getRequestParameter('id')); $this->forward404Unless($this->question); $user = $this->getUser()->getSubscriber(); $interest = new Interest(); $interest->setQuestion($this->question); $interest->setUser($user); $interest->save(); }
记住Interest对象的→save()方法被定义用来增加相关User表interested_user域的值。因此对当前问题有兴趣的用户数量将会在动作调用之后被神奇地在屏幕上增加。 此外,作为结果的interestedSuccess.php模版将会展示些什么呢?
<?php include_partial('question/interested_user', array('question' => $question)) ?>
它会再次展示question模块的_interested_user.php片段。这就是先把这个片段创建的好处。 我们同样也需要把这个模版的布局禁止掉(modules/user/config/view.yml):
interestedSuccess: has_layout: off
AJAX兴趣声明的开发现在结束了。你可以通过在登录页面中输入已有的登录名和密码来测试它。先显示问题清单,然后点击一个”interested?”链接。指示器会在请求被发送到服务器时呈现。接下来,数字会在服务器回答时被高亮的增加。主页最初的”interested?”链接现在变成了没有链接的”interested!”文本。感谢我们的link_to_user_interested助手:
如果你想要更多关于AJAX助手的例子,你可以阅读drag-and-drop shopping cart教程, 观看相关的视频演示或是阅读书里相关的章节
我们前面讲过只有注册用户才能对一个问题声明兴趣。这就意味着如果一个非验证过用户点击一个“interested?”链接,首先显示的应该是登录页。 但是等一下。为什么一个用户必须用一个新加载的网页登录,而且失去了与他想声明兴趣的问题的联系。一个更好的办法是让一个登录表格动态的呈现在网页之中。这就是我们将要去做的:
打开一个全局布局(在askeet/apps/forntend/templates/layout.php中),并且加入以下代码(在header和content div之间):
<?php use_helper('Javascript') ?> <div id="login" style="display: none"> <h2>Please sign-in first</h2> <?php echo link_to_function('cancel', visual_effect('blind_up', 'login', array('duration' => 0.5))) ?> <?php echo form_tag('user/login', 'id=loginform') ?> nickname: <?php echo input_tag('nickname') ?><br /> password: <?php echo input_password_tag('password') ?><br /> <?php echo input_hidden_tag('referer', $sf_params->get('referer') ? $sf_params->get('referer') : $sf_request->getUri()) ?> <?php echo submit_tag('login') ?> </form> </div>
再次,这个表格被设置成默认隐藏的。 referer隐藏标签包含了referer请求参数(如果它存在的话)或者是其它当前地址。
你还记着我们之前些的那个User助手吗?我们现在来处理用户是没有授权的这种情况。再次打开askeet/lib/helper/UserHelper.php文件并修改以下代码:
return link_to('interested?', 'user/login');
为:
return link_to_function('interested?', visual_effect('blind_down', 'login', array('duration' => 0.5)));
当用户是非登录时, 在“insterested?”上的连接将会触发一个prototype javascript效果(blind_down)。它将显示id为login的元素。这个元素也就是我们加在布局中的表单
user/login动作已经在第五天里实现了,并且在第六天里进行了重构。我们需要再次修改它吗?
public function executeLogin() { if ($this->getRequest()->getMethod() != sfRequest::POST) { // display the form $this->getRequest()->getParameterHolder()->set('referer', $this->getRequest()->getReferer()); return sfView::SUCCESS; } else { // handle the form submission // redirect to last page return $this->redirect($this->getRequestParameter('referer', '@homepage')); } }
总得来说,不用了。它非常完美的实现了它的目的,回调地址的处理将把用户转向到当连接被点击时他们正在访问的地方。
现在测试AJAX功能。一个非注册用户将不用离开当前页就可以看到登录表单。如果昵称和密码被识别,网页将会被刷新并且用户将可以点击“interested?”这个他之前想访问的连接。
注意: 在很多像这样的AJAX互动中,服务器动作的模板是一个简单的include_partial。这是因为一个初始的结果常常在整个页面第一次被加载时显示,并且被AJAX动作更新的部分也同样是最初模板的一部分。
在设计AJAX互动时,最困难的事是合适地定义呼叫器,服务器动作和结果区域。一旦你了解了他们,symfony提供你助手去做剩余的事情。请确定你明白它是怎样工作的,查看我们是怎样在一个用户去声明对答案相关性的兴趣中实现同样的机制的的。这次,被调用的AJAX动作是user/vote,_answer.php片段被分成了两个部分(因此创建了一个_user_vote.php片段),并且两个助手link_to_user_relevancy_up()和link_to_user_relevancy_down()被建立在User助手中。User模块同样获得了一个vote动作和一个voteSuccess.php模板。同样也不要忘了把这个模板的布局设置成off状态。
Askeet是一个看上去像web2.0应用程序的开始。并且他仅仅是一个开始: 在接下来的几天中,我们将添加更多的AJAX互动。明天我们将趁机做一个symfony MVC技术的全面回顾,并且实现一个外部库。
如果你在学习今天的教程中遇到了问题,你仍然可以从askeet SVN代码库中标签release_day_18里下载全部代码。如果你没有遇到问题,请到askeet 论坛中回答他人的问题。