第十天--使用Ajax表单修改数据

通过昨天对已知技术的回顾,我们已经熟悉交互的使用。显示丰富格式的问题与列表,甚至是分页,并不足以使一个程序鲜活。而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模板:

<?php use_helper('Validation') ?>
 
<?php echo form_tag('@add_question') ?>
 
  <fieldset>
 
  <div class="form-row">
    <?php echo form_error('title') ?>
    <label for="title">Question title:</label>
    <?php echo input_tag('title', $sf_params->get('title')) ?>
  </div>
 
  <div class="form-row">
    <?php echo form_error('body') ?>
    <label for="label">Your question in details:</label>
    <?php echo textarea_tag('body', $sf_params->get('body')) ?>
  </div>
 
  </fieldset>
 
  <div class="submit-row">
    <?php echo submit_tag('ask it') ?>
  </div>
</form>

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模板的结束处:

...    
<div id="answers">
<?php foreach ($question->getAnswers() as $answer): ?>
  <div class="answer">
  <?php include_partial('answer/answer', array('answer' => $answer)) ?>
  </div>
<?php endforeach; ?>
 
<?php echo use_helper('User') ?>
 
<div class="answer" id="add_answer">
  <?php echo form_remote_tag(array(
    'url'      => '@add_answer',
    'update'   => array('success' => 'add_answer'),
    'loading'  => "Element.show('indicator')",
    'complete' => "Element.hide('indicator');".visual_effect('highlight', 'add_answer'),
  )) ?>
 
    <div class="form-row">
      <?php if ($sf_user->isAuthenticated()): ?>
        <?php echo $sf_user->getNickname() ?>
      <?php else: ?>
        <?php echo 'Anonymous Coward' ?>
        <?php echo link_to_login('login') ?>
      <?php endif; ?>
    </div>
 
    <div class="form-row">
      <label for="label">Your answer:</label>
      <?php echo textarea_tag('body', $sf_params->get('body')) ?>
    </div>
 
    <div class="submit-row">
      <?php echo input_hidden_tag('question_id', $question->getId()) ?>
      <?php echo submit_tag('answer it') ?>
    </div>
  </form>
</div>
 
</div>

重构

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片段将会包含在答案添加动作的结果中,所以最终的结果看起来如同下面的样子:

...
<div id="answers">
  <!-- Answer 1 -->
  <!-- Answer 2 -->
  <!-- Answer 3 -->
  ...
</div>
 
<div class="answer" id="add_answer">
  <!-- The new answer -->
</div>


也许我们已经猜到了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模板。正如我们所希望的,这个模板只包含一行代码:

<?php include_partial('answer', array('answer' => $answer)) ?>

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

addSuccess:
  has_layout: off

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

测试

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

明天见
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值