5
Help us understand the problem. What are the problem?

More than 1 year has passed since last update.

posted at

updated at

Organization

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

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の要件への対応方法の一つとなるかと思います。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Sign upLogin
5
Help us understand the problem. What are the problem?