• 第十天--使用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表单已经可以使用了,干净,安全。我们现在可以进行测试,显示一个问题的答案列表,并且可以添加一个新问题。这个页面并不需要刷新,而新答案显示在前面的列表的底部。这很简单,不是吗?

    明天见
  • 相关阅读:
    【Android Developers Training】 51. 序言:打印内容
    小米手机开发遇到的问题
    Linux进程的实际用户ID和有效用户ID
    Linux core 文件介绍
    Linux下内存的几个基本概念
    mingw libgcc_s_sjlj-1.dll is missing
    C语言函数申明关键字inline
    关于编译错误--提领指向不完全类型的指针
    linux下创建用户[转]
    Linux查看CPU和内存使用情况[转]
  • 原文地址:https://www.cnblogs.com/dyllove98/p/2462006.html
Copyright © 2020-2023  润新知