Tvorba moderního eshopu: kategorie a parametrické hledání

Jak se pracuje se stromovými strukturami v AngularJS? Jak snadno vytvořit pomocí tohoto frameworku parametrické hledání, které je již nyní součástí mnoha internetových obchodů? A jak vypadá použití HTML5 slideru ve formulářích? Tato a další témata jsou náplní dnešního dílu o tvorbě moderního eshopu.
Seriál: E-shop pomocí moderních technologií (15 dílů)
- Úvodní analýza pro moderní e-shop 4. 1. 2013
- Návrh uživatelské části e-shopu 11. 1. 2013
- Tvorba uživatelské části e-shopu 18. 1. 2013
- Nákupní košík pomocí HTML5 Web Storage 25. 1. 2013
- Tvorba moderního eshopu: kategorie a parametrické hledání 1. 2. 2013
- Tvorba moderního e-shopu: dokončení uživatelské části 8. 2. 2013
- Tvorba moderního e-shopu: plánování administrace 15. 2. 2013
- Tvorba moderního e-shopu: správa objednávek 22. 2. 2013
- Tvorba moderního e-shopu: nahrávání obrázků k produktu 1. 3. 2013
- Tvorba moderního e-shopu: Bower, Yeoman a Gemnasium 15. 7. 2013
- Tvorba moderního e-shopu: HTML5 drag & drop a kategorie 29. 7. 2013
- Tvorba moderního e-shopu: zpracování chyb 12. 8. 2013
- Tvorba moderního e-shopu: Rich-Text Editing a dokončení administrace 26. 8. 2013
- Autentizace v single-page aplikacích 9. 9. 2013
- Autentizace v single-page aplikacích – serverová část 7. 10. 2013
Podobně jako v minulých dílech je i dnes dostupná demoverze a všechny zdrojové kódy jsou dostupné na Githubu pod tagem eshop05.
Výpis kategorií v postranním menu
Existují různé způsoby, jak lze celý strom kategorií vypsat. Za nejjednodušší považuji vytvoření šablony, která se bude pro jednotlivé podkategorie vypisovat v případě, že rodičovská kategorie má nějaké podkategorie.
Šablony je možné v AngularJS definovat buď externě, a pak se načítají klasickým HTTP požadavkem, nebo se vytváří lokálně uvnitř jiné šablony. Lokální šablony se definují uvnitř HTML elementu script
, který má však atribut type
nastaven na hodnotu text/ng-template
. Důležité je také zadání atributu id
, abychom se pak na danou šablonu mohli odkazovat.
Zdrojový kód lokálně vytvořené šablony tedy bude vypadat takto:
<script type="text/ng-template" id="template-category">
<a ng-href="/mobily/{{data.url}}">{{data.name}}</a>
<ul><li ng-repeat="data in data.children" ng-include="'template-category'"></li></ul>
</script>
<ul><li ng-repeat="data in categories" ng-include="'template-category'"></li></ul>
Direktiva ng-include načte do elementu li
obsah dané šablony. Tímto způsobem lze velmi snadno vypsat celou stromovou strukturu.
Parametrické vyhledání
Parametrické vyhledávání je dnes součástí téměř jakékoliv aplikace, kde potřebujeme vyhledávat podle více kritérií. Je poměrně těžké představit si e-shop se sortimentem jako má naše vzorová aplikace bez možnosti odfiltrovat produkty, které kupovat nechceme.
Filtrování v kategorii v naší aplikaci bude vypadat takto:
Filtrování podle parametrů
V našem případě necháme uživateli možnost v každé kategorii omezit výsledky podle vybraných parametrů (které parametry budou v dané kategorii dostupné pro filtrování, si nastaví správce sám v administraci). Parametry mohou být dvojího druhu, číselníky nebo hodnoty:
- Parametr typu číselník se skládá ze seznamu všech dostupných hodnot pro daný parametr, bude to např. parametr Operační systém a hodnoty Android, iOS ap.
- Parametr typu hodnota bude prostá číselná hodnota a použije se třeba pro parametr Váha, kde správce zadává unikátní hodnotu pro daný přístroj. V uživatelské části jsou pak z těchto hodnot vytvořeny rozsahy, které se zobrazují podobně jako klasický číselník. Z pohledu uživatelské části se tedy s oběma typy parametrů zachází úplně stejně.
Tyto parametry budou vypisovány v uživatelské části jako boxy. Jednotlivé hodnoty vypíšeme jako element input
typu checkbox
, takže uživatel může vybrat libovolné množství hodnot pro daný parametr.
Filtrování podle ceny
Vedle filtrování dle parametrů bude chtít drtivá většina uživatelů filtrovat výsledky podle ceny. Obvykle budou mít uživatelé představu o maximální ceně, kterou jsou ochotni za telefon zaplatit. Abychom je nenutili zadávat číselnou hodnotu, používá se k tomu účelu posuvný slider. Často se implementuje pomocí různých javascriptových knihoven, které jsou ale bohužel často špatně přístupné pro dotyková zařízení (nelze posunout ukazatel kliknutím na osu), naštěstí HTML5 přináší možnost použít element input
typu range
, který slider zobrazí.
Ačkoliv je v moderních prohlížečích input
typu range
již podporován, je zde bohužel jedna významná výjimka. Firefox v aktuální verzi tuto vlastnost nepodporuje, nicméně již v příští verzi se pravděpodobně situace změní. Naše aplikace je pouze vzorová a nejde do reálného provozu, můžeme si dovolit být trochu nadčasoví a neomezovat se aktuální slabší podporou novinek v HTML5. Navíc nepodpora prohlížeče nebrání používání aplikace, pouze je pro uživatele méně použitelnější (uživateli se zobrazí klasické textové pole).
Pro slider je potřeba zadat v elementu input
ještě atribut min
a max
pro minimální a maximální hodnotu a step
, který říká, o jakou hodnotu posouvat jeden krok. HTML může vypadat tedy takto:
<input type="range" min="1" max="10" step="1">
Implementace v AngularJS je o něco obtížnější, protože AngularJS zatím přímo input
typu range
nepodporuje (resp. není možné používat výrazy uvnitř atributů min
, max
ap.), proto vytvoříme direktivu range
, která slider nahradí. Kromě toho v ní můžeme ošetřit, abychom slider vytvořili pomocí nějaké javscriptové knihovny pro prohlížeče, které ho nepodporují.
Kód direktivy může vypadat takto:
zdrojak.directive('range', function(){
return {
restrict: 'E',
replace: true,
scope: {
max: '@max',
min: '@min',
model: '=model',
change: '=change'
},
template: '<input type="range" ng-model="model">',
link: function(scope, element) {
element.bind('change', scope.change);
}
}
});
V šabloně se direktiva zapíše takto:
<range change="filter" min="{{params.minPrice}}" max="{{params.maxPrice}}" step="500" model="price">
Řazení výsledků
Kromě toho také musíme nechat uživateli možnost řadit výsledky. Nepamatuji si, že bych někdy v e-shopu používal jiné řazení než podle ceny, takže nechám uživateli dvě možnosti řazení: podle ceny vzestupně a sestupně. Z pohledu UX bude nejjednodušší pod formulář vložit dva tlačítka s popisem, podle čeho řadit.
Pro jakékoliv podobné prvky silně doporučuji používat co nejjednodušší rozhraní (co do počtu kroků) a dostatečně velké ovládací prvky, z vlastní zkušeností ročního uživatele tabletu můžu říct, že výborně použitelný e-shop z prohlížeče na notebooku může být tragicky použitelný na tabletu, což pak vede k tomu, že raději objednám zboží tam, kde mi tvůrce aplikace překážky nestaví.
Úpravy v API
Nejjednodušší bude upravit výsledky, které vrací dotaz na danou kategorii. Kromě toho bude vracet ještě informace o minimální a maximální ceně všech produktů v kategorii, takže kód v Apiary pro pravidlo na detail kategorie změníme takto:
Detail kategorie podle URL.
GET /categories?url={url}
< 200
< Content-Type: application/json
{"name": "iPhone",
"url": "iphone",
"children": [],
"minPrice": 1000,
"maxPrice": 23000,
"params": [{
"name": "Funkce",
"code": "funkce",
"values": [
{"code": "wifi", "value": "WiFi"},
{"code": "bluetooth", "value": "BlueTooth"},
{"code": "dual-sim", "value": "Dual SIM"},
{"code": "gps", "value": "GPS"},
{"code": "fm-radio", "value": "FM radio"}
]},{
"name": "Operační paměť",
"code": "operacni-pamet",
"values": [
{"code": "256", "value": "256 MB"},
{"code": "512", "value": "512 MB"},
{"code": "1024", "value": "1024 MB"},
{"code": "2048", "value": "2048 MB"},
{"code": "4096", "value": "4096 MB"}
]},{
"name": "Uložiště",
"code": "uloziste",
"values": [
{"code": "0-2", "value": "0-2 GB"},
{"code": "2-10", "value": "2-10 GB"},
{"code": "10-50", "value": "10-50 GB"},
{"code": "50-100", "value": "50-100 GB"},
{"code": "100-512", "value": "100-512 GB"}
]},{
"name": "Paměťová karta",
"code": "pametova-karta",
"values": [
{"code": "sdhc", "value": "SDHC"},
{"code": "micro-sd", "value": "Micro SD"},
{"code": "micro-sdxc", "value": "Micro SDXC"},
{"code": "sdxc", "value": "SDXC"},
{"code": "micro-sdhc", "value": "Micro SDHC"}
]}
]}
Vytvoření celého filtrování v šabloně je pak jednoduché, jak je vidět v šabloně category.html.
V případě, že uživatel změní maximální cenu či vybere jiný způsob řazení, vyvolá se akce filter()
controlleru CategoryCtrl
. Ta sesbírá všechny informace o aktuálním nastavení filtrů a aktualizuje seznam odpovídajících produktů.
Kód akce bude vypadat takto:
$scope.filter = function() {
var query = {sort: $scope.sort};
var params = ['price:' + $scope.price];
$scope.category.params.forEach(function(param){
var vals = [];
param.values.forEach(function(value){
if (value.checked) vals.push(value.code);
});
if (vals.length > 0){
params.push(param.code + ':' + vals.join(','));
}
});
query.filter = params.join('@');
$scope.products = api.product.index(query);
$location.search('filter', query.filter).search('sort', $scope.sort);
};
Aktualizace URL
Při úpravách nastavení filtrů bychom měli také měnit URL, aby fungoval trvalý odkaz. To lze zajistit pomocí HTML5 History API. V angularJS stačí u routy pro detail kategorie (v souboru public/js/app.js) nastavit klíč reloadOnSearch
na hodnotu false
. Změny přes servisní objekt $location
pak nezpůsobí reload prohlížeče.
Za zmínku také stojí použitý formát URL. Všechny informace o nastavení filtrů budeme mít v HTTP query parametru filter
, takže URL pak bude vypadat například takto:
/mobily/android?filter=price:23000@funkce:bluetooth,gps@operacni-pamet:256,512,1024@uloziste:100-512@pametova-karta:micro-sd,micro-sdxc&sort=price
Pro oddělovače hodnot se použije zavináč, klíče od hodnot jsou odděleny dvojtečkou a všechny vybrané hodnoty jednoho parametru jsou odděleny čárkou. Mohli bychom použít standardní způsob serializace hodnot formulářů, ale ten má pro nás několik nevýhod jako např. problematičtější použití v Apiary a ngMockE2E, dále je taková URL výrazně kratší a pak jsou také přehlednější (a subjektivně i pěknější).
Nakonec zbývá při prvním nahrání controlleru zjistit, zda je v URL zadaná výše ceny, způsob řazení či nějaké parametry a pokud ano, stačí podle toho zaškrtnout hodnoty ve filtrování.
Co dále?
Příště budeme pokračovat v dokončování uživatelské části webu. Potřebujeme ještě upravit detail produktu, stránkování do kategorií a vyhledávání, chybové stránky a zpracování chyb a různé další drobnější úkony.