Chinaunix首页 | 论坛 | 博客
  • 博客访问: 891388
  • 博文数量: 96
  • 博客积分: 10681
  • 博客等级: 上将
  • 技术积分: 2449
  • 用 户 组: 普通用户
  • 注册时间: 2006-05-16 17:52
文章分类

全部博文(96)

文章存档

2011年(30)

2009年(36)

2008年(30)

分类: 系统运维

2008-03-20 17:19:26

通过昨天对已知技术的回顾,我们已经熟悉交互的使用。显示丰富格式的问题与列表,甚至是分页,并不足以使一个程序鲜活。而askeet概念的核心就是允许任何注册的用户询问一个新问题,而任何用户可以回答已存在的问题。现在是我们实现的时候了。

添加一个新问题

我们在第七天所创建的侧边栏已经包含一个添加新问题的链接。他链接到question/add动作,这正是我们将要开发的。

限制到注册用户的访问

首先,只有注册用户可以添加一个新问题。为了限制到question/add动作的访问,在askeet/apps/frontend/modules/question/config/目录下创建一个security.yml文件:

add:
  is_secure:   on
  credentials: subscriber

all:
  is_secure:   off

 
当一个非注册用户试着访问一个限制动作时,Symfony将其重定向到登陆动作。这个动作必须在程序设置中进行定义,在login_module与login_action关键字之后:

all:
  .actions:
    login_module:           user
    login_action:           login

关于动作访问的限制的更多信息可以在Symfony一书的安全一章进行了更多的解释。

addSuccess.php模板

question/add动作将会同时显示表单与处理表单。这就意味着要显示表单,我们只需要一个空动作。另外,如果表单验证出错,还会再次显示表单:

public function executeAdd()
{
}
 
public function handleErrorAdd()
{
  return sfView::SUCCESS;
}

所有的动作都会输出addSuccess.php模板:


 

 
 

 
 

   
   
    get('title')) ?>
 

 
 

   
   
    get('body')) ?>
 

 
 

 
 

   
 



title与body控件都有一个默认值(表单帮助器的第二个参数),其值由请求的表单参数进行定义。为什么是这样呢?因为我们要为表单添加一个验证文件。如果验证失败,表单就会再次显示,而前面的用户实体仍存在于请求参数中。他们可以用作表单元素的默认值。

前面的实体并不会因为表单验证失败而丢失。这是我们对于一个用户友好程序的最低要求。

但是,为了达到这个目的,我们需要一个表单验证文件。

表单验证

在question模块下创建一个validate/目录,并且添加一个add.yml验证文件:

methods:
  post:            [title, body]

names:
  title:
    required:      Yes
    required_msg:  You must give a title to your question

  body:
    required:      Yes
    required_msg:  You must provide a brief context for your question
    validators:    bodyValidator

bodyValidator:
    class:         sfStringValidator
    param:
      min:         10
      min_error:   Please, give some more details

如果我们要了解更多的关于表单验证的信息,我们回到第6天,或是阅读Symfony一书的表单验证一章。

处理表单提交

现在编辑question/add动作来处理表单提交:

public function executeAdd()
{
  if ($this->getRequest()->getMethod() == sfRequest::POST)
  {
    // create question
    $user = $this->getUser()->getSubscriber();
 
    $question = new Question();
    $question->setTitle($this->getRequestParameter('title'));
    $question->setBody($this->getRequestParameter('body'));
    $question->setUser($user);
    $question->save();
 
    $user->isInterestedIn($question);
 
    return $this->redirect('@question?stripped_title='.$question->getStrippedTitle());
  }
}

记 住,->setTitle()方不同时也会设置stripped_title,而->setBody()方法同时也会设置html_body 域,因为我们在Question.php模块类中重写了这两个方法。创建问题的用户将会声明对其感兴趣。这是为了避免出现没有人对其感兴趣的问题,为样就 太悲哀了。

动作的结束处包含了一个->redirect()方法来重定向到创建问题的详细页面。比起->forward() 方法,其最大的优点在于,如果用户以后刷新详细问题页面,表单不会再次提交。另外,'back'按钮也会按所希望的方式工作。这是一条通常的规则:我们不 要使用->forward()方法来结束表单提交的处理动作。

最好是如果表单不是在POST模式,动作仍然可以显示表单。其形为正如同我们在前面编写的空动作,返回sfView::SUCCESS,启动addSuccess.php模板。

不要忘记在User模块中创建珍上isInterestedIn()方法:

public function isInterestedIn($question)
{
  $interest = new Interest();
  $interest->setQuestion($question);
  $interest->setUserId($this->getId());
  $interest->save();
}

作为一次重构,我们可以在user/interested动作中使用这个方法来替换完在同样事情的代码。现在可以进行测试了。使用一个测试用户,我们可以添加一个问题。

添加一个新答案

答案的添加将会以一种略微不同的方式来实现。在这里并没有必要使用一个表单将用户重定向到一个新页面,而是再一次到要显示答案的另一个页面。所以新的答案表单应是Ajax形式,而且新答案应立即显示在问题详细页面。

添加一个Ajax表单

用下面的代码来更改modules/question/templates/showSuccess.php模板的结束处:

...    

getAnswers() as $answer): ?>
 

  $answer)) ?>
 


 

 

      'url'      => '@add_answer',
    'update'   => array('success' => 'add_answer'),
    'loading'  => "Element.show('indicator')",
    'complete' => "Element.hide('indicator');".visual_effect('highlight', 'add_answer'),
  )) ?>
 
   

      isAuthenticated()): ?>
        getNickname() ?>
     
       
       
     
   

 
   

     
      get('body')) ?>
   

 
   

      getId()) ?>
     
   

 

 


重构

link_to_login()函数必须添加到UserHelper.php帮助器中:

function link_to_login($name, $uri = null)
{
  if ($uri && sfContext::getInstance()->getUser()->isAuthenticated())
  {
    return link_to($name, $uri);
  }
  else
  {
    return link_to_function($name, visual_effect('blind_down', 'login', array('duration' => 0.5)));
  }
}

这 个函数所做的事情是我们在其他的User帮助器中已经看到过的:如果用户已被授权,将会显示一个到动作的链接,而如果没有授权,这个链接指向Ajax登陆 表单。所以在link_to_user_interested()和link_to_user_relevancy()函数中使用 link_to_login()来代替link_to_function()函数。不要忘记在 modules/sidebar/templatetes/defaultSuccess.php中到@add_question的链接。是的,这就是重 构。

处理表单提交

虽然他仍是调用代 码片段,我们在这里选择的用来处理Ajax请求的方法与我们在第八天所描述的还是有一些略微的不同。这是因为我们希望表单提交的结果实际替换表单。这就是 为什么将form_remote_tag()帮助器中的更新参数指向表单本身的容器,而不是指向外层空间。_answer.php片段将会包含在答案添加 动作的结果中,所以最终的结果看起来如同下面的样子:

...

 
 
 
  ...

 

 



也 许我们已经猜到了form_remote_tag() javascript是如何工作的了:他通过一个XMLHttpRequest对象将表单提交给url参数中指定的动作。动作的结果替换更新参数中指定的 元素。而且,与第八天中的link_to_remote()帮助器相类似,他会激活活动指示器可见,并且依据请求的提交使其不可邮,而且在Ajax事务的 结束处高亮显示更新的部分。

在这里我们要多说几名关于与一个新问题相关联的用户。我们在前面提到,答案必须链接到一个用户。如果用户已经 被验证,那么他的user_id就会用于新答案。否则,就会使用anonymous,除非用户选择登陆。位于GlobalHelper.php帮助器中的 link_to_login()帮助器就会激活布局中的隐藏表单。我们可以游览askeet源友来查看其代码。

answer/add动作

作为Ajax表单的url参数的@add_answer规则指向answer/add动作:

add_answer:
  url:   /add_anwser
  param: { module: answer, action: add }

下面是动作的内容:

public function executeAdd()
{
  if ($this->getRequest()->getMethod() == sfRequest::POST)
  {
    if (!$this->getRequestParameter('body'))
    {
      return sfView::NONE;
    }
 
    $question = QuestionPeer::retrieveByPk($this->getRequestParameter('question_id'));
    $this->forward404Unless($question);
 
    // user or anonymous coward
    $user = $this->getUser()->isAuthenticated() ? $this->getUser()->getSubscriber() : UserPeer::retriveByNickname('anonymous');
 
    // create answer
    $this->answer = new Answer();
    $this->answer->setQuestion($question);
    $this->answer->setBody($this->getRequestParameter('body'));
    $this->answer->setUser($user);
    $this->answer->save();
 
    return sfView::SUCCESS;
  }
 
  $this->forward404();
}
 首先,如果这个动作不是在POST模式下调用的,这就意味着是某些人在浏览器的地址栏中输入的。动作并不是为这种请类型而设置的,所以在这种情况下会返回一个404错误。

为 了确定要设置为答案作者的用户,动作会检测当前用户是否已被授权。如果不是这种情况,动作就会通地UserPeer类的new:: retrieveByNickname()方法使用'Anonymous Coward'用户。如果我们对这个方法还有疑惑,我们可以查看其源代码。

这样,就准备好了所有事情来创建一个新问题,并且将请求发往addSuccess.php模板。正如我们所希望的,这个模板只包含一行代码:

$answer)) ?>

我们同时需要在frontend/modules/answer/config/view.yml中禁止这个动作的布局:

addSuccess:
  has_layout: off

最后,如果用户提交一个空答案,我们并不要进行存储。所以就会略地数据处理部分,而动作并不会返回任何内容,这将简单的清除页面。我们已经在Ajax表单中完成了数据处理,但是这应将表单自身放在另一个片段中,而现在并不值得这样做。

测试

这样就完成了?正是,现在Ajax表单已经可以使用了,干净,安全。我们现在可以进行测试,显示一个问题的答案列表,并且可以添加一个新问题。这个页面并不需要刷新,而新答案显示在前面的列表的底部。这很简单,不是吗?

明天见
阅读(1755) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~