47歳でやむなくセミリタイア

病気のためセミリタイアをすることに。現在は週20時間程度のバイトをしています。その他、雑多なことを記録として書いています。

Yii2の使い方: GridViewのアイテムをModalで変更できるようにする方法

日曜日だというのに、天候は今一つ。さすがに鬱々してきたので、散歩でもしてきますかね。

さて、またYii2の話題です。先日、変更/追加を行う場合に、ページを遷移しないでGridView上のModalで変更するというのをやりました。 結構苦労させられたので、記憶が薄れないうちにメモを残しておきたいと思います。

modal/Member.phpを編集する場合を記載します。giiで生成されたCRUDがベースです。ここでは変更ですが、追加や表示も同じ方法で可能です。

views/member/index.php

GridViewに「編集」ボタンを追加

GridViewのwidgetの'columns'に以下を記載します。重要なのは以下の点。

  • activity-update-linkクラスを目印にする
  • Pjaxを使っている場合には、data-pjax => '0' を指定
            [
                'class' => 'yii\grid\ActionColumn',
                'header' => '編集',
                'template' => '{update}',
                'buttons' => [
                    'update' => function () {
                        return Html::a('<i class="fa fa-pencil-square-o"></i>編集', '#', [
                            'class' => 'btn btn-primary activity-update-link',
                            'data-pjax' => '0'
                        ]);
                    }
                ]
            ]

使用するModal

「編集」ボタンのクリック時に表示されるModalを記載します。 ここではbootstrapのmodalを使っています。modalとmodal-bodyにidを付けておきます。

<div class="modal fade" id="update-modal" role="dialog" data-backdrop="static">
    <div class="modal-dialog modal-dialog-centered">
        <div class="modal-content">
            <div class="modal-header">
                <h5 class="modal-title">編集</h5>
                <button type="button" class="close" data-dismiss="modal" aria-label="Close">
                    <span aria-hidden="true">&times;</span>
                </button>
            </div>
            <div class="modal-body" id="update-modal-body">
            </div>
        </div>
    </div>
</div>

modal表示のスクリプト

「編集」ボタンが押されたら、updateのviewをAjaxでmodal-bodyに読み込んだ後でmodalを表示するスクリプトを追加します。

$(document).on('click', '.activity-update-link', function() {
    $.get(
        'update',
        {
            id: $(this).closest('tr').data('key'),
        },
        function (data) {
            $.fn.modal.Constructor.prototype.enforceFocus = function() {};
            $('#update-modal-body').html(data);
            $('#update-modal').modal();
        }
    );
});

更新のスクリプト

modalの更新フォームのbeforeSubmitイベントで、Ajaxで更新するスクリプトを追加します。

注記: submit時のclient validationを行いたいため、beforeSubmitイベントを使います。

$(document).on('beforeSubmit', '#update-member-form',  function() {
    $('body').removeClass('modal-open');
    $('.modal-backdrop').remove();
    $('#update-modal').modal('hide');
    $.post($(this).attr('action'),
        $(this).serialize(),
        function(data, status, xhr) {
            $('#member-grid-view').yiiGridView('applyFilter');
        }
    );
    return false;
});

更新成功時に「$('#member-grid-view').yiiGridView('applyFilter');」を行うと、GridViewに変更が反映(再読み込み)されます。

controllers/MemberController.php

更新成功時のredirect()を止めるのと、renderをrenderAjaxに変更を行います。

    public function actionUpdate($id)
    {
        $model = $this->findModel($id);

        if ($model->load(Yii::$app->request->post()) && $model->save()) {
            return;
        }

        return $this->renderAjax('update', [ 
            'model' => $model,
        ]);
    }

views/member/_form.php

更新のフォームにidを付けます。

<div class="member-form">
    <?php $form = ActiveForm::begin(['options' => ['id' => 'update-member-form']]); ?>
    <?= $form->field($model, 'company')->textInput() ?>
    <?= $form->field($model, 'username')->textInput() ?>
    <?= Html::submitButton('保存', ['class' => 'btn btn-success']) ?>
    <?php ActiveForm::end(); ?>
</div>

modalで変更できるようにするための変更点は以上です。

注意点

注意点を記載しておきます。結構落とし穴が多かった。

Ajax validationと同時に使う場合の注意点

member/updateへの要求がAjaxで行われるようになりますので、そのままではvalidation要求と更新要求との区別がつきません。更新($.post)の際に追加でGETのパラメータを与えて区別するなどの工夫が必要です。

modal('hide')時の注意点

bootstrapの問題で、Ajaxで内容を読み込んだmodalをmodal('hide')した場合、modalの背景が残ってしまうことがあります。たまに残るという現象でちょっと厄介。

pistachio0416.hatenablog.com

これを防ぐために、modal('hide')する際に、以下のお約束を実行します。

    $('body').removeClass('modal-open');
    $('.modal-backdrop').remove();

modal中でSelect2を使う場合の注意点

これもbootstrapの問題で、Select2の選択肢が表示されない場合が、たまに発生します。

github.com

これを防ぐには、modalを表示する前に以下を実行すると良いらしい(bootstrap4の場合は_enforceFocus)。 Yii2のvendor/select2/select2/docs/pages/02.troubleshooting/02.common-problems/docs.mdにも同じ記載があります。

// Do this before you initialize any of your modals $.fn.modal.Constructor.prototype.enforceFocus = function() {};

JavaScript (jquery) は、Ajaxで読み込まれる場所には置かない

今回の場合、views/member/_form.phpに登録のスクリプトを置きたくなりますが、そうしてしまうと、何度も同じスクリプトが実行されてしまって、updateが何度も呼ばれるという事態に陥ります。

views/member/index.phpのように一度しか呼ばれない部分においてください。もしかすると別にいい方法があるかもしれません。

う~ん。長い。