原文:http://www.symfony-project.com/askeet/10

用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将把他/她转向到登录动作。这个动作必须在应用程序setttings.yml中定义,必须位于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 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

如果你需要更多关于表单验证的信息,请回到第六天或是阅读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(),这样做的主要好处是如果用户过后刷新了问题细节页面,表格不会被再次递交。此外, ‘返回’按钮如同期待的那样工作。这是一条通用规则:你不应该用→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/insterested动作中这个方法置换有同样功能的代码段。 再进一步,现在就测试它。使用一个测试用户帐号,你可以添加新问题了。

添加一个新问题

问题的添加将用一个稍微不同的方法实现。这里并没有必要通过重新定向用户到一个新的表单页,然后再到另外一个页面去显示问题。因此新问题表单将采用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_insterested()和link_to_user_relevancy函数中对link_to_function()的调用为link_to_login()。不要忘了在modules/sidebar/templates/defaultSuccess.php中对@add_question的链接。是的,这是重构。

处理表单递交

尽管它仍旧牵涉到了片段(fragment),在这里用到的处理AJAX请求的方法是与第八天中讲述的那种有着轻微的不同。这是因为我们想使表单递交的结果完全取代表单的位置。这就是为什么form_remote_tag()助手的update参数指向了表单自己的容器而不是一个外部区域。_answer.php片段将被包含在问题添加动作的结果中,从而使最终的结果看起来像:

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

你很可能已经猜到了form_remote_tage()javascript助手是怎样工作的:它通过XMLHttpRequest对象处理表单递交到在url参数指定的动作。动作的结果置换在update参数中指定的元素。并且,就像第八天的link_to_remote()助手一样,它打开和关闭活动指示器的可视性。

 
askeet_10.txt · 上一次变更: 2007/03/20 19:26 通过 root
 
Recent changes RSS feed Creative Commons License Donate Powered by PHP Valid XHTML 1.0 Valid CSS Driven by DokuWiki