[原创中文翻译]symfony askeet24:第八天,AJAX。

October 4, 2007 – 2:56 am

[欢迎转载,转载请注名出处http://symfony.net.cn。本文英文版权归symfony官方网站所有]

Add an indicator in the layout

给布局页面加一个指示器

While an asynchronous request is pending, users of an AJAX-powered website don’t have any of the usual clues that their action was taken into account and that the result will soon be displayed. That’s why every page containing AJAX interactions should be able to display an activity indicator.

当异步请求处于发送状态时,使用了AJAX技术的网站会在页面上提示用户结果会很快显示出来。所以我们需要使用AJAX技术,给用户提供一个指示器。

For that purpose, add at the top of the <body> of the global layout.php:

为了实现这个目的,我们在布局页面layout.php顶部的<body>处填加:

<div id="indicator" style="display: none"></div>

Although hidden by default, this <div> will be displayed when an AJAX request is pending. It is empty, but the main.css stylesheet (stored in the askeet/web/css/ directory) gives it shape and content:

尽管默认这个是隐藏的,当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;
}

indicator.gif

[译者:看到上面这个动态图片了么?所谓的指示器就是一个类似状态进度条的图片,提示 用户现在请求正在进行,马上就会显示内容。]

Add an AJAX interaction to declare interest

在用户对问题表示兴趣按钮上使用AJAX功能

An ajax interaction is made up of three parts: a caller (a link, a button or any control that the user manipulates to launch the action), a server action, and a zone in the page to display the result of the action to the user.

AJAX由三部分组成:caller(链接,按扭或者任何用户可以触发动作),server动作和显示给用户结果的界面。

Caller

Caller

Let’s go back to the questions displayed. If you remember the day four, a question can be displayed in the lists of questions and in the detail of a question.

回去看看问题显示界面。如果你还记得第四天的教材,一个问题可以在问题列表页面和问题详细介绍页面里显示。

wwwsymfony-projectcom_list-of-question_pager_navigation_day7.gif

That’s why the code for the question title and interest block was refactored into a _interested_user.php fragment. Open this fragment again, add a link to allow users to declare their interest:

这就是我们为什么把显示“问题题目”和“用户感兴趣”的代码放到代码片断_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) ?>

This link will do more than just redirect to another page. As a matter of fact, if a user already declared his/her interest about a given question, he/she must not be able to declare it again. And if the user is not authenticated… well, we will see this case later.

这个链接不光重定向其他页面。事实上,如果用户对某问题表示他/她感兴趣,提交一次,他/她就不能再提交一次,即登录用户只能对一个问题提交一次“感兴趣”。如果用户不是验证用户… …好了,我们接着看下面。

The link is written in a helper function, that needs to be created in a askeet/apps/frontend/lib/helper/UserHelper.php:

链接写在helper函数里,需要创建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');
}
}

The link_to_remote() function is the first component of an AJAX interaction: The caller. It declares which action must be requested when a user clicks on the link (here: user/interested) and which zone of the page must be updated with the result of the action (here: the element of id block_XX). Two event handlers (loading and complete) are added and associated to prototype javascript functions. The prototype library offers very handy javascript tools to apply visual effects in a web page with simple function calls. Its only fault is the lack of documentation, but the source is pretty straightforward.

link_to_remote()方法是AJAX的第一个组件:Caller。当用户点链接(user/interested)时发送请求给动作,根据动作返回结果更新对应页面zone(id block_XX)。两个事件处理(载入并完成)来自prototype javascript库。prototype库提供了非常方便的javascript工具包来实现web视觉效果。它的唯一缺点是缺少说明文档,但是库代码是对的。

We chose to use a helper instead of a partial because this function contains much more PHP code than HTML code.

我们选择用helper代替局部模板,因为这个方法包含的PHP代码比HTML代码多。

Don’t forget to add the id id=”block_<?php echo $question->getId() ?>” to the question/_list fragment.

别忘了加上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>

This will only work if you properly defined the sf alias in your web server configuration, as explained during day one.

只有你在web服务器上定义了sf别名后这才能工作正常,像第一天的教程里说的。

Result zone

zone结果显示

The update attribute of the link_to_remote() javascript helper specifies the result zone. In this case, the result of the user/interested action will replace the content of the element of id block_XX. If you are confused, take a look at what the integration of the fragment in the templates will render:

link_to_remote()javascript helper更新的属性详细表达了zone的结果。例如,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>
...

The result zone is the part between the two comments. The action, once executed, will replace this content.

zone结果在两个层中间。动作一旦执行了,会替换掉这些内容。

The interest of the second id (mark_XX) is purely visual. The complete event handler of the link_to_remote helper highlights the interested_mark <div> of the clicked interest… after the action returns an incremented number of interest.

“感兴趣”的第二个id(被点击后的)(mark_XX)实现的“感兴趣”功能纯粹为了视觉效果。完全事件运行link_to_remote helper后,高亮显示interested_mark层,即用户表示“感兴趣”后就显示新的id,新的id就是动作处理用户点“感兴趣”后返回的那个id。

[译者:这里实现的功能就是,用户表示“感兴趣”前显示一个数字表示有多少用户点击过“感兴趣”了,当用户点击后,id加一,而且用户不能再点击一次“感兴趣”了。]

Server action

服务动作

The AJAX caller points to a user/interested action. This action must create a new record in the Interest table for the current question and the current user. Here is how to do it with symfony:

AJAX的caller指向user/interested动作。这个动作必须给Interest表创建新记录,给User表更新纪录。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();
}

Remember that the ->save() method of the Interest object was modified to increment the interested_user field of the related User. So the number of interested users about the current question will be magically incremented on screen after the call of the action.

记得Interest对象的->save()函数被我们修改过了,给User表的interested_user字段加一。所以在动作被调用后,interested用户数量会先增加然后在屏幕上显示。

And what should the resulting interestedSuccess.php template display?

interestedSuccess.php模板会显示什么呢?

<?php include_partial('question/interested_user', array('question' => $question)) ?>

It displays the _interested_user.php fragment of the question module again. That’s the greatest interest of having written this fragment in the first place.

再次显示了问题模块动作的_interested_user.php代码片断。

We also have to disable layout for this template (modules/user/config/view.yml):

我们也必须禁用此模板的布局(module/user/config/view.yml):

interestedSuccess:
has_layout: off

Final test

最后测试

The development of the AJAX interest is now over. You can test it by entering an existing login/password in the login page, displaying the quesiton list and then clicking an ‘interested?’ link. The indicator appears while the request is passed to the server. Then, the number is incremented in a highlight when the server answers. Note that the initial ‘interested?’ link is now an ‘interested!’ text without link, thanks to our link_to_user_interested helper:

“感兴趣”部分的AJAX开发现在结束。你可以登录后,进入问题列表页面然后点“interested?”链接测试。当请求向服务器发送时指示器图片会显示。然后,“感兴趣”数字加一(处理完毕)并高亮显示。注意开始的“interested?”链接现在是“interested!”文本而没有链接了,靠link_to_user_interested helper实现:

wwwsymfony-projectcom_ajax_ajax.gif

If you want more examples about the use of the AJAX helpers, you can read the drag-and-drop shopping cart tutorial, watch the associated screencast or read the related book chapter.

如果你想知道更多的关于AJAX helper,看看drag-and-drop 购物车教程,看看associated screencast或者读读刚发布的书。

Add an inline ’sign-in’ form

增加内联登录表单

We previously said that only registered users could declare interest about a question. This means that if a non-authenticated user clicks on an ‘interested?’ link, the login page must be displayed first.

前面我们说了只有注册用户能使用“对此问题感兴趣”功能。这意味着如果没有验证的用户点了“interested?”链接,首先应该显示登陆页面。

But wait. Why should a user load a new page to login, and lose contact with the question he/she declared interest for? A better idea would be to have a login form appear dynamically on the page. That’s what we are going to do.

但等等。为什么用户要从新打开的页面登录呢,而不在当前页面登录呢?解决的好办法是使用动态登录表单。这就是我们要做的。

Add a hidden login form to the layout

在布局页面加隐藏登录表单

Open the global layout (in askeet/apps/frontend/templates/layout.php), and add in (between the header and the content div):

打开全局布局页面(askeet/apps/frontend/templates/layout.php),增加(在header和内容层之间):

<?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>

Once again, this form is hidden by default. The referer hidden tag contains the referer request parameter if it exists, or else the current URI.

和前面一样,表单默认被隐藏。如果存在,referer隐藏标签包含referer请求参数,否则还是使用当前URL。

Have the form appear when a non-authenticated user clicks an interested link

当未登录用户点interested?链接时表单出现

Do you remember the User helper that we wrote previously? We will now deal with the case where the user is not authenticated. Open again the askeet/lib/helper/UserHelper.php file and change the line:

还记得前面我们写的user helper吗?我们现在处理一下当用户没有登录的情况。再次打开askeet/lib/helper/userhelper.php改这行代码:

[译者,2007年10月17日:这里的userhelper.php文件应该在askeet/apps/frontend/lib/helper/下。]

return link_to('interested?', 'user/login');

改成:

return link_to_function('interested?', visual_effect('blind_down', 'login', array('duration' => 0.5)));

When the user is not authenticated, the link on the ‘interested?’ word launches a prototype javascript effect (blind_down) that will reveal the element of id login - and that’s the form that we just added to the layout.

当用户没有登录时,链接“interested?”载入prototype javascript效果(blind_down),显示id login元素——这也是我们填加到布局页面的表单。

Login the user

用户登录

The user/login action was already written during the fifth day, and refactored during day six. Do we have to modify it again?

user/login action在第五天已经写过了,第六天重写过。我们再改一遍吗?

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’));
}
}

After all, no. It works perfectly as it is, the handling of the referer will redirect the user to the page where he/she was when the link was clicked.

不用。它还是像它原来那样工作,当用户点击了链接referer会重定向用户到刚才他访问的页面。

wwwsymfony-projectcom_login-form-revealed_div_login_form.gif

In many AJAX interactions like this one, the template of the server action is a simple include_partial. That’s because an initial result is often displayed when the whole page is first loaded, and because the part that is updated by the AJAX action is also part of the initial template.

许多和这个功能类似的AJAX界面上,服务器动作模板是简单的include_partial(局部模板)。这是因为原始结果信息在整个页面第一次载入时被显示,因为被AJAX action更新的部分也是原始模板的部分。

Post a Comment