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ů)

  1. Úvodní analýza pro moderní e-shop 4.1.2013
  2. Návrh uživatelské části e-shopu 11.1.2013
  3. Tvorba uživatelské části e-shopu 18.1.2013
  4. Nákupní košík pomocí HTML5 Web Storage 25.1.2013
  5. Tvorba moderního eshopu: kategorie a parametrické hledání 1.2.2013
  6. Tvorba moderního e-shopu: dokončení uživatelské části 8.2.2013
  7. Tvorba moderního e-shopu: plánování administrace 15.2.2013
  8. Tvorba moderního e-shopu: správa objednávek 22.2.2013
  9. Tvorba moderního e-shopu: nahrávání obrázků k produktu 1.3.2013
  10. Tvorba moderního e-shopu: Bower, Yeoman a Gemnasium 15.7.2013
  11. Tvorba moderního e-shopu: HTML5 drag & drop a kategorie 29.7.2013
  12. Tvorba moderního e-shopu: zpracování chyb 12.8.2013
  13. Tvorba moderního e-shopu: Rich-Text Editing a dokončení administrace 26.8.2013
  14. Autentizace v single-page aplikacích 9.9.2013
  15. 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:

Parametrické hledání

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.

Zatím nebyl přidán žádný komentář, buďte první!

Přidat komentář
Zdroj: https://www.zdrojak.cz/?p=6883