FormのValidationをする時には。。。
EC-CUBE4系でフォームを書く時に、twig
では下記のように書くことがほとんどでしょう。
<div>
{{ form_widget(form.name.name01) }}
{{ form_errors(form.name.name01) }}
</div>
こうすることによって、form_widget
で動的にinput type
が生成され、かつvalidation
のエラーがあった時には、form_errors
でエラー用の文言が生成されます。
<input type="text" id="entry_name_name01" name="entry[name][name01]" required="required"><!-- ここがform_widget で生成されて-->
<p class="ec-errorMessage">入力されていません。</p><!-- ここがform_errorsで生成される-->
これでも一つの完成された形なのですが、このform-errors
にチェック内容を反映させるためには、一度、form全体をsubmit
をさせる必要があります。(当然ですが、、、)
formをAPIで処理してみよう!
昨今のUI要件は複雑でform全体をsubmit
するのではなく、例えばダイアログからユーザに入力をさせて、form
全体を更新することなくデータの取得・更新をする要望も多いかと思います。
その時は、下記のようにController
からJSON
を返すようにして、javascript
を使って画面の内容を動的に変化させるようにしましょう。
Controllerの例
//Controllerの一例
class SomeController extends AbstractController
{
/**
* 編集画面.
*
* @Route("/api/edit", name="api_edit")
*/
public function editApi(Request $request)
{
if (!$request->isXmlHttpRequest()) {
throw new BadRequestHttpException();//API通信のみ受け付ける
}
$builder = $this->formFactory
->createBuilder(SomeDataType::class, $SomeDataClass);
$form = $builder->getForm();
$form->handleRequest($request);//ここでValidation
if($form->isSubmitted() && $form->isValid() ){
$this->entityManager->persist($SomeDataClass);//何かを更新する
//成功した時のレスポンス(HTTPステータスはデフォルトの200で返却)
return $this->json([
'id' => $SomeDataClass->getId(),
'name' => $SomeDataClass->getName(),
]);
}
$errors = [];
//該当する要素のキー(この場合はnameなど)=>エラーメッセージの配列を作成する
foreach($form->getErrors(true) as $error){
if($error->getCause()){
preg_match_all(
'/\[.*?\]/',
$error->getCause()->getPropertyPath(),
$matches);
$element = str_replace(['[',']'],'',array_pop($matches[0]));
}else{
$element = $error->getOrigin()->getConfig()->getName();
}
$errors[$element] = $error->getMessage();
}
//HTTPステータス500を返却する。ステータスコードは、任意で好きなものに変更してください。
return $this->json($errors,500);
}
出力されるJSON
上記のContollerによって生成されるJSON
は下記の通りです。
成功した時
{
id: 1,
name: "サンプル 例です"
}
####失敗した時
{
name: "入力されていません。"
}
レスポンス(500)で返却される
POST http://localhost/api/sample/edit 500 (Internal Server Error)
Twig側の記述
ポイントとなるのは、いかにform-widgetで生成されたname
属性をAPIに渡してやるかだと思います。
Twigサンプル
<div id="change_something_div">
<div class="main">
<div class="panel">
<div class="u-mg-t__20">
{# お名前 #}
<dl>
<dt class="c-title__grayHeading">
<h3>お名前</h3>
<span>必須</span>
</dt>
<dd class="ec-halfInput">
<div>
姓
</div>
<div>
{{form_widget(form.name.name01 | e )}}
<p class="ec-errorMessage name01"></p><!-- class属性に項目名を入れておく -->
</div>
<div>
名
</div>
<div>
{{form_widget(form.name.name02 | e )}}
<p class="ec-errorMessage name02"></p><!-- class属性に項目名を入れておく -->
</div>
</dd>
</dl>
</div>
<div id="change_something">
<a class="button">変更する</a>
</div>
</div>
</div>
</div>
jQueryサンプル
//変更された時の対応
$("[id=change_something]").on("click",function(){
//親要素の配下にある、入力項目を取得する
const inputElements = $(this).parent().find('input');
const selectElements = $(this).parent().find('select');
//エラーメッセージを消去
const messageElements= $(this).parent().find('p');
$.each(messageElements,function(index,messageElement){
$(messageElement).text('');
$(messageElement).parent().removeClass('error');
});
const params = {};
//input要素を抽出してパラメータにセット
$.each(inputElements,function(index,inputElement){
const name = $(inputElement).attr('name');
params[name] = $(inputElement).val();
});
//ドロップダウンリストなどの選択内容はこちらで抽出
$.each(selectElements,function(index,selectElement){
const name = $(selectElement).attr('name');
params[name] = $(selectElement).val();
});
$.ajax({
type: "POST",
url: "{{url('api_change_somethig')}}",
data:params
})
.done(function (data) {
//成功した時は入力内容を画面に反映する
$('#some_where').text(data.name);
}).fail(function (data) {
//失敗した時は、エラー内容を所定の場所に出力する
$.each(data.responseJSON,function(targetElement,message){
$.each(messageElements,function(index,messageElement){
if($(messageElement).hasClass(targetElement)){
$(messageElement).text(message);
$(messageElement).parent().addClass('error');
}
});
})
});
});
最後に
今回のサンプルはjQueryを使っていますが、Contollerが返しているのはAPIのレスポンスに過ぎないので、Vue
やReact
などにも応用可能です。
複雑なUIの要件への対応方法の一つとなるかと思います。