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

20.07.2019

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

Наша корзина сейчас довольно примитивная — не хватает возможности удалить товар и обновить содержимое. Давайте это исправим и начнем с обновления view-шаблонов страницы корзины и содержимого модального окна. Добавим ссылки для удаления товаров из корзины и форму — чтобы можно было изменить количество.

Итак, вносим изменения в view-шаблон страницы корзины:

<?php
/*
 * Страница корзины покупателя, файл views/basket/index.php
 */

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

<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">
                <h1>Корзина</h1>
                <div id="basket-content">
                    <?php if (!empty($basket)): ?>
                        <p class="text-right">
                            <a href="<?= Url::to(['basket/clear']); ?>" class="text-danger">
                                Очистить корзину
                            </a>
                        </p>
                        <div class="table-responsive">
                            <form action="<?= Url::to(['basket/update']); ?>" method="post">
                                <?=
                                Html::hiddenInput(
                                    Yii::$app->request->csrfParam,
                                    Yii::$app->request->csrfToken
                                );
                                ?>
                                <table class="table table-bordered">
                                    <tr>
                                        <th>Наименование</th>
                                        <th>Кол-во, шт.</th>
                                        <th>Цена, руб.</th>
                                        <th>Сумма, руб.</th>
                                        <th></th>
                                    </tr>
                                    <?php foreach ($basket['products'] as $id => $item): ?>
                                        <tr>
                                            <td>
                                                <a href="<?= Url::to(['catalog/product', 'id' => $id]); ?>">
                                                    <?= Html::encode($item['name']); ?>
                                                </a>
                                            </td>
                                            <td class="text-right">
                                                <?=
                                                Html::input(
                                                    'text',
                                                    'count['.$id.']',
                                                    $item['count'],
                                                    ['style' => 'width: 100%; text-align: right;']
                                                );
                                                ?>
                                            </td>
                                            <td class="text-right"><?= $item['price']; ?></td>
                                            <td class="text-right"><?= $item['price'] * $item['count']; ?></td>
                                            <td>
                                                <a href="<?= Url::to(['basket/remove', 'id' => $id]); ?>" class="text-danger">
                                                    <i class="fa fa-times" aria-hidden="true"></i>
                                                </a>
                                            </td>
                                        </tr>
                                    <?php endforeach; ?>
                                    <tr>
                                        <td>
                                            <button type="submit"
                                                    class="btn btn-primary">
                                                <i class="fa fa-refresh" aria-hidden="true"></i>
                                                Пересчитать
                                            </button>
                                        </td>
                                        <td colspan="2" class="text-right">Итого</td>
                                        <td class="text-right"><?= $basket['amount']; ?></td>
                                        <td></td>
                                    </tr>
                                </table>
                            </form>
                        </div>
                    <?php else: ?>
                        <p>Ваша корзина пуста</p>
                    <?php endif; ?>
                </div>
                <?php if (!empty($basket)): ?>
                    <a href="<?= Url::to(['order/checkout']); ?>"
                       class="btn btn-warning pull-right">
                        Оформить заказ
                    </a>
                <?php endif; ?>
            </div>
        </div>
    </div>
</section>

Изменяем view-шаблон корзины в модальном окне:

<?php
/*
 * Корзина покупателя в модальном окне, файл views/basket/modal.php
 */

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

<?php if (!empty($basket)): ?>
    <p class="text-right clear">
        <a href="<?= Url::to(['basket/clear']); ?>" class="text-danger">
            Очистить корзину
        </a>
    </p>
    <div class="table-responsive">
        <form action="<?= Url::to(['basket/update']); ?>" method="post">
            <?=
            Html::hiddenInput(
                Yii::$app->request->csrfParam,
                Yii::$app->request->csrfToken
            );
            ?>
            <table class="table table-bordered">
                <tr>
                    <th>Наименование</th>
                    <th>Кол-во, шт.</th>
                    <th>Цена, руб.</th>
                    <th>Сумма, руб.</th>
                    <th></th>
                </tr>
                <?php foreach ($basket['products'] as $id => $item): ?>
                    <tr>
                        <td>
                            <a href="<?= Url::to(['catalog/product', 'id' => $id]); ?>">
                                <?= Html::encode($item['name']); ?>
                            </a>
                        </td>
                        <td class="text-right">
                            <?=
                            Html::input(
                                'text',
                                'count['.$id.']',
                                $item['count'],
                                ['style' => 'width: 100%; text-align: right;']
                            );
                            ?>
                        </td>
                        <td class="text-right"><?= $item['price']; ?></td>
                        <td class="text-right"><?= $item['price'] * $item['count']; ?></td>
                        <td>
                            <a href="<?= Url::to(['basket/remove', 'id' => $id]); ?>"
                               class="text-danger">
                                <i class="fa fa-times" aria-hidden="true"></i>
                            </a>
                        </td>
                    </tr>
                <?php endforeach; ?>
                <tr>
                    <td>
                        <button type="submit"
                                class="btn btn-primary">
                            <i class="fa fa-refresh" aria-hidden="true"></i>
                            Пересчитать
                        </button>
                    </td>
                    <td colspan="2" class="text-right">Итого</td>
                    <td class="text-right"><?= $basket['amount']; ?></td>
                    <td></td>
                </tr>
            </table>
        </form>
    </div>
<?php else: ?>
    <p>Ваша корзина пуста</p>
<?php endif; ?>

Удаление товара из корзины

Добавляем новый метод в контроллер BasketController:

<?php
namespace app\controllers;

use app\models\Basket;
use Yii;

class BasketController extends AppController {

    public function actionIndex() {
        /* ... */
    }

    public function actionAdd() {
        /* ... */
    }

    public function actionRemove($id) {
        $basket = new Basket();
        $basket->removeFromBasket($id);
        return $this->redirect(['basket/index']);
    }

}

Теперь удаление уже работает, но не слишком красиво. При удалении товара из корзины в модальном окне присходит переход на страницу корзины. Давайте это исправим, будем перехватывать событие клика по ссылке и отправлять GET-запрос с использованием AJAX:

jQuery(document).ready(function($) {
    /*
     * Добавление товара в корзину с использованием AJAX
     */
    $('.add-to-basket').on('submit', function (event) {
        /* ... */
    });
    /*
     * Удаление товара из корзины в модальном окне
     */
    $('#basket-modal .modal-body').on('click', 'table a.text-danger', function (event) {
        var href = $(this).attr('href');
        $('#basket-modal .modal-body').load(href, function () {
            // если корзина пуста, скрываем кнопку «Оформить заказ»
            if ( ! $('#basket-modal .modal-body table').length) {
                $('#basket-modal .modal-footer .btn-warning').hide();
            }
        });

        event.preventDefault();
    });
    /*
     * Удаление товара из корзины на странице корзины
     */
    $('#basket-content').on('click', 'table a.text-danger', function (event) {
        var href = $(this).attr('href');
        $('#basket-content').load(href, function () {
            // если корзина пуста, скрываем кнопку «Оформить заказ»
            if ( ! $(this).find('table').length) {
                $('#basket-content').next('.btn-warning').hide();
            }
        });
        event.preventDefault();
    });
});

Соответственно, изменим метод контроллера actionRemove(), чтобы он умел обрабатывать AJAX-запросы:

<?php
namespace app\controllers;

use app\models\Basket;
use Yii;

class BasketController extends AppController {

    public function actionIndex() {
        /* ... */
    }

    public function actionAdd() {
        /* ... */
    }

    public function actionRemove($id) {
        $basket = new Basket();
        $basket->removeFromBasket($id);
        /*
         * Тут возможны две ситуации: пришел просто GET-запрос
         * или GET-запрос с использованием XmlHttpRequest
         */
        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']);
        }
    }

}

Удаление всех товаров из корзины

Добавляем новый метод в контроллер BasketController:

<?php
namespace app\controllers;

use app\models\Basket;
use Yii;

class BasketController extends AppController {

    public function actionIndex() {
        /* ... */
    }

    public function actionAdd() {
        /* ... */
    }

    public function actionRemove($id) {
        /* ... */
    }

    public function actionClear() {
        $basket = new Basket();
        $basket->clearBasket();
        return $this->redirect(['basket/index']);
    }

}

По аналогии с удалением товара из корзины, будем отлавливать событие клика по ссылке «Очистить корзину» и отправлять GET-запрос с использованием AJAX:

jQuery(document).ready(function($) {
    /*
     * Добавление товара в корзину с использованием AJAX
     */
    $('.add-to-basket').on('submit', function (event) {
        /* ... */
    });
    /*
     * Удаление товара из корзины в модальном окне
     */
    $('#basket-modal .modal-body').on('click', 'table a.text-danger', function (event) {
        /* ... */
    });
    /*
     * Удаление товара из корзины на странице корзины
     */
    $('#basket-content').on('click', 'table a.text-danger', function (event) {
        /* ... */
    });
    /*
     * Удаление всех товаров из корзины в модальном окне
     */
    $('#basket-modal .modal-body').on('click', 'p a.text-danger', function (event) {
        var href = $(this).attr('href');
        $('#basket-modal .modal-body').load(href);
        // корзина пуста, скрываем кнопку «Оформить заказ»
        $('#basket-modal .modal-footer .btn-warning').hide();
        event.preventDefault();
    });
    /*
     * Удаление всех товаров из корзины на странице корзины
     */
    $('#basket-content').on('click', 'p a.text-danger', function (event) {
        var href = $(this).attr('href');
        $('#basket-content').load(href);
        // корзина пуста, скрываем кнопку «Оформить заказ»
        $('#basket-content').next('.btn-warning').hide();
        event.preventDefault();
    });
});

Вносим изменения в метод контроллера actionClear(), чтобы он умел обрабатывать AJAX-запросы:

<?php
namespace app\controllers;

use app\models\Basket;
use Yii;

class BasketController extends AppController {

    public function actionIndex() {
        /* ... */
    }

    public function actionAdd() {
        /* ... */
    }

    public function actionRemove($id) {
        /* ... */
    }

    public function actionClear() {
        $basket = new Basket();
        $basket->clearBasket();
        /*
         * Тут возможны две ситуации: пришел просто GET-запрос
         * или GET-запрос с использованием XmlHttpRequest
         */
        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']);
        }
    }

}

Изменение количества товаров

При нажатии на кнопку «Пересчитать» на сервер будет отправлен POST-запрос, содержащий идентификаторы всех товаров в корзине и их количество:

Array
(
    [_csrf] => .....
    [count] => Array
        (
            [123] => 1
            [456] => 2
        )
)

Мы принимаем данные в методе actionUpdate() контроллера и вызываем метод updateBasket() модели:

<?php
namespace app\controllers;

use app\models\Basket;
use Yii;

class BasketController extends AppController {

    public function actionIndex() {
        /* ... */
    }

    public function actionAdd() {
        /* ... */
    }

    public function actionRemove($id) {
        /* ... */
    }

    public function actionClear() {
        /*...*/
    }

    public function actionUpdate() {
        $basket = new Basket();

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

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

        $basket->updateBasket($data);

        return $this->redirect(['basket/index']);
    }

}
<?php
namespace app\models;

use yii\base\Model;
use Yii;

class Basket extends Model {
    /**
     * Метод добавляет товар в корзину
     */
    public function addToBasket($id, $count = 1) {
        /* ... */
    }

    /**
     * Метод удаляет товар из корзины
     */
    public function removeFromBasket($id) {
        /* ... */
    }

    /**
     * Метод возвращает содержимое корзины
     */
    public function getBasket() {
        /* ... */
    }

    /**
     * Метод удаляет все товары из корзины
     */
    public function clearBasket() {
        /* ... */
    }

    /**
     * Метод обновляет содержимое корзины
     */
    public function updateBasket($data) {
        $this->clearBasket();
        foreach ($data['count'] as $id => $count) {
            $this->addToBasket($id, $count);
        }
    }
}

Обновление теперь работает, но не слишком красиво. Здесь та же проблема, как и при удалении товаров из корзины. После отправки формы из модального окна происходит переход на страницу корзину. Но мы уже знаем, что с этим делать — надо отправлять данные формы с использованием AJAX:

jQuery(document).ready(function($) {
    /*
     * Добавление товара в корзину с использованием AJAX
     */
    $('.add-to-basket').on('submit', function (event) {
        /* ... */
    });
    /*
     * Удаление товара из корзины в модальном окне
     */
    $('#basket-modal .modal-body').on('click', 'table a.text-danger', function (event) {
        /* ... */
    });
    /*
     * Удаление товара из корзины на странице корзины
     */
    $('#basket-content').on('click', 'table a.text-danger', function (event) {
        /* ... */
    });
    /*
     * Удаление всех товаров из корзины в модальном окне
     */
    $('#basket-modal .modal-body').on('click', 'p a.text-danger', function (event) {
        /* ... */
    });
    /*
     * Удаление всех товаров из корзины на странице корзины
     */
    $('#basket-content').on('click', 'p a.text-danger', function (event) {
        /* ... */
    });
    /*
     * Обновление содержимого корзины в модальном окне
     */
    $('#basket-modal').on('submit', 'form', 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);
                // если корзина пуста, скрываем кнопку «Оформить заказ»
                if ( ! $('#basket-modal .modal-body table').length) {
                    $('#basket-modal .modal-footer .btn-warning').hide();
                }
            },
            error: function () {
                alert('Произошла ошибка при обновлении корзины');
            }
        });
        event.preventDefault();
    })
    /*
     * Обновление содержимого корзины на странице корзины
     */
    $('#basket-content').on('submit', 'form', 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-content').html(response);
                // если корзина пуста, скрываем кнопку «Оформить заказ»
                if ( ! $('#basket-content table').length) {
                    $('#basket-content').next('.btn-warning').hide();
                }
            },
            error: function () {
                alert('Произошла ошибка при обновлении корзины');
            }
        });
        event.preventDefault();
    });
});

И вносим изменения в метод контроллера actionUpdate(), чтобы он умел обрабатывать AJAX-запросы:

<?php
namespace app\controllers;

use app\models\Basket;
use Yii;

class BasketController extends AppController {

    public function actionIndex() {
        /* ... */
    }

    public function actionAdd() {
        /* ... */
    }

    public function actionRemove($id) {
        /* ... */
    }

    public function actionClear() {
        /*...*/
    }

    public function actionUpdate() {
        $basket = new Basket();

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

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

        $basket->updateBasket($data);

        /*
         * Тут возможны две ситуации: пришли просто данные POST или
         * пришли данные POST с использованием XmlHttpRequest
         */
        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']);
        }
    }

}

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

Каталог оборудования
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.