(0)
(0)
(0)
(0)
Total: 0 DAY10: AJAXでデータ操作
質問の投稿
新しい質問の投稿をAJAXを使ったフォームで実装していきます。
ログインフォームをAJAXで実装しているので基本は同じです。
symfony1.0との違いはやはりsfFormを利用した実装です。
ログインしているユーザーだけに許可
質問はログインしているユーザーだけに許可することにするので質問投稿フォームであるquestion/addに制限を加えてあります。
./apps/frontend/modules/question/config/security.yml
-
add:
-
is_secure: on
-
credentials: subscriber
-
all:
-
is_secure: off
ログインしていないユーザーがアクセスしてきた場合はログインフォームを表示させたいので、
setting.ymlにて遷移先を指定しておきます。
./apps/frontend/config/setting.yml
-
all:
-
.actions:
-
login_module: user
-
login_action: login
質問投稿フォームの作成
ログインフォームの実装と同じですね。sfFormを使って質問の投稿フォームを作成していきます。
まずはQuestionの基底モデルを作成しておきます。
./lib/form/doctrine/QuestionForm.class.php
-
...
-
public function configure()
-
{
-
// unset fields
-
'created_at',
-
'updated_at',
-
);
-
foreach ($unset_fields as $value) {
-
$this->offsetUnset($value);
-
}
-
//////////////////////////////////////////////////
-
// title
-
$key = 'title';
-
$this->widgetSchema[$key] = new sfWidgetFormInput();
-
$this->widgetSchema->setLabel($key, '件名');
-
$this->validatorSchema[$key] = new sfValidatorString();
-
$_max = 100;
-
$this->validatorSchema[$key]->setOption('max_length', $_max);
-
$this->validatorSchema[$key]->setOption('required', true);
-
$this->validatorSchema[$key]->setMessage('required', '件名は必須です');
-
//////////////////////////////////////////////////
-
// body
-
$key = 'body';
-
$this->widgetSchema[$key] = new sfWidgetFormTextarea();
-
$this->widgetSchema->setLabel($key, '質問内容');
-
$this->validatorSchema[$key] = new sfValidatorString();
-
$_max = 1000;
-
$this->validatorSchema[$key]->setOption('max_length', $_max);
-
$this->validatorSchema[$key]->setOption('required', true);
-
$this->validatorSchema[$key]->setMessage('required', '質問内容は必須です');
-
}
これを親とするPostQuestionForm.class.phpを作成します。
フォームから受け取る値ではなく、sfUserインスタンスに保持しているユーザーIDをアサインする必要があるので、
保存する前に呼び出されるupdateObjectメソッドを使ってuser_idをアサインしています。
./lib/form/doctrine/PostQuestionForm.class.php
-
class PostQuestionForm extends QuestionForm
-
{
-
public function configure()
-
{
-
parent::configure();
-
'interested_users',
-
'user_id',
-
);
-
foreach ($unset_fields as $value) {
-
$this->offsetUnset($value);
-
}
-
}
-
public function updateObject($values = null)
-
{
-
$object = parent::updateObject($values);
-
$object->setUserId(sfContext::getInstance()->getUser()->getSubscriberId());
-
return $object;
-
}
-
}
あとはaddアクションとaddSuccessテンプレートを用意すれば完了です。
sfFormでORMのsaveメソッドを叩くには$this->form->save()を利用します。
また保存したQuestionインスタンスが戻ってくるのでそれを利用して投稿したページへリダイレクトしています。
./apps/frontend/modules/question/actions/actions.class.php
-
...
-
public function executeAdd($request)
-
{
-
// get sfForm object
-
$this->form = new PostQuestionForm();
-
// check Request Method
-
if (!$request->isMethod('post')) {
-
return sfView::SUCCESS;
-
}
-
$this->form->bind($request->getParameter('question'));
-
if ($this->form->isValid()) {
-
$question = $this->form->save();
-
$this->redirect('question/show?id=' . $question->getId());
-
}
-
return sfView::SUCCESS;
-
}
./apps/frontend/modules/question/templates/addSuccess.php
-
<form action="<?php echo url_for('@add_question') ?>" method="POST">
-
<table>
-
<tr>
-
<td colspan="2" style="text-align: right"><input type='submit' value="投稿する"></td>
-
</tr>
-
</table>
-
</form>
回答を追加できるようにする
本家と同様におこなっていきます。
AJAXでの回答フォームを追加していきます。
PostAnserFormというフォームインスタンスを作成するので、以前行ったのと同様で
コンポーネントを利用するようにします。
./apps/frontend/modules/question/tempaltes/showSuccess.php
コンポーネントで質問ID(question_id)を用いて新しいAnswerインスタンスを作り
それをインスタンス作成時に渡しています。
こうすることで、初期値をsfFormが自動的におこなってくれます。
./apps/frontend/modules/answer/actions/components.class.php
-
<?php
-
class answerComponents extends sfComponents
-
{
-
public function executeAdd(sfWebRequest $request)
-
{
-
$answer = new Answer();
-
$answer->setQuestionId($this->question_id);
-
$this->form = new PostAnswerForm($answer);
-
}
-
}
テンプレートを用意しておきます。
そして、サブミットをAJAXにて処理するためform_remote_tagを使用しています。
./apps/frontend/modules/asnwer/templates/_add.php
-
<div class="answer" id="add_answer">
-
-
-
<div class="answer" id="add_answer">
-
'url' => '@add_answer',
-
'loading' => "Element.show('indicator')",
-
'complete' => "Element.hide('indicator');".visual_effect('highlight', 'add_answer'),
-
)) ?>
-
-
<table>
-
<tr>
-
<td colspan="2"><input type="submit" value="回答する"></td>
-
</tr>
-
</table>
-
</form>
-
</div>
ログインへのリンクのヘルパーを追加
本家のチュートリアルではここでいきなりリファクタリングが始まります。
やりたいことはlink_to_loginとしてGlobalヘルパーで共通化してしまうということです。
./apps/frontned/lib/helper/UserHelper.php
-
use_helper('Javascript', 'Global');
-
....
-
return link_to_login('interested?');
-
// return link_to_function('interested?', visual_effect('blind_down', 'login', array('duration' => 0.5)));
Globalヘルパーにlink_to_functionを実装します。
./apps/frontend/lib/helper/GrobalHelper.php
-
function link_to_login($name, $uri = null)
-
{
-
if ($uri && sfContext::getInstance()->getUser()->isAuthenticated())
-
{
-
return link_to($name, $uri);
-
}
-
else
-
{
-
}
-
}
これで、サイドバーの`ask a new question`をlink_to_loginに書き換えることができます。
./apps/frontend/modules/sidebar/templates/_default.php
-
<?php use_helper('Global') ?>
-
....
sfFormの実装
ようやく、sfFormの用意です。いままでと同じなのですね。
まずは親クラスを用意しておきます。
./lib/form/doctrine/AnswerForm.class.php
-
...
-
public function configure()
-
{
-
// unset fields
-
'created_at',
-
);
-
foreach ($unset_fields as $value) {
-
$this->offsetUnset($value);
-
}
-
//////////////////////////////////////////////////
-
// body
-
$key = 'body';
-
$this->widgetSchema[$key] = new sfWidgetFormTextarea();
-
$this->widgetSchema->setLabel($key, '回答内容');
-
$this->validatorSchema[$key] = new sfValidatorString();
-
$_max = 1000;
-
$this->validatorSchema[$key]->setOption('max_length', $_max);
-
$this->validatorSchema[$key]->setOption('required', true);
-
$this->validatorSchema[$key]->setMessage('required', '回答内容は必須です');
-
}
そして、回答投稿用のフォームクラスを作成します。
question_idは
./lib/form/doctrine/PostAnswerForm.class.php
-
<?php
-
class PostAnswerForm extends AnswerForm
-
{
-
public function configure()
-
{
-
parent::configure();
-
'user_id',
-
'relevancy_up',
-
'relevancy_down',
-
);
-
foreach ($unset_fields as $value) {
-
$this->offsetUnset($value);
-
}
-
//////////////////////////////////////////////////
-
// question_id
-
$key = 'question_id';
-
$this->widgetSchema[$key] = new sfWidgetFormInputHidden();
-
}
-
public function updateObject($values = null)
-
{
-
$object = parent::updateObject($values);
-
$object->setUserId(sfContext::getInstance()->getUser()->getSubscriberId());
-
return $object;
-
}
-
}
なにかと大変ですが、テンプレートで指定しているルーティングルールを追加しておきます。
また、本家のリポジトリを覗くと色々と定義がされているのであわせておきました。
./apps/frontend/config/routing.yml
-
add_answer:
-
url: /add_answer
-
param: { module: answer, action: add }
最後はaddアクションの準備です。
./apps/frontend/moules/answer/actions/actions.class.php
-
...
-
public function executeAdd(sfWebRequest $request)
-
{
-
$this->form = new PostAnswerForm();
-
$this->form->bind($request->getParameter('answer'));
-
if ($this->form->isValid()) {
-
$this->answer = $this->form->save();
-
return sfView::SUCCESS;
-
}
-
return sfView::ERROR;
-
}
本家と異なる点は処理が正しく行われたかどうかをsfView::ERRORとSUCCESSで返している点です。
これで、それぞれのテンプレートはaddError.php、addSuccess.phpになります。
このメリットはテンプレートをみればどういったステータスなのかが説明がなくてもわかるということですが、
その反面テンプレートの数が増えてしまうというデメリットもあります。
なので、sfView:SUCCESSを返すようにし、テンプレート内でifで分岐するという方法のほうが解りやすいかもしれませんね。
さて、今回はテンプレートを分けましのたのでそれぞれを実装していきます。
まずはエラーがあった場合のテンプレートです。
もう一度フォームを表示させる必要があるので、さきほど作成したコンポーネントを呼ぶようにしています。
さきほどはquestion_idを渡していましたが、ここでは$formそのものを渡すようにしておきます。
./apps/frontend/modules/answer/templates/addError.php
そして、コンポーネントアクションで$formが渡されてきたときはそれをそのまま利用するように書き換えておきます。
./apps/frontend/modules/answer/actions/components.class.php
-
...
-
public function executeAdd(sfWebRequest $request)
-
{
-
if (!$this->form) {
-
$answer = new Answer();
-
$answer->setQuestionId($this->question_id);
-
$this->form = new PostAnswerForm($answer);
-
}
-
}
これでエラーがあったときは既存のコンポーネントを再利用して入力フォームを表示します。
次に正常終了時の処理です。
投稿された回答を表示するので、回答1つずつをパーシャルとして切り分けておきます。
./apps/frontend/modules/answer/templates/_answer.php
-
<?php use_helper('Date') ?>
-
-
<div class="vote_block" id="vote_<?php echo $answer->getId() ?>">
-
</div>
-
<div>
-
</div>
そして、これを呼び出すテンプレートにinclude_partialを記述します。
まずは、回答投稿が正常に終了した場合のテンプレート
./apps/frontend/modules/answer/templates/addSuccess.php
質問表示のページにも記述しておきます。
こちらはquestionモジュール内のテンプレートのため、パーシャルの呼び出しにanswer/answerとモジュール名を指定しています。
./apps/frontend/moduels/question/tempaltes/showSuccess.php
-
<?php foreach ($question->getAnswer() as $answer): ?>
-
<div>
-
</div>
-
<?php endforeach; ?>
本家チュートリアルのリポジトリでは本文部分が質問と同じくMarkdown対応になってたりするんですが、
まずはこれで回答の実装を終える事にします。
また明日
symfony1.2のフォームを使う事に徐々になれてきました。
そして、sfFormの書き方になれればアクション、コンポーネントはかなりスッキリして気持ちよいものです。
ここまでのソースは以下のリポジトリからチェックアウトすることができます。
http://svn.1ms.jp/public/symfony/askeet12/tags/release_day_10
関連するその他の記事
Comments
Leave a Reply
