第十天--使用Ajax表單修改資料

來源:互聯網
上載者:User
通過昨天對已知技術的回顧,我們已經熟悉互動的使用。顯示豐富格式的問題與列表,甚至是分頁,並不足以使一個程式鮮活。而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表單已經可以使用了,乾淨,安全。我們現在可以進行測試,顯示一個問題的答案列表,並且可以添加一個新問題。這個頁面並不需要重新整理,而新答案顯示在前面的列表的底部。這很簡單,不是嗎?

明天見

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.