Начнем с предыстории
Давно я что-то не писал в блог. А ведь столько всего произошло и продолжает происходить в мире информационных технологий.
Давно уже поверхностно знакомился с фрейворком для верстки Twitter Bootstrap и JavaScript-фреймворком для разработки веб-приложений AngularJS.
Очень интересные штуки и я просто мечтал выкроить время, чтобы хоть где-то на практике их использовать. И вот это день настал - ко мне обратился бывший сокурсник с просьбой разработать сайт интернет-магазин. Сайт ему нужен был полностью, от "а до я". Дизайнер из меня никакой, поэтому сразу в голову пришла идея - использовать готовое решение Twitter Bootstrap, на мой взгляд его компоненты выглядят достаточно приятно и современно, к тому же накануне вышла обновленная 3-я версия с серьезными переработками и улучшениями. Идея попробовать AngularJS пришла уже по ходу разработки функционала корзины.
Да, еще обратил внимание, что у меня блогозаписи практически не проиллюстрированы и поэтому воспринимаются хуже. Исправляюсь - теперь по возможности будет больше картинок. Неужто, красота Twitter Bootstrap дала мне понять насколько скучны мои посты с одним лишь текстом и вкраплениями кусков кода? :-)
Twitter Bootstrap
Его я использовал на всем сайте. Хотелось бы, конечно, еще красивых рисованных элементов дизайна, но рисовать я не умею, так что пришлось довольствоваться глифами. Зато на сайте акцент будет сделан на красивые фото товаров.
Twitter Bootstrap - это набор готовых стилей для таких элементов как кнопки, метки, панели, списки, элементы форм и многие другие. Фреймворк также включает в себя готовые стили для типографики и небольшой, но полезный набора JavaScript-плагинов.
Важно, что имеется поддержка адаптивного дизайна - сайт сам подстраивается под разрешение устройства. Сетка включает в себя 12 колонок и с помощью классов (префиксов) можно управлять их размерами и положениями для 4-х видов устройств:
- .col-xs-: экстра-маленькие устройства, телефоны (<768px)
- .col-sm-: маленькие устройства, планшеты (≥768px)
- .col-md-: средние устройства, PC (≥992px)
- .col-lg-: большие устройства, PC (≥1200px)
Как минимум, верстка заключается в написании HTML-кода и прописывании тегам готовых классов и data-атрибутов для JavaScript-плагинов. В итоге для всей верстки на Twitter Bootstrap мне практически не пришлось писать свои стили.
AngularJS
Мощный JavaScript-фреймворк, удобен для разработки одностраничных веб-приложений.
С ним я еще не очень хорошо разобрался, ибо применил его только для реализации небольшого функционала корзины.
Корзина с заказами и форма заявки
Основной функционал сайта заключен в разделе "Корзина".
Рассмотрим функционал сначала в картинках, а потом и в коде.
Список товаров в заказе:
Товар пометили как удаляемый:
Форма с обязательным полем "Телефон":
Форма с одним верно заполненным полем и активным:
Форма с неверно заполненным полем:
Все обязательные поля заполнены, все поля заполнены верно:
Далее приведен реализующий весь этот функционал JavaScript- и HTML-код с комментариями.В JavaScript объявляются контроллеры и модели:
function CartForm($scope) {
// корзина в json
$scope.items = [{"id":34,"quantity":2,"info":"\u043e\u0434\u0438\u043d \u0441\u0435\u0440\u044b\u0439, \u0434\u0440\u0443\u0433\u043e\u0439 \u0431\u0435\u043b\u044b\u0439","name":"\u0422\u0435\u0441\u0442\u043e\u0432\u044b\u0439 \u0442\u043e\u0432\u0430\u0440\u0447\u0438\u043a","price":"123","detail_url":"\/katalog\/kpb\/byaz\/byaz-15-sp\/34\/","icon_url":"\/bitrix\/templates\/tb\/images\/1.png","picture_url":null,"nav":[{"name":"\u041a\u041f\u0411","url":"\/katalog\/kpb\/"},{"name":"\u0411\u044f\u0437\u044c","url":"\/katalog\/kpb\/byaz\/"},{"name":"1,5 \u0441\u043f.","url":"\/katalog\/kpb\/byaz\/byaz-15-sp\/"}]}];
// -1 товар
$scope.minus = function(index) { if ($scope.items[index].quantity > 0) { $scope.cartform.$setDirty(); $scope.items[index].quantity--; } };
// +1 товар
$scope.plus = function(index) {
$scope.cartform.$setDirty();
$scope.items[index].quantity++;
};
// удаление товара (сброс кол-ва в 0)
$scope.removeItem = function(index, id) {
$scope.cartform.$setDirty();
$scope.items[index].quantity = 0;
};
// подсчет итоговой суммы
$scope.total = function() {
var total = 0;
angular.forEach($scope.items, function(item) {
total += item.quantity * item.price;
});
return total;
};
// проверка корзины на пустоту
$scope.has_items = function() {
return $scope.items.length > 0;
};
// содержимое корзины
$scope.items_cart = function() {
var items = [];
angular.forEach($scope.items, function(item) {
items.push({
id: item.id,
quantity: item.quantity,
info: item.info
});
});
return items;
};
// флаг, указывающий на процесс обновления корзины на сервере
$scope.cartproc = false;
// процесс обновления корзины на сервере
$scope.save = function() {
$scope.cartproc = true;
var items = $scope.items_cart();
$.post('/cart/cart.php', {'cart': items}, function(data, textStatus, jqXHR) {
var ids = jQuery.map(data, function(el) {
return el.id;
});
$scope.items = jQuery.grep($scope.items, function(el) {
return jQuery.inArray(el.id, ids) >= 0;
});
$scope.cartproc = false;
// помечаем форму как не тронутую
$scope.cartform.$setPristine();
$('#cart-size').html(data.length);
// применяем изменения модели для обновления представления
$scope.$apply();
}, 'json');
};
// флаг, указывающий на процесс отправки заявки на сервере
$scope.orderproc = false;
// процесс обновления корзины на сервере
$scope.send = function() {
$scope.orderproc = true;
var items = $scope.items_cart();
var order = $scope.order;
$.post('/cart/cart.php', {'cart': items, 'order': order}, function(data, textStatus, jqXHR) {
var ids = jQuery.map(data, function(el) {
return el.id;
});
$scope.items = jQuery.grep($scope.items, function(el) {
return jQuery.inArray(el.id, ids) >= 0;
});
$scope.orderproc = false;
// помечаем форму как не тронутую
$scope.cartform.$setPristine();
$('#cart-size').html(data.length);
// плагин Twitter Bootstrap для модальных окон
$('#order-alert').modal();
// применяем изменения модели для обновления представления$scope.$apply(); }, 'json'); };
// модель заявки
$scope.order = {
name: '',
email: '',
phone: '',
address: '',
comments: ''
};
}
HTML:
<!-- приложение AngularJS --> <div class="container" ng-app> <!-- контроллер CartForm --> <div ng:controller="CartForm"> <div class="row"> <form novalidate id="cartform" name="cartform"> <!-- table-responsive: на маленьких разрешениях у корзины появится полоса прокрутки и вся страница не будет растянута --> <div class="table-responsive"> <table class="table"> <thead> <tr> <th>Товар</th> <th>Фото</th> <th>Количество и комментарии</th> <th>Цена</th> <th>Сумма</th> <th></th> </tr> </thead> <tbody> <-- 1) вывод товаров (item) из корзины (items) 2) условие: если заказываемое кол-во товара равно 0, то он считается удаляемым и помечается полупрозрачной строкой --> <tr ng:repeat="item in items" ng-class="{opacity50: item.quantity < 1}"> <td> <!-- вывод название товара, ссылки на его страницу и "хлебных крошек" разделов до его страницы --> <p><a href="{{item.detail_url}}">{{item.name}}</a></p> <ol class="breadcrumb"> <li ng:repeat="sect in item.nav"> <a href="{{sect.url}}">{{sect.name}}</a> </li> </ol> </td> <td class="carousel-extended"> <!-- ng-show: картинка будет показана только если она задана в модели товара --> <a href="{{item.picture_url}}" ng-show="item.picture_url" class="thumbnail image-detail"> <img src="{{item.icon_url}}" width="50" height="50" alt="{{item.name}}"> </a> </td> <td> <div class="row"> <div class="form-group col-xs-8" ng-class="{'has-error': cartform.quantity[{{item.id}}].$invalid}"> <-- для элемента формы задаются правила валидации --> <input type="text" name="quantity[{{item.id}}]" ng:model="item.quantity" ng:required ng:pattern="/^\d+$/" min="0" class="form-control input-sm"> </div> <!-- ng-click: уменьшение/увеличение заказываемого кол-ва --> <button ng-click="minus($index)" class="btn btn-default input-sm glyphicon glyphicon-minus"></button> <button ng-click="plus($index)" class="btn btn-default input-sm glyphicon glyphicon-plus"></button> </div> <textarea ng:model="item.info" class="form-control input-sm"></textarea> </td> <td>{{item.price}}</td> <td>{{item.quantity * item.price}}</td> <td><a href data-toggle="tooltip" title="пометить для удаления" data-placement="left" class="pointer helptip glyphicon glyphicon-remove text-danger" ng:click="removeItem($index, item.id)"></a></td> </tr> </tbody> <tfoot> <tr> <td colspan="2"> <-- ng-show="cartproc": полоса прогресса будет показана пока установлен флаг cartproc (пока выполняется ajax-запрос) --> <div ng-show="cartproc" class="progress progress-striped active"> <div class="progress-bar" role="progressbar" aria-valuenow="100" aria-valuemin="0" aria-valuemax="100" style="width: 100%"></div> </div> </td> <-- ng-switch: в зависимости от результатов валидации будет показана либо активная, либо не активная кнопка --> <td ng-switch="cartform.$dirty && cartform.$valid && !cartproc && has_items()"> <button ng-switch-when="true" ng-click="save()" class="btn btn-success btn-xs pull-right">Сохранить изменения</button> <button ng-switch-default class="btn btn-success btn-xs pull-right" disabled>Сохранить изменения</button> </td> <td>Итого:</td> <!-- вывод итоговой суммы с применением форматирования --> <td><strong><p class="text-primary">{{total() | currency:""}}</p></strong></td> <td></td> </tr> </tfoot> </table> </div> </form> </div> <!-- ng-switch: если в корзине пусто, то вместо формы заказа будет выведено соответствующее сообщение --> <div ng-switch="has_items()"> <div ng-switch-when="true"> <div class="row"> <div class="col-md-offset-2 col-md-8"> <h3>Оформление заказа:</h3> </div> <div class="col-md-2"></div> </div> <div class="row"> <div class="col-md-offset-2 col-md-8"> <form novalidate class="form-horizontal" id="orderform" name="orderform"> <div class="form-group" ng-class="{'has-success': order.name}"> <label for="order_name" class="control-label col-lg-3">Имя:</label> <div class="col-lg-9"> <input type="text" class="form-control" id="order_name" name="order_name" ng-model="order.name"> </div> </div> <!-- в зависимости от результатов валидации элементу формы будет применен класс из Twitter Bootstrap: has-error при ошибке заполнения has-success при правильно заполненном поле --> <div class="form-group" ng-class="{'has-error': orderform.order_email.$invalid, 'has-success': !orderform.order_email.$invalid && order.email}"> <label for="order_email" class="control-label col-lg-3">Эл. почта:</label> <div class="col-lg-9"> <input type="email" class="form-control" id="order_email" name="order_email" ng-model="order.email"> </div> </div> <div class="form-group" ng-class="{'has-error': orderform.order_phone.$invalid, 'has-success': !orderform.order_phone.$invalid}"> <label for="order_phone" class="control-label col-lg-3">Телефон:</label> <div class="col-lg-9"> <input type="text" class="form-control" id="order_phone" name="order_phone" ng-model="order.phone" required> </div> </div> <div class="form-group" ng-class="{'has-success': order.address}"> <label for="order_address" class="control-label col-lg-3">Адрес:</label> <div class="col-lg-9"> <input type="text" class="form-control" id="order_address" name="order_address" ng-model="order.address"> </div> </div> <div class="form-group" ng-class="{'has-success': order.comments}"> <label for="order_comments" class="control-label col-lg-3">Комментарии к заказу:</label> <div class="col-lg-9"> <textarea type="text" class="form-control" id="order_comments" name="order_comments" ng-model="order.comments"></textarea> </div> </div> </form> </div> <div class="col-md-2"></div> </div> <div class="row"> <div class="col-md-offset-2 col-md-2" ng-switch="cartform.$valid && orderform.$valid && !orderproc && has_items()"> <button ng-switch-when="true" ng-click="send()" class="btn btn-success">Отправить</button> <button ng-switch-default class="btn btn-success" disabled>Отправить</button> </div> <div class="col-md-6"> <div ng-show="orderproc" class="progress progress-striped active"> <div class="progress-bar" role="progressbar" aria-valuenow="100" aria-valuemin="0" aria-valuemax="100" style="width: 100%"></div> </div> </div> <div class="col-md-2"></div> </div> </div> <div ng-switch-default> <div class="alert alert-warning">Корзина пуста</div> </div> </div> </div> </div>
Комментариев нет:
Отправить комментарий