Делаем корзину покупок с помощью Codeigniter и JQuery. Часть 6.
В шестой части статьи, как и обещал, сделаем нашу корзину аяксовой, а так же стилизуем её с помощью CSS.
Статья обещает быть длинной, поэтому запаситесь терпением, нам надо много изучить и сделать.
Т.к. изучение CSS не входит в планы этих уроков, то я просто приведу уже готовый код без пояснений. В первых уроках мы создавали файл assets/css/styles.css, который был пустым. В этом файле и будет храниться весь CSS код сайта:
assets/css/styles.css
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117
body {
font-family: Arial, Helvetica, Verdana, Tahoma, sans-serif;
font-size: 16px;
color: black;
}
.content {
width: 700px;
margin: 20px auto;
}
.cart-items,
.products-list {
width: 100%;
text-align: center;
}
.cart-items thead {
font-weight: bold;
}
.products-list .product-title,
.products-list .price {
margin-bottom: 5px;
}
.products-list .product-title {
font-weight: bold;
}
.products-list .price {
color: red;
}
.cart {
background: #eee;
padding: 10px;
}
.cart .controls {
margin-top: 10px;
}
.message-success,
.message-error {
width: 100%;
padding: 10px;
border-radius: 8px;
border-width: 1px;
color: white;
box-sizing: border-box;
margin-bottom: 10px;
}
.message-success {
background: #5db954;
border-color: #51a34a;
}
.message-error {
background: #b94545;
border-color: #ab4040;
}
*:focus {
outline: none;
}
button,
input[type=submit],
input[type=button] {
position: relative;
display: inline-block;
padding: 7px 12px;
font-size: 13px;
font-weight: bold;
color: #333;
text-shadow: 0 1px 0 rgba(255,255,255,0.9);
white-space: nowrap;
vertical-align: middle;
cursor: pointer;
background-color: #eeeeee;
background-image: -moz-linear-gradient(#fcfcfc, #eee);
background-image: -webkit-linear-gradient(#fcfcfc, #eee);
background-image: linear-gradient(#fcfcfc, #eee);
background-repeat: repeat-x;
border: 1px solid #d5d5d5;
border-radius: 3px;
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
-webkit-appearance: none;
}
button:hover,
input[type=submit]:hover,
input[type=button]:hover {
text-decoration: none;
background-color: #dddddd;
background-image: -moz-linear-gradient(#eee, #ddd);
background-image: -webkit-linear-gradient(#eee, #ddd);
background-image: linear-gradient(#eee, #ddd);
background-repeat: repeat-x;
border-color: #ccc;
}
button:active,
input[type=submit]:active,
input[type=button]:active {
background-color: #dcdcdc;
background-image: none;
border-color: #b5b5b5;
box-shadow: inset 0 2px 4px rgba(0,0,0,0.15);
}
Вот мы и добрались до самого интересного - написание клиентского js кода функиционала ajax корзины. В основном шаблоне уже из первых уроков подключена библиотека jQuery и скрипт cart.js, в котором мы будеим писать весь js код.
application/views/template.php
12345
...
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js"></script>
<script src="<?php print base_url('assets/js/cart.js'); ?>"></script>
</body>
</html>
Открывайте файл cart.js и напишите следующее:
assets/js/cart.js
12345
;(function($) {
})(jQuery);
Это анонимная функция, которая сразу же вызывается, чтобы исключить конфликты с другими скриптами js (хоть у нас их и нет, но нужно привыкать к хорошему).
Нам нужно сделать формы (добавления в корзину, удаления товара из корзины и остальные) аяксовыми. Поэтому при клике на кнопку, мы будет посылать AJAX запрос на сервер, и в зависимости от результата, который возвратил сервер, выполнять какие то действия (например выводить сообщение пользователю).
Давайте добавим эту возможность кнопкам "купить":
assets/js/cart.js
1234567891011121314151617181920212223242526272829303132333435363738
;(function($) {
$('.products-list').on('submit', 'form', function(e) {
var $form = $(this);
var formData = $form.serialize();
$.post($form.attr('action'), formData, function(resp) {
var $messageHTML = $('<div class="message-' + resp.status + '">' + resp.message + '</div>').hide();
$messageHTML.prependTo('.content').fadeIn();
setTimeout(function() {
$messageHTML.slideUp(function() {
$(this).remove();
});
}, 2000);
});
e.preventDefault();
});
})(jQuery);
Итак, разберём что тут получилось. Для начала с помощью js необходимо изменить событие отправки формы на своё. Для этого в
1
$('.products-list').on('submit', 'form', function(e) {
мы "слушаем" все события submit на элементе form внутри блока .products-list, т.е. отправку формы добавления в корзину товара. Чтобы запретить обычную отправку формы мы используем функцию
1
e.preventDefault();
Далее необходимо отправить POST запрос к серверу. Для этого надо подготовить данные, которые будет отправлять. В jQuery есть функция serialize(), котороя преобзазует данные формы в строку (см. официальную документацию).
После того, как ответ от сервера получен, мы подготавливаем HTML код сообщения, которое будем выводить пользователю, показываем его и скрываем через 2 секунды. В коде выше всё подробно прокоментировано. Если есть какие-то опросы - пишите в комментариях.
Вот что у нас получилось:
++++
add-to-cart.gifТ.к. вывод сообщений пользователю с помощью javascript нам ещё понадобиться, то предлагаю сделать функцию setMessage(), чтобы не плодить код. Добавьте её в самое начало cart.js:
assets/js/cart.js
12345678910111213141516171819202122232425
function setMessage(text, status)
{
var $messageHTML = $('<div class="message-' + status + '">' + text + '</div>').hide();
$messageHTML.prependTo('.content').fadeIn();
setTimeout(function() {
$messageHTML.slideUp(function() {
$(this).remove();
});
}, 2000);
}
И изменим код добавления в корзину:
assets/js/cart.js
123456789101112131415
$('.products-list').on('submit', 'form', function(e) {
var $form = $(this);
var formData = $form.serialize();
$.post($form.attr('action'), formData, function(resp) {
setMessage(resp.message, resp.status);
});
e.preventDefault();
});
Совет 1
Хорошей практикой является именно добавление прослушки события для родительского элемента. Ведь Можно было написать и так:
1234567
$('.products-list form').submit(function(e) {
e.preventDefault();
});
Здесь для каждой формы добавляется событие submit. И, когда на странице например 1000 товаров, мы бы добавили 1000 событий, что явно не добавит производительности. Так что вседа нужно добавлять именно прослушку события.
Без прослушки событий не обойтись, если контент динамически меняется на странице (как у нас корзина. В неё постоянно добавляеются строки, удаляеются, очищаются).
Совет 2
Если Вы пользуетесь браузером Chrome (а я надеюсь вы делаете это), то наверно уже знаете про средства разработчика, вызываемые по нажитию клавиши F12. Если нет, то давайте попробуем на нашей корзине проследить за AJAX запросом, который посылает наше приложение серверу. Откройте средства разработчика и перейдите на вкладку Network (если там не пусто, то очистите список):
++++
clear-network.pngВ этом окне будут оторажаться все запросы к серверу от приложения. Теперь нажмите на кнопку "Купить". Должен добавиться новый запрос, и если на него нажать, то получим полную информацию о нём. На скриншоте ниже можно увидеть текст, который возвращает сервер:
++++
chrome-network.pngЕсли открыть вкладку Headers, то можно увидеть данные, которые мы передаём серверу, тип ответа и ещё много информации, которая позволит находить ошибки.
++++
headers.pngКак вы уже заметили, товар в корзину добавляется, но не появляется там. Он появится только после перезагрузки страницы. Почему? Да потому что мы не написали javascript функционал добавления строки с товаром в корзину.
Реализовывать это будем самым простым способом:
При добавлении товара в корзину, в случае успеха, сервер возвращает весь HTML код корзины (с уже добавленной в неё строкой нового товара), а мы с помощью js просто обновим её.
Этот способ простой, но не лишён недостатков. Один из них - нужно получать весь HTML корзины, а не только ту часть, которую нужно обновить. Т.е. мы будет "гонять" больше трафика. Но думаю это не такая большая проблема. Во-первых скорость интернета позволяет, во-вторых корзина не такой большой элемент на странице, чтобы мы почувствовали какие-то тормоза в работе интерфейса. В-третьих - вы посмотрите исходный код opencart, и код в этой стетье покажется вам идеальным.
Итак, поправим немного код в методе add_to_cart() контроллера cart. Я не буду приводить полный код метода, только небольшой кусок:
application/controllers/cart.php
1234567891011121314151617181920212223242526
...
$product_data = $this->cart_model->get_product($product_id);
if ($product_data) {
$this->cart->insert(array(
'id' => $product_id,
'qty' => 1,
'price' => $product_data['price'],
'name' => $product_data['name'],
));
$user_cart = $this->cart->contents();
$cart_html = $this->load->view('cart-view', array('cart_items' => $user_cart), true);
$result = array(
'status' => 'success',
'message' => 'Товар добавлен в корзину',
'cart_html' => $cart_html,
);
После добавления товара в корзину методом $this->cart->insert(), получаем в массив $user_cart данные корзины пользователя. Далее передаём этом пассив в шаблон. В итоге, весь HTML код корзины будет храниться в переменной $cart_html. Впринципе, мы повторили обычные получение и вывод корзины, которые использователи в методе index() контроллера cart.
Поле этого в массив $result добавляем новый элемент cart_html, в котором будет весь HTML код корзины, и который мы возвратим вместе с сообщением о результате.
Теперь, если с помощью средств разработчка, мы посмотрим на результат, котоый сервер возвращает нам после добавления в корзину мы увидим HTML код корзины:
++++
ci_cart-add_to_cart.pngДалее, по плану - обновление корзины на странице. Вот изменённый js код добавления товара в корзину и её обновления:
12345678910111213141516171819
$('.products-list').on('submit', 'form', function(e) {
var $form = $(this);
var formData = $form.serialize();
$.post($form.attr('action'), formData, function(resp) {
$('.cart').replaceWith(resp.cart_html);
setMessage(resp.message, resp.status);
});
e.preventDefault();
});
jQuery методом replaceWith() (ссылка на документацию) мы заменяем блок .cart на новый, который возвратил сервер. И так же выводим сообщение пользователю.
В прошлом уроке мы не доделали до конца функционал корзины, а в частности её очистку. Кнопка очистки корзины это обычная форма с кнопкой, которая отсылает POST запрос на адрес /cart/cart_empty. Давайте создадим в контроллере cart метод cart_empty()
1234567891011121314151617181920212223242526272829
public function cart_empty()
{
$this->cart->destroy();
$result = array(
'status' => 'success',
'message' => 'Корзина очищена',
);
$is_ajax = $this->input->is_ajax_request();
if ($is_ajax) {
$this->output->set_content_type('application/json');
$this->output->set_output(json_encode($result));
} else {
$this->session->set_flashdata('message', $result);
redirect(base_url());
}
}
Здесь многое уже знакомо. С помощью стандартного метода
1
$this->cart->destroy();
мы очищаем корзину пользователя и выводим ему сообщение.
Добавим немного javascript'a, чтобы сделать очистку корзины без перезагрузки страницы.
Что нужно сделать:
- По нажатию на кнопку "Очистить корзину" отослать AJAX запрос на сервер
- После получения ответа удалить таблицу с корзиной из DOM
- Вывести сообщение пользвателю.
Приступим к реализации:
1234567891011121314
$('.content').on('submit', '.controls form', function(e) {
var $form = $(this);
$.post($form.attr('action'), function(resp) {
if (resp.status === 'success') {
$('.cart')
.prepend('<p>В корзине нет товаров</p>')
.find('.cart-items').remove();
}
setMessage(resp.message, resp.status);
});
e.preventDefault();
});
По событии submit формы очистки корзины, посылаем AJAX запрос на сервер (на URL, который указан в атрибуте action формы), затем, если сервер вернул статус success, удаляем таблицу с корзиной, выводим надпись "В корзине нет товаров", и выводим сообщение пользователю с помощью нашей функции setMessage().
Почему я повесил прослушку события аж на блок .content, а не на, например, .cart. Потому что при очистке, добавлении товара и его удаления, мы полностью заменяем блок .cart, и событие с него "слетает". Поэтому самый ближайший родительский неизменяемый динамически элемент к корзине это блок .content.
Чтобы уже логически закончить статью, давайте напишем обработчик для кнопок удаления товара из корзины.
Алгоритм действий:
- Вешаем прослушку события отправки формы с кнопкой удаления из корзины
- Отправляем POST запрос на сервер для удаления товара из корзины
- Сервер возвращает результат, и, в случае успеха, обновлённый HTML корзины
- С помощью js мы обновляем корзину и выводим сообщение пользователю, что товар удалён из корзины
Открываем файл cart.js и пишем новый обраточик:
assets/js/cart.js
123456789101112131415161718192021
$('.content').on('submit', '.cart-items form', function(e) {
var $form = $(this);
var formData = $form.serialize();
$.post($form.attr('action'), formData, function(resp) {
if (resp.status === 'success') {
$('.cart').replaceWith(resp.cart_html);
}
setMessage(resp.message, resp.status);
});
e.preventDefault();
});
Код очень похож на добавление товара в корзину. Мы так же собираем данные формы с помощью метода $form.serialize(), отсылает данные на URL, который указан в атрибуте action формы. Далее, если сервер возвратис статус success, обновляем корзину и выводит сообщение пользователю.
Теперь доработает код метода удаления товара из корзины delete_item(). Догадываетесь что нужно сделать? Получить данные корзины пользователя, пропустить их через шаблон и возвратить в ответе.
Код для получения HTML корзины точно такой же, как и при обновлении или добавлении товара в корзину:
1234567
$user_cart = $this->cart->contents();
$cart_html = $this->load->view('cart-view', array('cart_items' => $user_cart), true);
Листинг метода delete_item():
application/controllers/cart.php
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354
public function delete_item()
{
$rowid = $this->input->post('rowid', true);
$is_ajax = $this->input->is_ajax_request();
$result = array(
'status' => 'error',
'message' => 'Такого товара нет в корзине',
);
if ($rowid) {
$data = array(
'rowid' => $rowid,
'qty' => 0,
);
$this->cart->update($data);
$user_cart = $this->cart->contents();
$cart_html = $this->load->view('cart-view', array('cart_items' => $user_cart), true);
$result = array(
'status' => 'success',
'message' => 'Товар успешно удалён из корзины',
'cart_html' => $cart_html,
);
}
if ($is_ajax) {
$this->output->set_content_type('application/json');
$this->output->set_output(json_encode($result));
} else {
$this->session->set_flashdata('message', $result);
redirect(base_url());
}
}
Теперь посмотрим что получилось:
++++
remove-from-cart.gifНа этом основной этам разработки корзины завершён. Далее можно будет совершенствовать корзину. Например добавить туда:
- Увеличение количества товара в корзине при повторном добавление товара в корзину.
- Возможность изменять количество товара прямо в блоке с корзиной.
- Строка с общей суммой заказа.
В общем много ещё чего. Вы держитесь там, успехов, удачи.