Yii 2 Modal Window
Ad Hoc Modal

Create a button to call modal:

<?= Html::button(Yii::t('app', 'Open'), [
    // ...
    'data' => [
        'toggle' => 'modal',
        'target' => '#modalPopup',
   ],
]) ?>

Create a modal using widget:

yii\bootstrap\Modal::begin([
    'id' => '#modalPopup',
    //...
]);
 
   // Some modal content here
 
yii\bootstrap\Modal::end();
<?php
    //------------------------
    // Image
    //------------------------
    $itemImage = (!empty($model->image_file) ? 
        Yii::$app->homeUrl . 'img/data/' . Yii::$app->controller->id . "/{$model->image_file}" : 
        ''
    );
 
    //------------------------
    // Image Thumbnail
    //------------------------
    if(!empty($itemImage)) {
        echo "<img class='img-thumbnail' src='{$itemImage}' alt='Item Image'>";
    } else {
        echo "<img class='img-thumbnail' src='data:image/png;base64," . 
            base64_encode(\app\models\Tool::generateEmptyPng()) . "'/>";
    }
 
 
    echo Html::a('<i class="fa fa-plus" aria-hidden="true"></i> ' . Yii::t('app', 'Add Image'), 
        ['upload-image', 'id' => $model->id], ['class' => 'btn btn-default']
    );
 
    //------------------------
    // Modal to view image 
    //------------------------
    yii\bootstrap\Modal::begin([
        'header' => '<h4>Item Image</h4>',
        'size'   => yii\bootstrap\Modal::SIZE_LARGE,
        'toggleButton' => [
            'label' => '<i class="fa fa-image" aria-hidden="true"></i> ' . Yii::t('app', 'View Image'), 
            'class' => 'btn btn-default'
        ],
    ]);
 
    if(!empty($itemImage)) {
        echo "<img class='img-fluid' src='{$itemImage}' alt='Item Image'>";
    } else {
        echo "<img class='img-fluid' src='data:image/png;base64," . 
            base64_encode(\app\models\Tool::generateEmptyPng()) . "'/>";
    }
    echo '<div class="modal-footer">';
    echo '    <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>';
    echo '</div>';
 
    yii\bootstrap\Modal::end();  
?>
<?php Pjax::begin(); ?>
    <?= GridView::widget([
        'dataProvider' => $dataProvider,
        'filterModel' => $searchModel,
        'columns' => [
            ['class' => 'yii\grid\SerialColumn'],
 
            'id',
            'account_number',
            'contact',
            'company_name',
             //...
            [   // use Modal window to display Notes
                'attribute' => 'notes',
                'label'     => 'Notes',
                'format'    => ['raw'],
                'value'     => function($data) {
 
                    if (!empty($data["notes"])) {
                        $str = '
                            <!-- Button trigger modal -->
                            <button type="button" 
                               class="btn btn-default btn-sm" 
                                  data-toggle="modal" 
                                  data-target="#modalNotes'.$data['id'].'">
                              <span class="glyphicon glyphicon-option-horizontal"></span>
                            </button>
 
                            <!-- Modal -->
                            <div class="modal fade" id="modalNotes'.$data['id'].'" 
                                 tabindex="-1" 
                                 role="dialog" 
                                 aria-labelledby="modalLabel'.$data['id'].'">
                              <div class="modal-dialog modal-lg" role="document">
                                <div class="modal-content">
                                  <div class="modal-header">
                                    <button type="button" class="close" data-dismiss="modal" aria-label="Close">
                                        <span aria-hidden="true">&times;</span>
                                    </button>
                                    <h4 class="modal-title" id="modalLabel'.$data['id'].'">Notes</h4>
                                  </div>
                                  <div class="modal-body">
                                    <pre>'. $data['notes'] . '</pre>
                                  </div>
                                  <div class="modal-footer">
                                    <button type="button" 
                                       class="btn btn-default" data-dismiss="modal">Close</button>
                                  </div>
                                </div>
                              </div>
                            </div>';
                    } else {
                        $str = "";
                    }
 
                    return ($str);
                },
                //'filter' => ['' => 'All', 1 => 'With Notes']
            ],
 
            ['class' => 'yii\grid\ActionColumn'],
        ],
    ]); ?>
<?php Pjax::end(); ?>

Modal to view or update a record, called from a GridView:

<?= yii\grid\GridView::widget([
    'dataProvider' => $dataProviderPrice,
    'filterModel'  => $searchModelPrice,
    'tableOptions' => ['class' => 'table table-striped table-bordered table-hover'],
    'columns' => [
        // record number column
        ['class' => 'yii\grid\SerialColumn'],  
        'id',
        'item_code',
        'description',
        //['class' => 'yii\grid\ActionColumn'],
        [
            'class' => 'yii\grid\ActionColumn',
            //'template' => '{view} {update} {delete}',
            'buttons' => [
                'view' => function ($url, $model) {
                     return Html::a('<span class="glyphicon glyphicon-eye-open"></span>', $url , ['class' => 'view']);
                },
                'update' => function ($url, $model) {
                     return Html::a('<span class="glyphicon glyphicon-pencil"></span>', $url , ['class' => 'update']);
                },
            ],
        ], 
    ],
]) ?>
 
<?php
    // Attach click events to buttons 'View' and 'Update'
    $this->registerJs(
        "$(document).ready(function() {
            $('.view').click(function(e){
               e.preventDefault();
               $('#modalView').modal('show')
                   .find('.modal-content')
                   .load($(this).attr('href'));
           });
           $('.update').click(function(e){
               e.preventDefault();      
               $('#modalUpdate').modal('show')
                   .find('.modal-content')
                   .load($(this).attr('href'));
           });
       });
    ");
?>
 
<?php
    // Modal for VIEW
    yii\bootstrap\Modal::begin([
        'id'           => 'modalView',
        'header'       => '<b>' . Yii::t('app', 'View') . '</b>',
        'footer'       => Html::submitButton(Yii::t('app', 'Close')),
        //'toggleButton' => ['label' => '<span class="glyphicon glyphicon-eye-open"></span>', 'href' => yii\helpers\Url::to(['view'])],
        'size' => yii\bootstrap\Modal::SIZE_LARGE,
    ]);
    echo "<div id='modal-content'></div>";
    yii\bootstrap\Modal::end();
?>
 
<?php
    // Modal for UPDATE
    yii\bootstrap\Modal::begin([
        'id'           => 'modalUpdate',
        'header'       => '<b>' . Yii::t('app', 'Update') . '</b>',
        'footer'       => Html::submitButton(Yii::t('app', 'Close')),
        //'toggleButton' => ['label' => '<span class="glyphicon glyphicon-pencil"></span>', 'href' => yii\helpers\Url::to(['update'])],
        'size' => yii\bootstrap\Modal::SIZE_LARGE,
    ]);
    echo "<div id='modal-content'></div>";
    yii\bootstrap\Modal::end();
?>

See also: Yii2 Bootstrap Modal

To add a modal window (popup) from the index view actions:

Add renderAjax() call to your action:

public function actionView($id)
{
    if (Yii::$app->request->isAjax) {
        $modal = '';
        $modal .= '<div class="modal-header">';
        $modal .= '  <button type="button" class="close" data-dismiss="modal" aria-label="Close">
            <span aria-hidden="true">&times;</span></button>';
        $modal .= '  <h4 class="modal-title" id="modalLabel">View</h4>';
        $modal .= '</div>';
        $modal .= '<div class="panel-body">';
        $modal .= $this->renderAjax('view', ['model' => $this->findModel($id)]);
        $modal .= '</div>';
        $modal .= '<div class="modal-footer">';
        $modal .= '    <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>';
        $modal .= '</div>';
        return $modal;
    } else {
        return $this->render('view', [
            'model' => $this->findModel($id),
        ]);
    }
}

In the view index, add some AJAX code:

<?php
 
use yii\helpers\Html;
use yii\grid\GridView;
use yii\widgets\Pjax;
 
?>
<div class="price-index">
    <!-- // ... -->
<?php Pjax::begin(); ?>    
    <?= GridView::widget([
        'dataProvider' => $dataProvider,
        'filterModel' => $searchModel,
        'columns' => [
            ['class' => 'yii\grid\SerialColumn'],
 
            'id',
            'item_code',
            'description',
            //...
 
            //['class' => 'yii\grid\ActionColumn'],
            [
                'class' => 'yii\grid\ActionColumn',
                'buttons' => [
                    'view' => function ($url, $model) {
                         return Html::a('<span class="glyphicon glyphicon-eye-open"></span>', 
                           $url, ['class' => 'modal-view', 'data-pjax' => '0']
                         );
                    },
                    'update' => function ($url, $model) {
                         return Html::a('<span class="glyphicon glyphicon-pencil"></span>', 
                           $url, ['class' => 'modal-update', 'data-pjax' => '0']
                         );
                    },
                ],
            ],
        ],
    ]); ?>
 
    <?php
        $this->registerJs(
            "$(document).on('ready pjax:success', function() {  // 'pjax:success' when using pjax
                $('.modal-view').click(function(e){
                   e.preventDefault();
                   $('#modalView').modal('show')
                       .find('.modal-content')
                       .load($(this).attr('href'));
               });
               $('.modal-update').click(function(e){
                   e.preventDefault();      
                   $('#modalUpdate').modal('show')
                       .find('.modal-content')
                       .load($(this).attr('href'));
               });
           });
        ");
 
        yii\bootstrap\Modal::begin([
            'id'           => 'modalView',
            'header'       => '<b>' . Yii::t('app', 'View') . '</b>',
            'footer'       => Html::submitButton(Yii::t('app', 'Close')),
            //'toggleButton' => [
            //   'label' => '<span class="glyphicon glyphicon-eye-open"></span>', 
            //   'href'  => yii\helpers\Url::to(['view'])
            //],
            'size' => yii\bootstrap\Modal::SIZE_LARGE,
        ]);
        echo "<div id='modal-content'></div>";
        yii\bootstrap\Modal::end();
 
        yii\bootstrap\Modal::begin([
            'id'           => 'modalUpdate',
            'header'       => '<b>' . Yii::t('app', 'Update') . '</b>',
            'footer'       => Html::submitButton(Yii::t('app', 'Close')),
            //'toggleButton' => [
            //   'label' => '<span class="glyphicon glyphicon-pencil"></span>', 
            //   'href'  => yii\helpers\Url::to(['update'])
            //],
            echo "<div id='modal-content'></div>";
            'size' => yii\bootstrap\Modal::SIZE_LARGE,
        ]);
 
        yii\bootstrap\Modal::end();
    ?>
 
<?php Pjax::end(); ?>
</div>

Example of confirmation dialog in a view:

<?php
<?= yii\grid\GridView::widget([
    'dataProvider' => $dataProviderPrice,
    'filterModel'  => $searchModelPrice,
    'tableOptions' => ['class' => 'table table-striped table-bordered table-hover'],
    'columns' => [
        // record number column
        ['class' => 'yii\grid\SerialColumn'],  
        'id',
        'item_code',
        'description',
        ['class' => 'yii\grid\ActionColumn'],
        [   // Checkboxes to delete selection
            'class' => 'yii\grid\CheckboxColumn',
           'footer' => getDeleteConfirmationDialog(),  // Call Modal dialog
        ],  
    ],
    'showFooter' => true,
]) ?>
 
<?php
    // Add Javascript code to perform AJAX call (using JQuery)
    $this->registerJs(
        "$(document).ready(function() {
 
            $('#modalConfirmDelete').on('hide.bs.modal', function (event) {
                var activeElement = $(document.activeElement);
                if (activeElement.is('[data-toggle], [data-dismiss]')) {
                    console.log(activeElement);
                    console.log('Button : ' + activeElement[0].textContent);
                    var action = activeElement[0].textContent;
                    switch(action) {
                        case 'Delete': console.log('Deleting selected records. Action: ' + action); delSelected(); break;
                        case 'Cancel': console.log('Canceled deletion. Action: ' + action); break;
                        default:       console.log('Nothing to be deleted. Action: ' + action); break;
                    }
                }
            });
 
        });
 
        function delSelected()
        {
            if ( $( '#w5' ).length ) {
                var keys = $('#w5').yiiGridView('getSelectedRows'); // #w5 = col 5 in GridView for checkboxes
                console.log('Keys selected for deletion: ' + keys);
                $.post({ 
                    url:'". Yii::$app->homeUrl ."price/delete-selected', 
                    data: {keylist: keys},
                    success: function( data ) {
                        console.log('Data Received: ');
                        console.log(data.status);
                        console.log(data.details);
                        console.log(data.data_post);
                        console.log(data.data_get);
                    },
                    dataType: 'json'  // return data type
                });
            } else {
                alert('No items available/selected for deletion.');
            }
        }
    ", \yii\web\View::POS_END);
?>
 
<?php
function getDeleteConfirmationDialog()
{
    $str = '';
    $str .= '<!-- Button trigger modal -->';
    $str .= '<button type="button" class="btn btn-default btn-sm" data-toggle="modal" data-target="#modalConfirmDelete">';
    $str .= '  <span class="fa fa-trash-o" aria-hidden="true"></span>';
    $str .= '</button>';
    $str .= '';
 
    $str .= '<!-- Modal -->';
    $str .= '<div class="modal fade" id="modalConfirmDelete" tabindex="-1" role="dialog" aria-labelledby="modalLabelConfirmDelete">';
    $str .= '  <div class="modal-dialog modal-sm" role="document">';
    $str .= '    <div class="modal-content">';
    $str .= '      <div class="modal-header">';
    $str .= '        <button type="button" class="close" data-dismiss="modal" aria-label="Close">';
    $str .= '          <span aria-hidden="true">&times;</span>';
    $str .= '        </button>';
    $str .= '        <h4 class="modal-title" id="modalLabelConfirmDelete">Delete</h4>';
    $str .= '      </div>';
    $str .= '      <div class="modal-body"><p>Delete selected records?</p></div>';
    $str .= '      <div class="modal-footer">';
    $str .= '          <button type="button" class="btn btn-alert" data-dismiss="modal">Delete</button>';
    $str .= '          <button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>';
    $str .= '      </div>';
    $str .= '    </div>';
    $str .= '  </div>';
    $str .= '</div>';
 
    return $str;
}
?>

The previous modal definition within getDeleteConfirmationDialog() can be refactored at the end of the view as:

<?php
<?= yii\grid\GridView::widget([
    'dataProvider' => $dataProviderPrice,
    'filterModel'  => $searchModelPrice,
    'tableOptions' => ['class' => 'table table-striped table-bordered table-hover'],
    'columns' => [
        // record number column
        ['class' => 'yii\grid\SerialColumn'],  
        'id',
        'item_code',
        'description',
        ['class' => 'yii\grid\ActionColumn'],
        [   // Checkboxes to delete selection
            'class' => 'yii\grid\CheckboxColumn',
            'footer' => \yii\helpers\Html::button('<i class="fa fa-trash-o" aria-hidden="true"></i>', [
                    'id'          => 'btnDelete',
                    'class'       => 'btn btn-default btn-sm',
                    'data-toggle' => 'modal',
                    'data-target' => '#modalConfirmDelete',
                ]),  // Call Modal dialog
        ],  
    ],
    'showFooter' => true,
]) ?>
 
<?php
    // Modal for Delete
    yii\bootstrap\Modal::begin([
        'id'     => 'modalConfirmDelete',
        'header' => '<b>' . Yii::t('app', 'Delete') . '</b>',
        'footer' => 
            Html::submitButton(Yii::t('app', 'Delete'), [
                'class' => 'btn btn-danger',  'data-dismiss' => 'modal', 'data-action' =>'Delete'
            ]) . " " . 
            Html::submitButton(Yii::t('app', 'Cancel'), [
                'class' => 'btn btn-default', 'data-dismiss' => 'modal', 'data-action' =>'Cancel'
            ]),
        //'toggleButton' => ['label' => '<span class="fa fa-trash-o"></span>', 'href' => yii\helpers\Url::to(['delete-selected'])],
        'size' => yii\bootstrap\Modal::SIZE_SMALL,
    ]);
    echo "<div id='modal-content'>";
    echo "  <p>### Delete selected records?</p>";
    echo "</div>";
    yii\bootstrap\Modal::end();
?>
 
<?php
    // Add Javascript code to perform AJAX call (using JQuery)
    $this->registerJs(
        "$(document).ready(function() {
 
            $('#modalConfirmDelete').on('hide.bs.modal', function (event) {
                var activeElement = $(document.activeElement);
                if (activeElement.is('[data-toggle], [data-dismiss]')) {
                    console.log(activeElement);
                    console.log('Button : ' + activeElement[0].textContent);
                    var action = activeElement[0].textContent;
                    switch(action) {
                        case 'Delete': console.log('Deleting selected records. Action: ' + action); delSelected(); break;
                        case 'Cancel': console.log('Canceled deletion. Action: ' + action); break;
                        default:       console.log('Nothing to be deleted. Action: ' + action); break;
                    }
                }
            });
 
        });
 
        function delSelected()
        {
            if ( $( '#w5' ).length ) {
                var keys = $('#w5').yiiGridView('getSelectedRows'); // #w5 = col 5 in GridView for checkboxes
                console.log('Keys selected for deletion: ' + keys);
                $.post({ 
                    url:'". Yii::$app->homeUrl ."price/delete-selected', 
                    data: {keylist: keys},
                    success: function( data ) {
                        console.log('Data Received: ');
                        console.log(data.status);
                        console.log(data.details);
                        console.log(data.data_post);
                        console.log(data.data_get);
                    },
                    dataType: 'json',  // return data type
                });
            } else {
                alert('No items available/selected for deletion.');
            }
        }
    ", \yii\web\View::POS_END);
?>

In controller, add the action to call. Eg: actionDeleteSelected()

//...
class PriceController extends Controller
{
    public function behaviors()
    {
        return [
            'access' => [
                'class' => AccessControl::className(),
                'rules' => [
                    [
                        'actions' => [
                            'index', 'delete-selected', //... 
                        ],
                        'allow' => true,
                        'roles' => ['@'],  // @ = Authenticated users
                    ],
                    //...
                ],
            ],
            //...
        ];
    }
 
    //...
 
    /**
     * Deletes selected Price models for specified customer.
     * @return mixed
     */
    public function actionDeleteSelected()
    {
        if (Yii::$app->request->post('keylist')) {
            $keys = Yii::$app->request->post('keylist');
            if (!is_array($keys)) {
                return \yii\helpers\Json::encode([
                    'status'  => 'error',
                    'details' => 'Not an array',
                ]);
            }
 
            if (count($keys) > 0) {
                $model = $this->findModel($keys[0]);
                $customer_id = $model->customer_id;
 
                // Method 1: Delete each record (using a loop)
                //foreach ($keys as $key => $id) {
                //    $model = $this->findModel($id);
                //    $model->delete();
                //}
 
                // Method 2: Delete each record (using single query)
                $result = (new \yii\db\Query)
                    ->createCommand()
                    ->delete('price', ['in', 'id', $keys])
                    ->execute();
 
                // Return Method 1: JSON
                //return \yii\helpers\Json::encode([
                //    'status'    => 'success',
                //    'details'   => "Deleted items:\n" . implode(", ", $keys),
                //]);
 
                // Return Method 2: Redirect
                return $this->redirect(['customer/view', 'id' => $customer_id, 'tab'=>'price']);
            }
        }
        return \yii\helpers\Json::encode([
            'status'    => 'error',
            'details'   => 'Not POST data',
        ]);
    }

See also: