Twigとjs間の値の受け渡し

EC-CUBEの画面の表示内容の制御を行う際に、
TwigとJS間の値の受け渡しで詰まったので、調べたことをまとめます。

#### 《 目次 》
1. js → Twig は NG、Twig → js は OK
2. Ajax(非同期通信)でincludeする方法

# js → Twig は NG、Twig → js は OK
TwigはPHPで実行されて、javascriptが動く前に動作が完了するので、javasriptの変数をTwigで使用するのは原理的に不可能です。

https://teratail.com/questions/101077

### TwigでControllerから渡した値を使用して、jsにセットする方法

```php:ShoppingController.php
    const PAYMENT_LIST = [
        PaymentType::CREDIT_CARD, // int
        PaymentType::POSTPAID,    // int
        PaymentType::PAYMENT_NONE // int
    ];

    /**
     * 支払方法選択画面を表示する
     *
     * @Route("/order/payment", name="order_payment", methods={"POST"})
     * @Template("Order/payment.twig")
     */
    public function payment(Request $request)
    {

        // $Order を取得

        return [
            'form' => $form->createView(),
            'Order' => $Order,
            'PaymentList' => self::PAYMENT_LIST,
        ];
    }
```

```twig:Order/payment.twig
{% block javascript %}
    <script>
        $(function() {
        
            var payments = [ {{ PaymentList|join(',') }} ];

            var checkedPayments = [];
            $('input:checked').each(function () {
                checkedPayments.push(Number($(this).val()));
                if ($.inArray(Number($(this).val()),payments) > -1) {
                    $(this).addClass('d_none');
                } else {
                    $(this).removeClass('d_none');
                }
                if (checkedPayments.every(payment =>payments.includes(payment))) {
                    $('#button').parent().addClass('none');
                } else {
                    $('.#button').parent().removeClass('none');
                }
            });
        });
    </script>
{% endblock javascript %}        

{% block main %}
    <ul class="radioUl jsCheckList require-input">
        {% for key, item in form.payments.children %}
            <li>
                <label>
                    <input type="radio" id="{{ item.vars.id }}" name="{{ item.vars.full_name }}" value="{{ item.vars.value }}" {{ item.vars.checked ? 'checked' : '' }}{% if key == 1 %} disabled{% endif %}><span>{{ item.vars.label }}</span>
                </label>
            </li>
        {% endfor %}
    </ul>
    {{ form_errors(form.corporate_flg) }}
{% endblock %}

```

#### 《解説》

##### 1.ControllerからTwigに値を渡し、Twigで値を出力する
``` {{ 値 }} ```で出力可能
配列も渡すことができます。

##### 2.Twigからjsに値を渡す
script内にでもTwigの記述方法が有効なので、1の方法で出力可能

##### 3.《応用》配列とinputのvalue等と比較する方法
###### 3-1. Twigの配列をjsで扱う

``` {{ 値 }} ```は文字列として扱われるため、jsの配列にそのまま置換することはできません。
そのため、以下の方法でjsの配列にセットする必要があります。
```twig
// 1. カンマ区切りの文字列として出力し、jsの配列の初期値としてセット
var payments = [{{ PaymentList|join(',') }}];

// 2. forで取り出してjsの配列に追加
var payments = [];
{% for Payment in PaymentList %}
    payments.push({{ Payment }});
{% endfor %}

```

###### 3-2. 配列に値が含まれているか比較
```twig
if ($.inArray(Number($(this).val()),payments) > -1) {
```
jQueryの```inArray()```を使用

https://www.sejuku.net/blog/33745

>inArrayメソッドは、配列を検索する時に「型」も同時にチェックしているという点に注意が必要なわけです。

※ int型の```payments```に型を合わせるために```Number()```でキャスト

※ クラスの出し分けは```addClass(),removeClass()```を使用
※ ```$('input:checked').each(function () {```で複数のinputに適用

###### 3-3. 配列同士の比較
```twig
// checkedPaymentsの要素がすべてpaymentsに含まれるかどうか
if (checkedPayments.every(payment =>payments.includes(payment))) {

```

■ 配列.some()
意味：配列内の要素で1つでも条件に合致するものがあるかどうか？
戻り値：真偽値

■ 配列.every()
意味：配列内のすべての要素が、条件に合致するかどうか？
戻り値：真偽値

https://ics.media/entry/200825/#find2


###### 《 おまけ 》 form_widgetは分解できる！
```input[type="radio"]```の選択肢ごとに挙動を変えたい場合など、for文で分解することができる！
``` twig
 <ul class="radioUl jsCheckList require-input">
        {% for key, item in form.payments.children %}
            <li>
                <label>
                    <input type="radio" id="{{ item.vars.id }}" name="{{ item.vars.full_name }}" value="{{ item.vars.value }}" {{ item.vars.checked ? 'checked' : '' }}{% if key == 1 %} disabled{% endif %}><span>{{ item.vars.label }}</span>
                </label>
            </li>
        {% endfor %}
    </ul>
```

# Ajax(非同期通信)でincludeする方法

#### そもそもAjaxって何？？
非同期通信とは、一部の情報をサーバーに送信して、それを受け取り反映させる仕組みのこと。

つまり、**画面遷移せずにバックエンド処理する**手法です。

https://qiita.com/hisamura333/items/e3ea6ae549eb09b7efb9

AjaxはEC-CUBEでもいろいろな場面で使用しています。
例）・検索ボタンを押したら、バックエンドで処理結果を返却して表示する
　　・ファイルをアップロードする際、バックエンドで処理を行いながら、画面に進捗を表示する
  etc...

### 今回やりたかったこと

ロード時間の短縮のために、画面読み込みを同時並行で行いたい。
（カート画面からおすすめ商品エリアを切り離して読み込みする）

#### 《実装内容》

``` php:CartController.php
    /**
     * カート画面.
     *
     * @Route("/cart", name="cart", methods={"GET", "POST"})
     * @Template("Cart/top.twig")
     */
    public function index(Request $request)
    {
        
        // カート処理
    
         return ['Carts' => $ViewCarts];
    }
```
```twig:Cart/top.twig
{% block javascript %}
    <script>
        $(function() {
            // おすすめ商品の表示
            viewLoading('recommend_area');
            $.ajax({
                url: '{{ url('recommend_group_list', {'type': 1}) }}',
                type: 'GET',
            }).done(function(data) {
                $.when(
                    $('#recommend_area').html(data)
                ).done(function(){
                    // 商品画像スライド表示
                    slickload();
                    // ボタン連打対応
                    doubleClickGuard(".recommend_item_view_btn");
                });
            }).fail(function() {
                console.log('Failed');
            });
        });

        // ローディング中画像表示
        function viewLoading(eid) {
            $('#'+eid).html('<div class="loading_icon"></div>');
        }
    </script>
{% endblock javascript %}

{% block main %}

    {# カート 表示内容 #}


    {# おすすめ商品 #}
    <div id="recommend_area" class="completeSec"></div>
    
{% endblock %}
```
``` php:RecommendController.php
    /**
     * おすすめ商品の表示
     * ※会員毎に出しわけます。
     * type -> 1:カート、2:注文完了
     *
     * @Route("/recommend/{type}/groups", name="recommend_group_list", methods={"GET"})
     * @Template("Recommend/group_list.twig")
     */
    public function loadRecommendItemGroups(Request $request, $type)
    {
        log_info("おすすめ商品の表示 : " . $type);
        if ($this->isGranted('IS_AUTHENTICATED_FULLY')) {
            // ログインしている場合は、会員情報を取得
            $Customer = $this->getUser();
        } else {
            $Customer = null;
        }

        // おすすめ商品の取得
        $recommendItemGroups = $this->recommendItemGroupRepository->findRecommendGroupByCustomerRank($type, $Customer);

        return ['RecommendItemGroups' => $recommendItemGroups];
    }
```
``` twig:Recommend/group_list.twig
{% if RecommendItemGroups|length > 0 %}
    <section>

        {# おすすめ商品　表示内容 #}
    
    </section>
{% endif %}
```

#### 《解説》
##### 1.カート画面表示
Controllerで処理をしてTwigでカート画面を表示する
Twig読み込み → Ajax発火 → url(recommend_group_list)にアクセス
```
    $.ajax({
        url: '{{ url('recommend_group_list', {'type': 1}) }}',
        type: 'GET',   
    })
```
##### 2.おすすめ商品を取得
Controllerで処理をしてTwigを返却

:::note warn
ただし、返却先はAjaxのため、そのまま画面表示することはできないのでので要注意！
:::


##### 3.Ajaxで表示処理

Ajax処理が完了したら、カート画面上のおすすめ商品表示エリアに、戻り値のtwigの内容をセットする

```twig
    
    }).done(function(data) {
        // 成功した場合の処理
        $.when(
            $('#recommend_area').html(data)
        ).done(function(){
            // 商品画像スライド表示
            slickload();
            // ボタン連打対応
            doubleClickGuard(".recommend_item_view_btn");
        });      
    }).fail(function() {
        // 失敗した場合の処理  
         console.log('Failed');
    });
```

```.when()```と```.done()```を使って、Twigの内容が完全に読み込まれた後に、画像スライド表示のjsを起動します。

```.when()```の記述方法は少し特殊なので、以下を参考にすること。

https://on-ze.com/archives/7778