
Witamy w II części tutoriala o Mustache JS i Express. Dziś dokończymy dzieło rozpoczęte w części I.
Mustache JS i jQuery w akcji
Dotąd omówiliśmy podstawy pracy z biblioteką Mustache, przygotowaliśmy dane testowe oraz serwer w node.js. Teraz pora na oprogramowanie warstwy front-end.
W naszym przypadku użyjemy:
Bootstrap v3.3.5
jQuery v1.11.3
jQuery Easing v1.3 plugin
Ionicons
mustache JS
Kolejny zatem krok to strona html5, stworzona w oparciu o Bootstrap.
Szczególnie zwróćmy uwagę na 2 elementy – nawigację oraz szablony Mustache.
Fragment kodu – top menu z koszykiem na zakupy z boku:
... <nav class="navbar navbar-default navbar-fixed-top" role="navigation"> <div class="container"> <div class="navbar-header page-scroll"> <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-ex1-collapse"> <span class="sr-only">Toggle navigation</span> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> </button> <a class="navbar-brand page-scroll" href="#page-top"> <span class="logo-text">Code.js Food</span> </a> </div> <div class="collapse navbar-collapse navbar-ex1-collapse"> <ul class="nav navbar-nav center-nav"> <li class="hidden"> <a class="page-scroll" href="#page-top"></a> </li> <li> <a class="page-scroll" href="#pizza"> <span> <i class="ionicons ion-pizza"></i> Pizza </span> </a> </li> ... </ul> </div> <div class="order-preview"> <a class="page-scroll" href="#order" title="Ordered items"> <i class="glyphicon glyphicon-shopping-cart"></i> <strong id="order-items-count">(0)</strong> </a> </div> </div> </nav> ...
Dalej tworzymy poszczególne sekcje.
Proszę o przejrzenie pełnego kodu strony index.html, który znajduje się tutaj:
https://github.com/dominik-w/express-food-app/blob/master/index.html
Warto również rzucić okiem na style CSS dostępne tutaj:
https://github.com/dominik-w/express-food-app/blob/master/public/css/app.css
W pliku tym znajdziemy style elementów naszej aplikacji. Warto spojrzeć na media queries, które adaptują front-end dla mniejszych ekranów:
@media(max-width:767px) { .center-nav { position: relative; left: 0.2em; width: 98%; } ...
Pora na szablon Mustache:
<script id="pizzas-grid" type="text/template"> <div class="col-xs-4 col-md-4 pizza-col-item"> <img class="photo-sml" src="{{img}}" title="Details: {{details}}" alt="Pizza"> <h6 class="pizza-title"><em>{{name}}</em></h6> <div class="pizza-price">{{price_format}}</div> <div title="Add to cart" class="pizza-add-to-cart"> <a href="javascript:pizzaApp.addPizzaToCart('{{name}}', '{{price}}', '{{index}}')"> <i class="glyphicon glyphicon-shopping-cart"></i> (+) <span class="cart-added-blinker" id="blinker-{{index}}"> Added to cart</span> </a> </div> </div> </script>
Tak to wygląda. Mieszamy kod szablonu z placeholderami, w których zostaną podstawione prawdziwe wartości z przetworzonych danych.
Przejdźmy teraz do ożywienia całości.
Plik pizza_app.js – obsługa front-end
jQuery(document).ready(function ($) { // Handle page scrolling using Easing plugin $(function () { $('a.page-scroll').bind('click', function (event) { var $anch = $(this); try { $('html, body').stop().animate({ scrollTop: $($anch.attr('href')).offset().top }, _scroll_speed, 'easeInOutExpo'); } catch (e) { ; } event.preventDefault(); }); }); // Load the initial data pizzaApp.loadPizza(); pizzaApp.loadDrinks(); });
Poza kodem korzystającym z jquery.easing, który definiuje animacje przejść między sekcjami, wywoływane są także metody wczytujące dane naszego menu z jedzeniem i drinkami. Zaimplementujemy je więc.
Wczytywanie danych menu via AJAX:
pizzaApp.loadPizza = function () { var url = _app_host + '/pizzas/'; $.get(url, function () { // processData: false }).done(function (data) { pizzaApp.cb_loadPizza(data); }).fail(function () { $('#pizzas-items').append("A problem: cannot retrieve menu items."); }); }; // callback pizzaApp.cb_loadPizza = function (data) { // data = JSON.parse(data); if (data.error > 0) { alert(data.msg); return false; } var template = $('#pizzas-grid').html(); try { for (var i = 0; i < data.length; ++i) { var obj = data[i]; // add extra fields - index and formatted price obj.index = i; obj.price_format = _currency + ' ' + obj.price.toFixed(2); var html = Mustache.to_html(template, obj); $('#pizzas-items').append(html); } } catch (e) { console.error(e); } };
Właśnie w funkcji callback dzieje się cała magia z użyciem Mustache JS. Następuje przetwarzanie szablonu, który zdefiniowaliśmy w skrypcie o ID „pizzas-grid”.
Następuje wczytanie szablonu:
var template = $('#pizzas-grid').html();
a dalej wygenerowanie HTML wyjściowego i dodanie go do strony:
var html = Mustache.to_html(template, obj); $('#pizzas-items').append(html);
Pomiędzy generowane i wstawiane są dodatkowe, przydatne dane.
Sytuacja dla drinków jest podobna, z tym że pracujemy z osobnymi szablonami – dla każdej kategorii napoju.
Pełny kod dostępny na GitHubie:
https://github.com/dominik-w/express-food-app/blob/master/public/js/pizza_app.js
Znajdziemy tam kolejne metody, takie jak obsługa dodania pozycji z menu do koszyka:
pizzaApp.addPizzaToCart = function (name, price, idx) { var pizzaObj = {name: name, price: parseFloat(price)}; shoppingCartPizzas.push(pizzaObj); // update items counter var count = shoppingCartPizzas.length + shoppingCartDrinks.length; $('#order-items-count').text('(' + count + ')'); // update data in "Your order" area pizzaApp.rebuildOrderArea(); // some effects on dynamic elements $('#blinker-' + idx).show('slow').delay(800).fadeOut(200); $('.order-preview').fadeTo('fast', 0.1).fadeTo('fast', 1.0); };
To prosty kod, który dodaje sformatowane dane do wirtualnego koszyka i odświeża wyświetlane informacje. Wszystko z prostymi efektami animacji.
Dalej zdefiniowano walidację, która w przypadku wyniku pozytywnego, wywołuje metodę pizzaApp.submit() – oto jej kod:
pizzaApp.submit = function () { var url = _app_host + '/submit'; // pack whole order and post to the server var output = { pizzas: shoppingCartPizzas, drinks: shoppingCartDrinks, address: shoppingCartAddress, total: _total }; var call = $.ajax({ type: 'POST', data: JSON.stringify(output), contentType: 'application/json', url: url }); // callbacks call.fail(function (jqXHR, textStatus) { alert("Request failed: " + textStatus); }); call.done(function (data) { // console.log(JSON.stringify(data)); $("#pizza-alert-dialog").modal('show'); $("#alert-validation-msg").html("<strong>Ready!</strong>"); // make cleanups shoppingCartPizzas.length = 0; shoppingCartDrinks.length = 0; shoppingCartAddress.length = 0; _total = 0.0; $('#order-items-count').text('(0)'); $('#submit-area').hide(); $('#order-items').html('No items yet.'); }); };
Całe zamówienie użytkownika zostaje wysłane metodą POST, poprzez AJAX, do serwera w node.js, który napisaliśmy w poprzedniej części.
I to wszystko. Możemy jeść:
$ npm start
Polecam także dobry tutorial o samym Mustache JS:
http://coenraets.org/blog/2011/12/tutorial-html-templates-with-mustache-js
Z kolei cały projekt jest dostępny na GitHub:
https://github.com/dominik-w/express-food-app
Podsumowanie
Gorąco zachęcam do analizy całego kodu aplikacji. Mam nadzieję że ukaże on piękno i użyteczność nie tylko node.js i frameworku Express, ale także samej biblioteki Mustache JS, dzięki której nie musieliśmy programować ręcznie całego żmudnego przetwarzania danych wejściowych.
Dziękuję za uwagę i miłego programowania!