Магазин на Yii2, часть 18. Корзина покупателя, часть вторая

14.07.2019

Теги: AJAXJSONWeb-разработкаYii2ИнтернетМагазинКаталогТоваровКорзинаПрактикаФреймворк

Теперь надо изменить форму добавления в корзину на странице товара. Но вот что плохо — после добавления товара в корзину происходит редирект на страницу корзины. Это не очень удобно, поэтому будем отправлять POST-запрос с использованием AJAX. И после добавления товара в корзину будем показывать модальное окно с содержимым корзины. Кроме того, в модальном окне будут две кнопки — «Продолжить покупки» и «Оформить заказ».

Итак, изменяем форму на странице товара:

<?php
/*
 * Страница товара, файл views/catalog/product.php
 */

use app\components\TreeWidget;
use app\components\BrandsWidget;
use app\components\ChainWidget;
use yii\helpers\Url;
use yii\helpers\Html;
?>

<section>
    <div class="container">
        <div class="row">
            <div class="col-sm-3">
                <h2>Каталог</h2>
                <div class="category-products">
                    <?= TreeWidget::widget(); ?>
                </div>

                <h2>Бренды</h2>
                <div class="brand-products">
                    <?= BrandsWidget::widget(); ?>
                </div>
            </div>

            <div class="col-sm-9">
                <?= ChainWidget::widget(['itemCurrent' => $product['category_id']]); ?>
                <h1><?= Html::encode($product['name']); ?></h1>
                <div class="row">
                    <div class="col-sm-5">
                        <div class="product-image">
                            <?=
                            Html::img(
                                '@web/images/products/large/'.$product['image'],
                                ['alt' => $product['name']]
                            );
                            ?>
                        </div>
                    </div>
                    <div class="col-sm-7">
                        <div class="product-info">
                            <p class="product-price">
                                Цена: <span><?= $product['price']; ?></span> руб.
                            </p>
                            <form method="post"
                                  action="<?= Url::to(['basket/add']); ?>"
                                  class="add-to-basket">
                                <label>Количество</label>
                                <input name="count" type="text" value="1" />
                                <input type="hidden" name="id"
                                       value="<?= $product['id']; ?>">
                                <?=
                                Html::hiddenInput(
                                    Yii::$app->request->csrfParam,
                                    Yii::$app->request->csrfToken
                                );
                                ?>
                                <button type="submit"
                                        class="btn btn-warning">
                                    <i class="fa fa-shopping-cart"></i>
                                    Добавить в корзину
                                </button>
                            </form>
                            <p>Артикул: 1234567</p>
                            <p>Наличие: На складе</p>
                            <p>
                                Бренд:
                                <a href="<?= Url::to(['catalog/brand', 'id' => $brand['id']]); ?>">
                                    <?= Html::encode($brand['name']); ?>
                                </a>
                            </p>

                        </div>
                    </div>
                </div>
                <div class="product-descr">
                    <?= $product['content']; ?>
                </div>
                <?php if (!empty($similar)): /* популярные товары */ ?>
                    <h2>Популярные товары</h2>
                    <div class="row">
                        <?php foreach ($similar as $item): ?>
                            <div class="col-sm-4">
                                <div class="product-wrapper text-center">
                                    <?=
                                    Html::img(
                                        '@web/images/products/medium/'.$item['image'],
                                        ['alt' => $item['name'], 'class' => 'img-responsive']
                                    );
                                    ?>
                                    <h2><?= $item['price']; ?> руб.</h2>
                                    <p>
                                        <a href="<?= Url::to(['catalog/product', 'id' => $item['id']]); ?>">
                                            <?= Html::encode($item['name']); ?>
                                        </a>
                                    </p>
                                    <a href="#" class="btn btn-warning">
                                        <i class="fa fa-shopping-cart"></i>
                                        Добавить в корзину
                                    </a>
                                    <?php
                                    if ($item['new']) { // новинка?
                                        echo Html::img(
                                            '@web/images/home/new.png',
                                            ['alt' => 'Новинка', 'class' => 'new']
                                        );
                                    }
                                    if ($item['sale']) { // распродажа?
                                        echo Html::img(
                                            '@web/images/home/sale.png',
                                            ['alt' => 'Распродажа', 'class' => 'sale']
                                        );
                                    }
                                    ?>
                                </div>
                            </div>
                        <?php endforeach; ?>
                    </div>
                <?php endif; ?>
            </div>
        </div>
    </div>
</section>

Отправляем ajax-запрос

Теперь добавим css-класс add-to-basket для всех форм добавления товара в корзину. Он нам потребуется, когда будем писать js-код отправки ajax-запроса. Для начала все сделаем предельно просто — при отправке запроса будем отправлять обратно массив содержимого корзины.

<?php
namespace app\controllers;

use app\models\Basket;
use Yii;

class BasketController extends AppController {

    public function actionIndex() {
        $basket = (new Basket())->getBasket();
        return $this->render('index', ['basket' => $basket]);
    }

    public function actionAdd() {

        $basket = new Basket();

        /*
         * Данные должны приходить методом POST; если это не
         * так — просто показываем корзину
         */
        if (!Yii::$app->request->isPost) {
            return $this->redirect(['basket/index']);
        }

        $data = Yii::$app->request->post();
        if (!isset($data['id'])) {
            return $this->redirect(['basket/index']);
        }
        if (!isset($data['count'])) {
            $data['count'] = 1;
        }

        // добавляем товар и получаем содержимое корзины
        $basket->addToBasket($data['id'], $data['count']);

        if (Yii::$app->request->isAjax) { // с использованием AJAX
            $content = $basket->getBasket();
            return print_r($content, true);
        } else { // без использования AJAX
            return $this->redirect(['basket/index']);
        }
    }
}
jQuery(document).ready(function($) {
    /*
     * Добавление товара в корзину с использованием AJAX
     */
    $('.add-to-basket').on('submit', function (event) {
        var action = $(this).attr('action');
        var method = $(this).attr('method');
        if (method == undefined) {
            method = 'get';
        }
        var data = $(this).serialize();
        $.ajax({
            url: action,
            type: method,
            data: data,
            dataType: 'text',
            success: function (response) {
                console.dir(response);
            },
            error: function () {
                alert('Произошла ошибка при добавлении товара в корзину');
            }
        });
        event.preventDefault();
    });
});

Добавляем модальное окно

Будем использовать компонент Bootstrap «Модальное окно», тем более, что в Yii2 есть готовый виджет. Открываем на редактирование файл views/layouts/main.php и добавляем перед закрывающим тегом </body>:

<footer>
    <div class="container">
        Copyright © 2018 E-SHOPPER Inc. All rights reserved.
    </div>
</footer>

<?php
$footer =
<<<FOOTER
<button type="button" class="btn btn-default" data-dismiss="modal">
    Продолжить покупки
</button>
<button type="button" class="btn btn-warning">
    Оформить заказ
</button>
FOOTER;
Modal::begin([
    'header' => '<h2>Корзина</h2>',
    'id' => 'basket-modal',
    'size'=>'modal-lg',
    'footer' => $footer
]);
Modal::end();
unset($footer);
?>

<?php $this->endBody() ?>
</body>
</html>
<?php $this->endPage() ?>

Этот виджет добавит на все страницы следующий html-код:

<div id="basket-modal" class="fade modal" role="dialog" tabindex="-1">
    <div class="modal-dialog">
        <div class="modal-content">
            <div class="modal-header">
                <button type="button" class="close" data-dismiss="modal"
                        aria-hidden="true">&times;</button>
                <h2>Корзина</h2>
            </div>
            <div class="modal-body">

            </div>
            <div class="modal-footer">
                <button type="button" class="btn btn-default" data-dismiss="modal">
                    Продолжить покупки
                </button>
                <button type="button" class="btn btn-primary">
                    Оформить заказ
                </button>
            </div>
        </div>
    </div>
</div>

И нам теперь после получения ответа сервера надо будет вставить внутрь modal-body html-код корзины и показать модальное окно.

Показываем модальное окно, первый вариант

Давайте изменим в js-коде тип ответа сервера на json и на стороне сервера будем отправлять ответ в формате json:

jQuery(document).ready(function($) {
    /*
     * Аккордеон для меню каталога в левой колонке
     */
    $('#accordion').dcAccordion({
        speed: 'fast'
    });

    /*
     * Добавление товара в корзину с использованием AJAX
     */
    $('.add-to-basket').on('submit', function (event) {
        var action = $(this).attr('action');
        var method = $(this).attr('method');
        if (method == undefined) {
            method = 'get';
        }
        var data = $(this).serialize();
        $.ajax({
            url: action,
            type: method,
            data: data,
            dataType: 'json',
            success: function (response) {
                /*
                 * Создаем таблицу товаров корзины, помещаем внутрь
                 * модального окна, а потом показываем это окно
                 */
                // создаем таблицу корзины
                var table = $('<table>')
                    .addClass('table')
                    .addClass('table-bordered')
                    .appendTo('#basket-modal .modal-body');
                var thead = $('<thead>').appendTo(table);
                $('<tr>')
                    .append($('<th>').text('Наименование'))
                    .append($('<th>').text('Количество'))
                    .append($('<th>').text('Цена, руб.'))
                    .append($('<th>').text('Сумма, руб.'))
                    .appendTo(thead);
                var tbody = $('<tbody>').appendTo(table);
                var products = response.products;
                for (var key in products) {
                    var cost = Number(products[key].count) * Number(products[key].price);
                    $('<tr>')
                        .append($('<td>').text(products[key].name))
                        .append($('<td>').text(products[key].count))
                        .append($('<td>').text(products[key].price))
                        .append($('<td>').text(cost))
                        .appendTo(tbody);
                }
                $('<tr>')
                    .append($('<td>').attr('colspan', 3).text('Итого'))
                    .append($('<td>').text(response.amount))
                    .appendTo(tbody);
                // показываем модальное окно
                $('#basket-modal').modal();
            },
            error: function () {
                alert('Произошла ошибка при добавлении товара в корзину');
            }
        });
        event.preventDefault();
    });
});
<?php
namespace app\controllers;

use app\models\Basket;
use Yii;

class BasketController extends AppController {

    /* ... */

    public function actionAdd() {

        /* ... */

        if (Yii::$app->request->isAjax) { // с использованием AJAX
            $content = $basket->getBasket();
            return $this->asJson($content);
        } else { // без использования AJAX
            return $this->redirect(['basket/index']);
        }
    }
}
{
    "products": {
        "123": {
            "name": "Мужская рубашка",
            "price": 1000,
            "count": 2
        },
        "456": {
            "name": "Мужская футблока",
            "price": 1200,
            "count": 1
        }
    },
    "amount": 3200
}

Показываем модальное окно, второй вариант

Мне не нравится формирование таблицы корзины с помощью JavaScript, поэтому давайте перенесем формирование html-кода на сервер. Для этого изменим в js-коде тип ответа сервера на html и создадим view-шаблон view/basket/modal.php:

jQuery(document).ready(function($) {
    /*
     * Аккордеон для меню каталога в левой колонке
     */
    $('#accordion').dcAccordion({
        speed: 'fast'
    });

    /*
     * Добавление товара в корзину с использованием AJAX
     */
    $('.add-to-basket').on('submit', function (event) {
        var action = $(this).attr('action');
        var method = $(this).attr('method');
        if (method == undefined) {
            method = 'get';
        }
        var data = $(this).serialize();
        $.ajax({
            url: action,
            type: method,
            data: data,
            dataType: 'html',
            success: function (response) {
                $('#basket-modal .modal-body').html(response);
                $('#basket-modal').modal();
            },
            error: function () {
                alert('Произошла ошибка при добавлении товара в корзину');
            }
        });
        event.preventDefault();
    });
});
<?php
/*
 * Корзина покупателя в модальном окне, файл views/basket/modal.php
 */

use yii\helpers\Html;
use yii\helpers\Url;
?>

<?php if (!empty($basket)): ?>
    <div class="table-responsive">
        <table class="table table-bordered">
            <tr>
                <th>Наименование</th>
                <th>Количество</th>
                <th>Цена, руб.</th>
                <th>Сумма, руб.</th>
            </tr>
            <?php foreach ($basket['products'] as $item): ?>
                <tr>
                    <td><?= $item['name']; ?></td>
                    <td class="text-right"><?= $item['count']; ?></td>
                    <td class="text-right"><?= $item['price']; ?></td>
                    <td class="text-right"><?= $item['price'] * $item['count']; ?></td>
                </tr>
            <?php endforeach; ?>
            <tr>
                <td colspan="3" class="text-right">Итого</td>
                <td class="text-right"><?= $basket['amount']; ?></td>
            </tr>
        </table>
    </div>
<?php else: ?>
    <p>Ваша корзина пуста</p>
<?php endif; ?>
<?php
namespace app\controllers;

use app\models\Basket;
use Yii;

class BasketController extends AppController {

    /* ... */

    public function actionAdd() {

        /* ... */

        if (Yii::$app->request->isAjax) { // с использованием AJAX
            // layout-шаблон нам не нужен, только view-шаблон
            $this->layout = false;
            $content = $basket->getBasket();
            return $this->render('modal', ['basket' => $content]);
        } else { // без использования AJAX
            return $this->redirect(['basket/index']);
        }
    }
}

Поиск: Web-разработка • Yii2 • Интернет магазин • Каталог товаров • Корзина • Фреймворк • AJAX • Modal • Модальное окно • JSON

Каталог оборудования
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Производители
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Функциональные группы
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.