LoginSignup
7

More than 3 years have passed since last update.

EC-CUBE4系(Symfony)のtwigをAjax( jQuery)を使ってValidationしてみた。

Last updated at Posted at 2019-11-30

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のレスポンスに過ぎないので、VueReactなどにも応用可能です。
複雑なUIの要件への対応方法の一つとなるかと思います。

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
7