Pull to refresh

Одностраничный магазин с корзиной на Phalcon + AngularJS + Zurb Foundation

Reading time 7 min
Views 37K

Введение


Всем привет! Завтра у меня дедлайн по проекту, который я делаю для местной Камчатской компании по доставки еды. И поэтому у меня есть две причины написать эту статью, первая — прокрастинация перед дедлайном, а вторая — я не нашёл на Хабре какого-либо обучающего мануала по написанию корзины товаров на AngularJS.

Я нашёл статью на стороннем блоге, которая частично помогла мне решить пару задач, которые стояли передо мной. Но оформление статьи оставляло желать лучшего, да и за 5 лет я уже отвык от кода в блокноте, без подсветки синтаксиса, поэтому нужно было как-то структурировать и сделать более читабельной эту полезную информацию.



Почему был выбран формат одностраничного магазина?



Кто-то из вас, наверное уже знает, что на Камчатке существует проблема с интернетом, так как наш полуостров ещё не связан с материком оптоволокном, и весь поток идёт через единственную вышку. К концу 2015 года планируется завершить работы по прокладке оптоволокна по дну Охотского моря, и возможно у нас появится наконец-то стабильный и быстрый интернет.

И соответственно чтобы исключить потери клиентов, переходящих по разным категориям блюд, из-за нестабильного канала, была выбрана подобная модель. То есть человеку достаточно один раз загрузить сайт и получить всю необходимую информацию, и сделать заказ.

Так же такой формат позволил избежать хранения товаров корзины в сессиях, localStorage или же в базе данных. Так как мы точно знаем, что человек никуда не уйдёт с этой страницы, мы храним данные корзины в объекте javascript. Ещё одним плюсом стало уменьшение времени заказа, так как нет нужды перемещаться по категориям, и загружать новые страницы. И так как позиций блюд не слишком много, нам даже не пришлось делать Ajax-подгрузку данных при нажатии на категорию, всё подгружалось из кеша базы данных.

База данных


После того, как был готов и свёрстан дизайн одностраничного сайта, пришло время создать структуру базы данных категорий и товаров. Это наверно самый быстрый и самый простой этап, учитывая наши потребности и направленность на простоту работы системы и взаимодействия с пользователями. У меня уже был набросок админки на Phalcon PHP Framework, поэтому поправить его для работы с двумя таблицами category и products, не составило особого труда.

Таблица category


Таблица products


Вот так выглядит админка сайта


Получаем категории и блюда из базы данных


Для работы с базой данных использовалась стандартная ORM система фреймворка Phalcon, если вы с ней не работаете, можете пропустить этот раздел, он для общего развития, чтоб дальше было понятно откуда ноги растут, точнее откуда и как берутся блюда на сайте.

В основном контроллере IndexController.php в модуле frontend, я написал функцию, которая сформирует данные нужным нам образом и выведет их на единственную нашу страницу.
public function indexAction()
    {
         //Получаем все категории в массив
        $category = Category::find()->toArray();
        $help = new \Lib\Url();
        Перебираем массив и для каждой категории подгружаем блюда
        foreach ($category as $key => $val)
        {
            $category[$key]['url'] = $help->translit_url($val['name']);
            //Это не родительская категория? Тогда удаляем из массива
            if ($val['pid'] != 0)
            {
                unset($category[$key]);
            }
            else
            {
                //Подгружаем массив подкатегорий, правда в нашем случае только одна категория имеет подкатегории
                $category[$key]['sub'] = Category::find("pid = '".$val['id']."'")->toArray();
                //Если есть подкатегории, цепляем к ним блюда, если нет, то цепляем блюда к основном категории
                if(count($category[$key]['sub']) > 0)
                {
                    foreach($category[$key]['sub'] as $i => $cat)
                    {
                        $category[$key]['sub'][$i]['products'] = Products::find("category = '" . $cat['id'] . "'")->toArray();
                    }
                }
                else
                {
                    $category[$key]['products'] = Products::find("category = '" . $val['id'] . "'")->toArray();
                }
            }
        }
        $this->view->setVar('items',$category);
    }


Возможно есть более изящные решения этой задачи, но у нас не будет больше 200 посетителей за день, и мы подключим кеширование запросов к базе данных, и в принципе не будет такой сильной нагрузки, тем более это Phalcon — «Самый быстрый PHP фреймворк». Но вопрос сейчас не о производительно и оптимизации, это пока рано, главное что пора выводить товар на странице.

Кому доверить рендеринг товаров на странице? AngularJS или Phalcon?


Сначала я реализовал всё на AngularJS, ну это было как-то изящнее и красивее, но потом задумался о СЕО-оптимизации, и индексации поисковыми системами, и подумал что лучше наверно не рисковать, и рендеринг доверить нашему старому доброму любимому PHP.

Приводить код выводящий товары на странице я тут не буду, как из соображений величины этого кода, так и из этических соображений, всё таки это продукт нашего заказчика. Да и думаю, кто читает Хабр знает как пользоваться функцией foreach в php.

Ну ладно, покажу как я выводил категории блюд, а там уже по примеру каждый разберётся и с товарами.

<div class="row">
        <div class="large-12 columns">
            <ul class="tabs small-block-grid-2 medium-block-grid-4 large-block-grid-6" data-tab>
                <?php foreach ($items as $item): ?>
                    <li>
                        <a href="#<?= $item['url'] ?>">
                            <img ng-src="/uploads/<?= $item['images'] ?>" alt="<?= $item['name'] ?>">
                        </a>
                        <p><?= $item['name'] ?></p>
                    </li>
                <?php endforeach; ?>
            </ul>
        </div>
    </div>


Выглядит это всё дело очень и очень аппетитно.


Добавление товара в корзину


Нажимая на категорию, внизу открывается таб с блюдами из этой категории. Возможно кто-то из вас уже не может смотреть на эти аппетитные картинки, но для продолжения описания работы одностраничного магазина, мне просто необходимо показать вам, как выглядит карточка товара на сайте. Смотрите.



Так, а теперь вытирайте слюни с клавиатуры, и поехали дальше, смотреть как же работает основной функционал добавления в корзину и высчитывания конечной суммы заказа.

В основном для понимания основы вам нужно видеть только этот отрезок кода, который выводит кнопку «Добавить» с полем, куда можно ввести количество штук блюда.

<div class="add-cart">
      <input type="number" ng-model="num<?=$p['id']?> value="1" min="1" max="50">
     <button type="button" ng-click="addCart(<?=$p['id']?>, num<?=$p['id']?>, '<?=$p['title']?>', <?=$p['price']?>)"></button>
</div>


Как видно из исходного кода, функция addCart() выполняет добавление данных в объект javascript, давайте посмотрим исходный код на AngularJS.

    $scope.carts = [];
    $scope.addCart = function(id, num, title, price)
    {
        var nums = num || 1;
        $scope.carts.push({
            id : id,
            num : nums,
            title : title,
            price : price
        });
    };


Я думаю тут нечего объяснять, всё предельно просто и понятно, а главное — работает! После того, как мы добавили данные в $scope.carts, нужно их куда-то вывести. В нашем случае, дизайнер решил сделать это в плавающее окошко справа, вот так.


И соответственно код.

<div class="cart ng-cloak" ng-cloak ng-show="carts.length > 0">
    <div class="cart-items">
        <h3 class="text-center">Корзина</h3>
        <div class="row items" ng-repeat="item in carts">
            <div class="small-5 columns text-right">
                {{item.title}}
            </div>
            <div class="small-2 columns">
                <input type="number" ng-model="item.num" min="1" max="50">
            </div>
            <div class="small-4 columns">
                {{item.price}} руб.
            </div>
            <div class="small-1 columns">
                <a href ng-click="removeItem(carts,item)">X</a>
            </div>
        </div>
    </div>
    <div class="cart-results text-center">
        <div class="row">
            <div class="small-12 columns">
                <select ng-model="delivery" ng-init="delivery = 0">
                    <option value="0">Октябрьский район</option>
                    <option value="1">Ленинский район</option>
                    <option value="2">Долиновка, Завойко</option>
                    <option value="3">Самовывоз (-10%)</option>
                </select>
            </div>
        </div>
        <h3>{{total() | number : 0}} руб.</h3>
        <button class="new_btn" data-reveal-id="order">Заказать</button>
    </div>
</div>


Кстати, функция ng-cloak просто выручает в условиях Камчатского интернета, если её не использовать, пока человек будет ждать загрузку страницы, ему будет видна пустая корзина со страшными символами. Для тех кто не знаком с AngularJS, укажу на несколько ключевых моментов.
Это нужно, чтобы показывать корзину, только в случае если в ней есть товары.
ng-show="carts.length > 0"

Этот код выводит конечную сумму заказа, форматируя число, и убирая копейки, которые могут получится, при высчитывании 10% скидки, например в случае самовывоза.
{{total() | number : 0}}

У меня часто возникала задача удалить элемент ассоциативного массива, я каждый раз забывал как это делать, и обращался к Google, но надеюсь после этой публикации я наконец-то запомню, а те кто не знал, узнают.
$scope.removeItem = function(carts, item) {
        carts.splice(item, 1);
};

А что, кто-то рассчитывал что будет больше кода? Можно кстати даже в одну строчку написать. Но не будем изгаляться, нам главное читабельность кода. И наверно последнюю функцию которую я хочу привести в этой статье — это подсчёт конечной суммы заказа. Он был выполнен исходя из условий и способов доставки.
$scope.total = function() {
        var total = 0;
        angular.forEach($scope.carts, function(item) {
            total += item.num * item.price;
        });
        var delivery = 0;
        if($scope.delivery == 0)
        {
            if(total >= 600)
            {
                delivery = 0;
            }
            else
            {
                delivery = 130;
            }
        }
        if($scope.delivery == 1)
        {
            if(total >= 1500)
            {
                delivery = 0;
            }
            else
            {
                delivery = 130;
            }
        }
        if($scope.delivery == 2)
        {
            delivery = 300;
        }
        if($scope.delivery == 3)
        {
            delivery = 10/total*100*(-1);
        }
        return total + delivery;
    };

Чем объяснять откуда растут цифры, я просто покажу блок с информацией о доставке, а вы уже сами разгадаете откуда появились разные цифры в коде. Тем более у каждого эти данные будут разные, и будет разное количество районов, поэтому не вижу смысла заострят на этом внимание, тем более статья и так уже получилась достаточно объёмная.


Послесловие


Главная задача моего поста решена, теперь на Хабре есть материал, о том как сделать добавление товаров в корзину и подсчёт суммы заказа на AngularJS, а в интернете есть хорошо оформленный материал об этом, который дополнит запись из стороннего блога, ссылку на который я привёл в начале статьи. Ну а так же, я уже достаточно прокрастинировал, поэтому пора приступать снова к работе, и заканчивать отправку заказа нашему заказчику. Надеюсь статья поможет таким же как я. Если у вас всё же возникнут вопросы по приведённому исходному коду, или что-то будет не понятно, я с удовольствием отвечу в комментариях. К сожалению опыт работы с AngularJS всего пол года, поэтому чем смогу, тем помогу. Спасибо за ваше внимание.

Обновление
Спасибо всем за комментарии, я постарался исправить недочёты и описал проделанную работу в следующей публикации — «Одностраничный магазин на Phalcon PHP + AngularJS. Работа над ошибками».
Tags:
Hubs:
-2
Comments 45
Comments Comments 45

Articles