Darmowy fragment publikacji:
Tytuł oryginału: Web Development Recipes
Tłumaczenie: Łukasz Piwko
ISBN: 978-83-246-5149-8
© Helion 2013.
All rights reserved.
Copyright © 2012 The Pragmatic Programmers, LLC.
All rights reserved.
No part of this publication may be reproduced, stored in a retrieval system, or transmitted,
in any form, or by any means, electronic, mechanical, photocopying, recording, or otherwise,
without the prior consent of the publisher.
Many of the designations used by manufacturers and sellers to distinguish their products are claimed
as trademarks. Where those designations appear in this book, and The Pragmatic Programmers, LLC
was aware of a trademark claim, the designations have been printed in initial capital letters or in all
capitals. The Pragmatic Starter Kit, The Pragmatic Programmer, Pragmatic Programming, Pragmatic
Bookshelf, PragProg and the linking g device are trademarks of The Pragmatic Programmers, LLC.
Wszelkie prawa zastrzeżone. Nieautoryzowane rozpowszechnianie całości lub fragmentu niniejszej
publikacji w jakiejkolwiek postaci jest zabronione. Wykonywanie kopii metodą kserograficzną,
fotograficzną, a także kopiowanie książki na nośniku filmowym, magnetycznym lub innym powoduje
naruszenie praw autorskich niniejszej publikacji.
Wszystkie znaki występujące w tekście są zastrzeżonymi znakami firmowymi bądź towarowymi ich
właścicieli.
Materiały graficzne na okładce zostały wykorzystane za zgodą iStockPhoto Inc.
Wydawnictwo HELION dołożyło wszelkich starań, by zawarte w tej książce informacje były kompletne
i rzetelne. Nie bierze jednak żadnej odpowiedzialności ani za ich wykorzystanie, ani za związane z tym
ewentualne naruszenie praw patentowych lub autorskich. Wydawnictwo HELION nie ponosi również
żadnej odpowiedzialności za ewentualne szkody wynikłe z wykorzystania informacji zawartych
w książce.
Wydawnictwo HELION
ul. Kościuszki 1c, 44-100 GLIWICE
tel. 32 231 22 19, 32 230 98 63
e-mail: helion@helion.pl
WWW: http://helion.pl (księgarnia internetowa, katalog książek)
Pliki z przykładami omawianymi w książce można znaleźć pod adresem:
ftp://ftp.helion.pl/przyklady/twstnr.zip
Drogi Czytelniku!
Jeżeli chcesz ocenić tę książkę, zajrzyj pod adres
http://helion.pl/user/opinie/twstnr
Możesz tam wpisać swoje uwagi, spostrzeżenia, recenzję.
Printed in Poland.
• Kup książkę
• Poleć książkę
• Oceń książkę
• Księgarnia internetowa
• Lubię to! » Nasza społeczność
Spis treĂci
PodziÚkowania ..............................................................................................7
WstÚp ...........................................................................................................11
Rozdziaï 1. ¥wiecideïka ..............................................................................17
Receptura 1. Stylizowanie przycisków i ïÈczy ...........................................................17
Receptura 2. Stylizowanie cytatów przy uĝyciu CSS ................................................21
Receptura 3. Tworzenie animacji przy uĝyciu transformacji CSS3 ............................28
Receptura 4. Tworzenie interaktywnych pokazów slajdów przy uĝyciu jQuery ............33
Receptura 5. Tworzenie i stylizowanie wewnÈtrztekstowych okienek pomocy ..............38
Rozdziaï 2. Interfejs uĝytkownika ............................................................47
Receptura 6. Tworzenie szablonu wiadomoĂci e-mail ................................................47
Receptura 7. WyĂwietlanie treĂci na kartach .............................................................58
Receptura 8. Rozwijanie i zwijanie treĂci z zachowaniem zasad dostÚpnoĂci ...............65
Receptura 9. Nawigacja po stronie internetowej przy uĝyciu klawiatury ......................71
Receptura 10. Tworzenie szablonów HTML przy uĝyciu systemu Mustache ..............79
Receptura 11. Dzielenie treĂci na strony ....................................................................84
Receptura 12. ZapamiÚtywanie stanu w Ajaksie ........................................................90
Receptura 13. Tworzenie interaktywnych interfejsów uĝytkownika
przy uĝyciu biblioteki Knockout.js .......................................................95
Receptura 14. Organizacja kodu przy uĝyciu biblioteki Backbone.js ..........................105
Rozdziaï 3. Dane ........................................................................................123
Receptura 15. Wstawianie na stronÚ mapy Google ...................................................123
Receptura 16. Tworzenie wykresów i grafów przy uĝyciu Highcharts ........................129
Receptura 17. Tworzenie prostego formularza kontaktowego ....................................137
Receptura 18. Pobieranie danych z innych serwisów przy uĝyciu formatu JSONP .....144
6
Web development. Receptury nowej generacji
Receptura 19. Tworzenie widĝetów do osadzenia w innych serwisach ........................147
Receptura 20. Budowanie witryny przy uĝyciu JavaScriptu i CouchDB .....................153
Rozdziaï 4. UrzÈdzenia przenoĂne ..........................................................163
Receptura 21. Dostosowywanie stron do wymogów urzÈdzeñ przenoĂnych .................163
Receptura 22. Menu rozwijane reagujÈce na dotyk ...................................................168
Receptura 23. Operacja „przeciÈgnij i upuĂÊ” w urzÈdzeniach przenoĂnych ...............171
Receptura 24. Tworzenie interfejsów przy uĝyciu biblioteki jQuery Mobile ................178
Receptura 25. Sprite’y w CSS ................................................................................187
Rozdziaï 5. Przepïyw pracy ......................................................................191
Receptura 26. Szybkie tworzenie interaktywnych prototypów stron ............................191
Receptura 27. Tworzenie prostego bloga przy uĝyciu biblioteki Jekyll ........................200
Receptura 28. Tworzenie modularnych arkuszy stylów przy uĝyciu Sass ....................207
Receptura 29. Bardziej przejrzysty kod JavaScript, czyli CoffeeScript ........................215
Receptura 30. ZarzÈdzanie plikami przy uĝyciu narzÚdzia Git ..................................222
Rozdziaï 6. Testowanie ............................................................................233
Receptura 31. Debugowanie JavaScriptu .................................................................233
Receptura 32. ¥ledzenie aktywnoĂci uĝytkowników przy uĝyciu map cieplnych ...........239
Receptura 33. Testowanie przeglÈdarek przy uĝyciu Selenium ..................................242
Receptura 34. Testowanie stron internetowych przy uĝyciu Selenium i Cucumber ......247
Receptura 35. Testowanie kodu JavaScript przy uĝyciu Jasmine ................................260
Rozdziaï 7. Hosting i wdraĝanie ..............................................................271
Receptura 36. Wspólna praca nad stronÈ poprzez Dropbox ......................................271
Receptura 37. Tworzenie maszyny wirtualnej ...........................................................275
Receptura 38. Zmienianie konfiguracji serwera WWW przy uĝyciu programu Vim ......279
Receptura 39. Zabezpieczanie serwera Apache za pomocÈ SSL i HTTPS ..............284
Receptura 40. Zabezpieczanie treĂci .......................................................................288
Receptura 41. Przepisywanie adresów URL w celu zachowania ïÈczy .......................292
Receptura 42. Automatyzacja procesu wdraĝania statycznych serwisów
za pomocÈ Jammit i Rake .................................................................296
Dodatek A. Instalowanie jÚzyka Ruby ...................................................305
Dodatek B. Bibliografia ............................................................................309
Skorowidz ..................................................................................................311
Rozdziaï 2. • Interfejs uĝytkownika
79
Receptura 10.
Tworzenie szablonów HTML
przy uĝyciu systemu Mustache
Problem
Do utworzenia naprawdÚ wyjÈtkowego interfejsu potrzebne jest zastosowanie
zarówno dynamicznych, jak i asynchronicznych technik programowania. DziÚki
Ajaksowi i takim bibliotekom jak jQuery moĝemy modyfikowaÊ interfejs uĝytkow-
nika bez zmieniania jego kodu HTML, poniewaĝ potrzebne elementy dodamy
za pomocÈ JavaScriptu. Elementy te zazwyczaj dodaje siÚ, stosujÈc technikÚ
konkatenacji ïañcuchów. Powstaïy w wyniku tego kod jest, niestety, zagmatwany
i ïatwo w nim popeïniÊ bïÈd. Peïno w nim mieszajÈcych siÚ ze sobÈ pojedynczych
i podwójnych cudzysïowów oraz niekoñczÈcych siÚ ïañcuchów wywoïañ metody
append() z biblioteki jQuery.
Skïadniki
jQuery
Mustache.js
RozwiÈzanie
Na szczÚĂcie, sÈ nowoczesne narzÚdzia, takie jak Mustache, dziÚki którym moĝemy
pisaÊ prawdziwy kod HTML, renderowaÊ przy jego uĝyciu dane oraz wstawiaÊ
go do dokumentów. Mustache to system szablonów HTML dostÚpny w kilku
popularnych jÚzykach programowania. Implementacja JavaScript pozwala na
pisanie widoków dla klienta przy uĝyciu czystego kodu HTML caïkowicie
oddzielonego od kodu JavaScript. Moĝna w nim uĝywaÊ takĝe instrukcji logicz-
nych i iteracji.
Mustache pozwala uproĂciÊ tworzenie kodu HTML podczas generowania nowej
treĂci. SkïadniÚ tego narzÚdzia poznamy na przykïadzie aplikacji JavaScript do
zarzÈdzania produktami.
Obecnie aplikacja ta umoĝliwia dodawanie nowych produktów do listy. Ponie-
waĝ wszystkie ĝÈdania sÈ obsïugiwane przez JavaScript i Ajax, w przykïadzie tym
uĝywamy naszego standardowego serwera roboczego. Gdy uĝytkownik wypeïni
formularz dodawania nowego produktu, wysyïa do serwera ĝÈdanie zapisania
80
Web development. Receptury nowej generacji
tego produktu i wyrenderowania nowego produktu na liĂcie. Aby dodaÊ produkt
do listy, musimy zastosowaÊ konkatenacjÚ ïañcuchów, której kod jest zagmatwany
i nieelegancki:
mustache/submit.html
var newProduct = $( li /li );
newProduct.append( span class= product-name +
product.name + /span );
newProduct.append( em class= product-price +
product.price + /em );
newProduct.append( div class= product-description +
product.description + /div );
productsList.append(newProduct);
Aby uĝyÊ systemu Mustache.js, wystarczy go tylko zaïadowaÊ na stronÚ. JednÈ
z wersji tego pliku znajdziesz w pakiecie kodu towarzyszÈcym tej ksiÈĝce, ale
moĝesz teĝ pobraÊ jego najnowszÈ wersjÚ z serwisu GitHub7.
Renderowanie szablonu
Aby przerobiÊ naszÈ istniejÈcÈ aplikacjÚ, przede wszystkim musimy dowiedzieÊ
siÚ, jak wyrenderowaÊ szablon przy uĝyciu Mustache. Najprostszym sposobem na
zrobienie tego jest uĝycie funkcji to_html().
Mustache.to_html(templateString, data);
Funkcja ta przyjmuje dwa argumenty. Pierwszy jest ïañcuchem szablonowego kodu
HTML, w którym ma odbywaÊ siÚ renderowanie, a drugi to dane, które majÈ
zostaÊ do tego szablonu wstawione. Zmienna data jest obiektem, którego klucze
w szablonie zostajÈ zamienione na lokalne zmienne. Spójrz na poniĝszy kod:
var artist = {name: John Coltrane };
var rendered = Mustache.to_html( span class= artist name {{ name }} /span ,
artist);
$( body ).append(rendered);
Zmienna rendered zawiera nasz ostateczny kod HTML zwrócony przez metodÚ
to_html(). Aby umieĂciÊ wïasnoĂÊ name w naszym kodzie HTML, uĝyliĂmy
znaczników Mustache ograniczonych podwójnymi klamrami. W klamrach tych
umieszcza siÚ nazwy wïasnoĂci. Ostatni wiersz kodu dodaje wyrenderowany kod
HTML do elementu body .
Jest to najprostsza technika renderowania szablonu przy uĝyciu Mustache.
W naszej aplikacji bÚdzie wiÚcej kodu zwiÈzanego z wysyïaniem ĝÈdania do serwera
w celu pobrania danych, ale proces tworzenia szablonu pozostanie bez zmian.
7 https://github.com/janl/mustache.js
Rozdziaï 2. • Interfejs uĝytkownika
81
Podmienianie istniejÈcego systemu
Skoro wiemy juĝ, jak siÚ renderuje szablony, moĝemy z naszego programu usunÈÊ
stary kod konkatenacji. Przyjrzymy mu siÚ, aby zobaczyÊ, co moĝna usunÈÊ,
a co trzeba podmieniÊ.
mustache/submit.html
function renderNewProduct() {
var productsList = $( #products-list );
var newProductForm = $( #new-product-form );
var product = {};
product.name = newProductForm.find( input[name*=name] ).val();
product.price = newProductForm.find( input[name*=price] ).val();
product.description =
newProductForm.find( textarea[name*=description] ).val();
var newProduct = $( li /li );
newProduct.append( span class= product-name +
product.name + /span );
newProduct.append( em class= product-price +
product.price + /em );
newProduct.append( div class= product-description +
product.description + /div );
productsList.append(newProduct);
productsList.find( input[type=text], textarea ).each(function(input) {
input.attr( value , );
});
}
Ten skomplikowany kod bardzo trudno jest rozszyfrowaÊ, a jeszcze trudniej go
modyfikowaÊ. Dlatego zamiast metody append() jQuery do budowy struktury
HTML uĝyjemy systemu Mustache. Moĝemy napisaÊ prawdziwy kod HTML,
a nastÚpnie wyrenderowaÊ dane przy uĝyciu Mustache! Pierwszym krokiem
w kierunku pozbycia siÚ plÈtaniny JavaScriptu jest zbudowanie szablonu. Póěniej
wyrenderujemy go wraz z danymi produktów w jednym prostym procesie.
JeĂli utworzymy element script z typem treĂci text/template, to bÚdziemy mogli
w nim umieĂciÊ kod HTML Mustache i pobieraÊ go stamtÈd do szablonu.
Nadamy mu identyfikator, aby móc siÚ do niego odwoïywaÊ z kodu jQuery.
script type= text/template id= product-template
!-- Szablon HTML --
/script
NastÚpnie napiszemy kod HTML naszego szablonu. Mamy juĝ produkt w postaci
obiektu, a wiÚc jego wïasnoĂci w szablonie moĝemy uĝyÊ jako nazw zmiennych:
82
Web development. Receptury nowej generacji
JaĂ pyta:
Czy moĝna uĝywaÊ zewnÚtrznych szablonów?
Szablony wewnÚtrzne sÈ porÚczne, ale my chcemy oddzieliÊ logikÚ szablo-
nowÈ od widoków serwera. Na serwerze naleĝaïoby utworzyÊ folder
do przechowywania wszystkich plików widoków. NastÚpnie, gdy trzeba
wyrenderowaÊ jeden z szablonów, pobieramy go za pomocÈ jQery
i ĝÈdania GET.
$.get( http://mojastrona.com/js_views/zewnetrzny_szablon.html ,
function(template) {
Mustache.to_html(template, data).appendTo( body );
}
);
W ten sposób moĝna serwowaÊ widoki serwera osobno od widoków
klienta.
script type= text/template id= product-template
li
span class= product-name {{ name }} /span
em class= product-price {{ price }} /em
div class= product-description {{ description }} /div
/li
/script
MajÈc gotowy szablon, moĝemy wróciÊ do poprzedniego kodu, aby zmieniÊ sposób
wstawiania kodu HTML. Moĝemy pobraÊ odwoïanie do szablonu przy uĝyciu
jQuery i za pomocÈ metody html() pobraÊ treĂÊ wewnÚtrznÈ. Póěniej trzeba
jeszcze tylko przesïaÊ kod HTML i dane do Mustache.
var newProduct = Mustache.to_html( $( #product-template ).html(), product);
Wynik tego powinien byÊ juĝ zadowalajÈcy, chociaĝ gdy nie ma ĝadnego opisu,
pole opisu nie powinno byÊ widoczne. Innymi sïowy, jeĂli nie ma opisu, nie
powinniĂmy renderowaÊ jego elementu div . Na szczÚĂcie, w Mustache moĝna
uĝywaÊ instrukcji warunkowych. Przy ich uĝyciu sprawdzimy, czy opis istnieje,
i jeĂli tak, to wyrenderujemy dla niego element div .
{{#description}}
div class= product-description {{ description }} /div
{{/description}}
Przy uĝyciu tego samego operatora Mustache wykona iteracjÚ po tablicy. System
sprawdzi, czy dana wïasnoĂÊ jest tablicÈ, i jeĂli tak, to automatycznie zastosuje
iteracjÚ.
Rozdziaï 2. • Interfejs uĝytkownika
83
Stosowanie iteracji
Poniewaĝ udaïo nam siÚ wymieniÊ znacznÈ czÚĂÊ istniejÈcego kodu budujÈcego
nowy produkt, postanowiliĂmy, ĝe wiÚkszÈ czÚĂÊ logiki aplikacji napiszemy
w JavaScripcie. Zamienimy stronÚ indeksowÈ, na której wyĂwietlane sÈ produkty
wraz z uwagami, na kod JavaScript, który bÚdzie robiï to samo. Utworzymy tablicÚ
produktów na jednej z wïasnoĂci obiektu danych i kaĝdy produkt w tej tablicy
bÚdzie miaï wïasnoĂÊ notes. WïasnoĂÊ notes bÚdzie tablicÈ, po której iteracja bÚdzie
siÚ odbywaÊ wewnÈtrz szablonu.
Najpierw pobierzemy i wyrenderujemy produkty. Przyjmujemy zaïoĝenie, ĝe ser-
wer zwraca tablicÚ w formacie JSON wyglÈdajÈcÈ tak:
$.getJSON( /products.json , function(products) {
var data = {products: products};
var rendered = Mustache.to_html($( #products-template ).html(), data);
$( body ).append(rendered);
});
Teraz musimy zbudowaÊ szablon do wyrenderowania produktów. W Mustache
iteracjÚ po tablicy wykonuje siÚ, przekazujÈc tÚ tablicÚ operatorowi #, np.
{{#zmienna}}. WewnÈtrz iteracji wïasnoĂci, które wywoïujemy, znajdujÈ siÚ
w kontekĂcie obiektów w tablicy.
mustache/index.html
script type= text/template id= products-template
{{#products}}
li
span class= product-name {{ name }} /span
em class= product-price {{ price }} /em
div class= product-description {{ description }} /div
ul class= product-notes
{{#notes}}
li {{ text }} /li
{{/notes}}
/ul
/li
{{/products}}
/script
Teraz nasza strona indeksowa moĝe byÊ w caïoĂci generowana w przeglÈdarce
przy uĝyciu szablonów i Mustache.
Szablony JavaScript sÈ doskonaïym narzÚdziem pozwalajÈcym dobrze zorgani-
zowaÊ kod aplikacji JavaScript. NauczyïeĂ siÚ renderowaÊ szablony, uĝywaÊ
instrukcji warunkowych oraz stosowaÊ iteracjÚ. Mustache.js jest prostym narzÚ-
dziem pozwalajÈcym pozbyÊ siÚ konkatenacji ïañcuchów i budowaÊ strukturÚ
HTML w czytelny i zgodny z semantykÈ sposób.
84
Web development. Receptury nowej generacji
Kontynuacja
Szablony Mustache pozwalajÈ zachowaÊ przejrzystoĂÊ nie tylko kodu dziaïajÈcego
po stronie klienta, ale równieĝ serwerowego. IstniejÈ implementacje tego systemu
w jÚzykach Ruby, Java, Python, ColdFusion i wielu innych. WiÚcej informacji
na ten temat moĝna znaleěÊ na stronie Mustache8.
Mustache moĝna zatem uĝywaÊ jako systemu szablonów zarówno przy budowie
frontu, jak i zaplecza aplikacji. GdybyĂmy na przykïad mieli szablon Mustache
reprezentujÈcy wiersz tabeli HTML i uĝyli go wewnÈtrz pÚtli budujÈcej tabelÚ
przy wczytywaniu strony, to tego samego szablonu moglibyĂmy teĝ uĝyÊ w celu
dodania wiersza do tej tabeli po udanym wykonaniu ĝÈdania Ajax.
Zobacz równieĝ
Receptura 11.: „Dzielenie treĂci na strony”
Receptura 13.: „Tworzenie interaktywnych interfejsów uĝytkownika
przy uĝyciu biblioteki Knockout.js”
Receptura 14.: „Organizacja kodu przy uĝyciu biblioteki Backbone.js”
Receptura 20.: „Budowanie witryny przy uĝyciu JavaScriptu
i CouchDB”
Receptura 11.
Dzielenie treĂci na strony
Problem
Aby zaoszczÚdziÊ uĝytkownikom nadmiaru treĂci na jednej stronie, a przy okazji
nie przeciÈĝyÊ serwerów, naleĝy ustaliÊ limit iloĂci danych, jaka moĝe zostaÊ
wyĂwietlona na jednej stronie. NajczÚĂciej w tym celu dodaje siÚ mechanizm
dzielenia stron. Jego dziaïanie polega na tym, ĝe wyĂwietlana jest tylko czÚĂÊ
zawartoĂci strony i uĝytkownik moĝe w razie potrzeby przejrzeÊ takĝe pozostaïe
czÚĂci. PoczÈtkowo wyĂwietlana jest tylko maïa czÈstka tego, co moĝna obejrzeÊ.
W toku ewolucji stron internetowych ich twórcy spostrzegli, ĝe uĝytkownicy
wiÚkszoĂÊ czasu spÚdzajÈ na przeglÈdaniu treĂci w sposób liniowy. NajchÚtniej
8 http://mustache.github.com/
Rozdziaï 2. • Interfejs uĝytkownika
85
przeglÈdaliby caïe listy danych, aĝ do znalezienia szukanych informacji albo
osiÈgniÚcia koñca zbioru. Naszym zadaniem jest umoĝliwienie im takiego prze-
glÈdania i jednoczeĂnie unikniÚcie przeciÈĝenia serwera.
Skïadniki
jQuery
Mustache.js9
QEDServer
RozwiÈzanie
Paginacja to dobry sposób na zapanowanie nad zasobami, który dodatkowo uïa-
twia korzystanie ze strony uĝytkownikowi. Zamiast zmuszaÊ uĝytkownika do
wybrania nastÚpnej strony wyników i wczytywaÊ caïy interfejs od nowa, nastÚpnÈ
stronÚ wczytujemy w tle i dodajemy jÈ do bieĝÈcej strony, podczas gdy uĝytkow-
nik zbliĝa siÚ do jej koñca.
Chcemy umieĂciÊ na stronie listÚ naszych produktów, ale jest ich za duĝo, aby
wyĂwietliÊ je wszystkie naraz. Dlatego zastosujemy paginacjÚ z ograniczeniem
polegajÈcym na wyĂwietlaniu maksymalnie 10 produktów naraz. ¿eby jeszcze
bardziej uïatwiÊ ĝycie uĝytkownikom, pozbÚdziemy siÚ przycisku NastÚpna strona
i zamiast tego bÚdziemy automatycznie wczytywaÊ kolejne strony, gdy uznamy,
ĝe naleĝy to zrobiÊ. Od strony uĝytkownika bÚdzie to wyglÈdaïo tak, jakby caïa
lista byïa dostÚpna na stronie od samego poczÈtku.
Do budowy prototypu uĝyjemy QEDServera i jego katalogu produktów. Caïy
kod ěródïowy umieĂcimy w folderze public w przestrzeni roboczej QEDServera.
Uruchom QEDServer i utwórz nowy plik o nazwie products.html w folderze
public utworzonym przez QEDServer. JeĂli nie wiesz, czym jest QEDServer,
zajrzyj do „WstÚpu”, w którym znajduje siÚ objaĂnienie.
Aby utrzymaÊ porzÈdek w kodzie, uĝyjemy biblioteki szablonów Mustache,
o której byïa mowa w recepturze 10., „Tworzenie szablonów HTML przy uĝyciu
systemu Mustache”. Pobierz tÚ bibliotekÚ i umieĂÊ jÈ takĝe w folderze public.
Zaczniemy od utworzenia prostego szkieletu strony index.html w HTML5.
DoïÈczymy do niej biblioteki jQuery, Mustache oraz plik endless_pagination.js,
który bÚdzie zawieraï nasz kod paginacji.
9 http://github.com/documentcloud/underscore/blob/master/underscore.js
86
Web development. Receptury nowej generacji
endlesspagination/products.html
!DOCTYPE html
html
head
meta charset= utf-8
title Produkty AwesomeCo /title
link rel= stylesheet href= endless_pagination.css
script type= text/javascript
src= http://ajax.googleapis.com/ajax/libs/jquery/1.7/jquery.min.js
/script
script type= text/javascript src= mustache.js /script
script src= endless_pagination.js /script
/head
body
div id= wrap
header
h1 Produkty /h1
/header
/div
/body
/html
W strukturze tej strony umieĂciliĂmy kontener na treĂÊ i obraz wirujÈcego kóïka
widoczny na rysunku 2.8. Animacja ta ma na celu zasygnalizowaÊ uĝytkownikowi,
ĝe trwa wczytywanie nastÚpnej strony, które powinno siÚ odbywaÊ w tle.
endlesspagination/products.html
div id= content
/div
img src= spinner.gif id= next_page_spinner
API QEDServer jest tak skonfigurowane, ĝe zwraca stronicowane wyniki i reaguje
na ĝÈdania JSON. Moĝemy siÚ o tym przekonaÊ, otwierajÈc adres http://localhost:
8080/Products.json?page=2.
WiedzÈc, jakie informacje otrzymujemy od serwera, moĝemy rozpoczÈÊ budowÚ
kodu, który bÚdzie aktualizowaï interfejs. W tym celu napiszemy funkcjÚ pobiera-
jÈcÈ tablicÚ w formacie JSON, znakujÈcÈ jÈ przy uĝyciu szablonu Mustache
i wynik tego dziaïania dodajÈcÈ na koñcu strony. Caïy ten kod umieĂcimy w pliku
o nazwie endless_pagination.js. Zaczniemy od napisania funkcji pomocniczych.
Na pierwszy ogieñ pójdzie funkcja renderujÈca odpowiedě w formacie JSON do
HTML.
endlesspagination/endless_pagination.js
function loadData(data) {
$( #content ).append(Mustache.to_html( {{#products}} \
div class= product \
a href= /products/{{id}} {{name}} /a \
br \
span class= description {{description}} /span \
/div {{/products}} , { products: data }));
}
Rozdziaï 2. • Interfejs uĝytkownika
87
Rysunek 2.8. Widok dolnej czÚĂci strony
W procesie iteracji szablon utworzy dla kaĝdego produktu element div , w któ-
rym treĂÊ jest nazwÈ produktu w postaci ïÈcza. NastÚpnie nowe elementy zostanÈ
dodane na koñcu listy produktów i pojawiÈ siÚ na stronie.
NastÚpnie, jako ĝe po dotarciu przez uĝytkownika na koniec strony chcemy
wczytaÊ kolejnÈ stronÚ, musimy znaleěÊ sposób na okreĂlenie, czym jest ta nastÚpna
strona. W tym celu moĝemy przechowywaÊ bieĝÈcÈ stronÚ jako zmiennÈ globalnÈ,
a nastÚpnie gdy bÚdziemy gotowi — utworzyÊ URL dla nastÚpnej strony.
endlesspagination/endless_pagination.js
var currentPage = 0;
function nextPageWithJSON() {
currentPage += 1;
var newURL = http://localhost:8080/products.json?page= + currentPage;
var splitHref = document.URL.split( ? );
var parameters = splitHref[1];
88
Web development. Receptury nowej generacji
if (parameters) {
parameters = parameters.replace(/[? ]page=[^ ]*/, );
newURL += + parameters;
}
return newURL;
}
Funkcja nextPageWithJSON() zwiÚksza wartoĂÊ zmiennej currentPage i dodaje jÈ
do bieĝÈcego adresu jako wartoĂÊ parametru page=. ZapamiÚtujemy teĝ wszystkie
inne parametry znajdujÈce siÚ w bieĝÈcym URL. JednoczeĂnie upewniamy siÚ,
ĝe stary parametr page, jeĂli istnieje, zostanie nadpisany. DziÚki temu otrzymamy
wïaĂciwÈ odpowiedě od serwera.
Funkcje wyĂwietlajÈce nowÈ treĂÊ i okreĂlajÈce adres nastÚpnej strony sÈ juĝ
gotowe. Czas w takim razie napisaÊ funkcjÚ pobierajÈcÈ treĂÊ z serwera. W istocie
funkcja ta bÚdzie po prostu ĝÈdaniem Ajax wysyïanym do serwera. Musimy tylko
zaimplementowaÊ w niej podstawowy mechanizm zapobiegajÈcy wysyïaniu nie-
potrzebnych ĝÈdañ. Utworzymy zmiennÈ globalnÈ o nazwie loadingPage(), którÈ
zainicjujemy wartoĂciÈ 0. Przed wykonaniem ĝÈdania bÚdziemy jÈ zwiÚkszaÊ,
a po jego zakoñczeniu — zmniejszaÊ z powrotem. Jest to coĂ w rodzaju muteksu,
czyli mechanizmu blokujÈcego. Bez tej blokady do serwera wysyïane byïyby
dziesiÈtki ĝÈdañ, a serwer by na nie skwapliwie odpowiadaï, mimo ĝe nie o to nam
chodziïo.
endlesspagination/endless_pagination.js
var loadingPage = 0;
function getNextPage() {
if (loadingPage != 0) return;
loadingPage++;
$.getJSON(nextPageWithJSON(), {}, updateContent).
complete(function() { loadingPage-- });
}
function updateContent(response) {
loadData(response);
}
Po otrzymaniu odpowiedzi na ĝÈdanie Ajax przekazujemy jÈ do funkcji loadData(),
której definicjÚ przedstawiono wczeĂniej. Po dodaniu nowej treĂci przez funkcjÚ
loadData() aktualizujemy adres URL przechowywany w zmiennej nextPage. Jeste-
Ămy gotowi na wykonanie kolejnego ĝÈdania Ajax.
MajÈc funkcjÚ ĝÈdajÈcÈ nastÚpnej strony, teraz musimy zajÈÊ siÚ sprawdzaniem,
czy uĝytkownik w ogóle jest gotowy na wczytanie kolejnej strony. Normalnie
wyĂwietlilibyĂmy po prostu ïÈcze NastÚpna strona, ale nam chodzi o coĂ innego.
Potrzebujemy funkcji, która bÚdzie zwracaÊ true, gdy dolna krawÚdě okna prze-
glÈdarki znajdzie siÚ w okreĂlonej odlegïoĂci od doïu strony.
Rozdziaï 2. • Interfejs uĝytkownika
89
endlesspagination/endless_pagination.js
function readyForNextPage() {
if (!$( #next_page_spinner ).is( :visible )) return;
var threshold = 200;
var bottomPosition = $(window).scrollTop() + $(window).height();
var distanceFromBottom = $(document).height() - bottomPosition;
return distanceFromBottom = threshold;
}
Na koniec dodajemy procedurÚ obsïugi zdarzenia przewijania kóïkiem myszy,
która wywoïuje funkcjÚ observeScroll(). Gdy uĝytkownik przewinie stronÚ za
pomocÈ kóïka myszy, nastÈpi wywoïanie funkcji pomocniczej readyForNextPage().
Gdy funkcja ta zwróci true, wywoïamy funkcjÚ getNextPage(), aby wykonaÊ ĝÈda-
nie Ajax.
endlesspagination/endless_pagination.js
function observeScroll(event) {
if (readyForNextPage()) getNextPage();
}
$(document).scroll(observeScroll);
CzÚĂÊ dotyczÈcÈ „nieskoñczonego wyĂwietlania treĂci” mamy za sobÈ, ale przecieĝ
kiedyĂ ta nasza treĂÊ jednak siÚ skoñczy. Po wyĂwietleniu ostatniego produktu
wirujÈce kóïko powinno zostaÊ ukryte, poniewaĝ jeĂli bÚdzie widoczne, uĝytkow-
nik pomyĂli, ĝe albo coĂ jest nie tak z jego ïÈczem internetowym, albo z naszÈ
stronÈ. Aby usunÈÊ wirujÈce kóïko, dodamy test, który bÚdzie powodowaï jego
ukrycie, gdy serwer zwróci pustÈ listÚ.
endlesspagination/endless_pagination.js
function loadData(data) {
$( #content ).append(Mustache.to_html( {{#products}} \
div class= product \
a href= /products/{{id}} {{name}} /a \
br \
span class= description {{description}} /span \
/div {{/products}} , { products: data }));
if (data.length == 0) $( #next_page_spinner ).hide();
}
To wszystko. Gdy dotrzemy do koñca listy, wirujÈce kóïko zniknie.
Kontynuacja
Opisana tu technika jest doskonaïÈ metodÈ wyĂwietlania dïugich list danych
w sposób zgodny z oczekiwaniami uĝytkowników. DziÚki podziaïowi rozwiÈzania
na funkcje ïatwo je bÚdzie przystosowaÊ do róĝnych projektów. Moĝna zmieniÊ
90
Web development. Receptury nowej generacji
FunkcjonalnoĂÊ przeglÈdarki IE 8
W przeglÈdarce IE 8 ten kod nie dziaïa. PrzeglÈdarka ta wymaga, aby
nagïówki ĝÈdañ JSON byïy w bardzo specyficznym formacie, np. strona
kodowa UTF-8 musi zostaÊ wysïana jako utf8. Bez poprawnych nagïówków
ĝÈdanie Ajax nie powiedzie siÚ i na stronie bÚdzie wyĂwietlone tylko wiru-
jÈce kóïko. Naleĝy o tym pamiÚtaÊ podczas pracy z formatem JSON na ser-
werze i w przeglÈdarce IE.
wartoĂÊ zmiennej treshold, aby treĂÊ byïa wczytywana wczeĂniej lub póěniej, lub
zmodyfikowaÊ funkcjÚ loadData(), aby zwracaïa odpowiedě w formacie HTML
lub XML zamiast JSON. A najlepsze jest to, ĝe moĝemy byÊ spokojni o dostÚp-
noĂÊ naszej strony takĝe wówczas, gdy z jakiegoĂ powodu biblioteka jQuery nie
bÚdzie obsïugiwana. Moĝesz to sprawdziÊ, wyïÈczajÈc JavaScript w swojej prze-
glÈdarce.
W nastÚpnej recepturze pokaĝemy Ci, jak ulepszyÊ ten kod poprzez dodanie
obsïugi zmian adresu URL i przycisku Wstecz.
Zobacz równieĝ
Receptura 12.: „ZapamiÚtywanie stanu w Ajaksie”
Receptura 10.: „Tworzenie szablonów HTML przy uĝyciu systemu
Mustache”
Receptura 12.
ZapamiÚtywanie stanu w Ajaksie
Problem
JednÈ z najwiÚkszych zalet internetu jest moĝliwoĂÊ dzielenia siÚ odnoĂnikami
z innymi luděmi. Jednak wraz z nadejĂciem ery Ajaksa nie wszystko jest takie
proste. KlikajÈc ïÈcze Ajax, nie mamy gwarancji, ĝe spowoduje to zmianÚ adresu
URL w pasku przeglÈdarki. To nie tylko utrudnia wymianÚ odnoĂnikami z innymi
luděmi, ale równieĝ powoduje wadliwe dziaïanie przycisku Wstecz przeglÈdarki.
Strony zawierajÈce takie ïÈcza nie sÈ dobrymi obywatelami internetu, poniewaĝ
gdy siÚ je wyïÈczy, nie da siÚ wróciÊ bezpoĂrednio do poprzedniego stanu.
Rozdziaï 2. • Interfejs uĝytkownika
91
Niestety, skrypt paginacji, który napisaliĂmy w recepturze 11., „Dzielenie treĂci
na strony”, równieĝ nie naleĝy do wzorowych obywateli. Gdy uĝytkownik prze-
wija stronÚ i przechodzi do kolejnych porcji informacji, adres URL caïy czas
pozostaje taki sam. A przecieĝ kaĝde wczytanie oznacza nowy stan, w którym
prezentowane sÈ inne dane niĝ bezpoĂrednio po wczytaniu strony. Gdyby
na przykïad spodobaï siÚ nam produkt ze strony piÈtej i wysïalibyĂmy znajomemu
odnoĂnik do niej, znajomy ten mógïby nie znaleěÊ tego, o czym mu pisaliĂmy,
poniewaĝ zobaczyïby innÈ listÚ niĝ my.
To nie wszystko. Gdy uĝytkownik kliknie przycisk Wstecz przeglÈdarki na stro-
nie zbudowanej w caïoĂci na bazie Ajaksa, to nie przejdzie tam, gdzie by chciaï,
tylko na poprzedniÈ stronÚ, z której trafiï do nas. Zdziwiony kliknie przycisk Dalej
i pogubi siÚ caïkowicie. Na szczÚĂcie, znamy rozwiÈzanie tych problemów.
Skïadniki
jQuery
Mustache.js10
QEDServer
RozwiÈzanie
W tej recepturze dokoñczymy pracÚ rozpoczÚtÈ w recepturze 11., „Dzielenie
treĂci na strony”. Mimo iĝ zastosowana tam metoda ogólnie dziaïa, to jednak ma
tÚ wadÚ, ĝe uniemoĝliwia odwiedzajÈcym dzielenie siÚ linkami do stron. Aby
speïniÊ wymogi dobrego projektowania stron i nie utrudniaÊ ĝycia uĝytkowni-
kom, musimy sprawiÊ, aby nasza lista produktów Ăledziïa swój stan. Innymi
sïowy, gdy zmieni siÚ strona, na którÈ patrzymy, wraz z niÈ powinien zmieniaÊ siÚ
adres URL. W specyfikacji HTML5 wprowadzono funkcjÚ JavaScript o nazwie
pushState(), która w wiÚkszoĂci przeglÈdarek pozwala na zmianÚ adresu URL
bez opuszczania strony. To doskonaïa wiadomoĂÊ dla programistów stron inter-
netowych! DziÚki temu moĝemy napisaÊ stronÚ w caïoĂci opartÈ na Ajaksie,
której przeglÈdanie nigdy nie wymaga wykonywania caïego cyklu ĝÈdañ i przeïa-
dowañ. Oznacza to, ĝe nie musimy juĝ wczytywaÊ takich zasobów, jak nagïówek
i stopka dokumentu, wysyïaÊ w nieskoñczonoĂÊ ĝÈdañ plików graficznych, arkuszy
stylów i skryptów JavaScript za kaĝdym razem, gdy przechodzimy na nowÈ stronÚ
10 http://github.com/documentcloud/underscore/blob/master/underscore.js
92
Web development. Receptury nowej generacji
w obrÚbie witryny. A uĝytkownicy mogÈ bez problemu przesyïaÊ linki znajomym
i nigdy siÚ nie pogubiÈ, w którym miejscu w serwisie aktualnie siÚ znajdujÈ. Naj-
lepsze jest to, ĝe przycisk Wstecz przeglÈdarki równieĝ bÚdzie dziaïaï poprawnie.
Funkcja pushState()
Funkcja pushState() jest jeszcze dopracowywana. WiÚkszoĂÊ starych przeglÈda-
rek jej nie obsïuguje, ale istniejÈ rozwiÈzania awaryjne wykorzystujÈce czÚĂÊ adresu
URL za znakiem #. RozwiÈzania te moĝe nie sÈ eleganckie, ale dziaïajÈ. Poza
tym nie chodzi tylko o piÚkne adresy URL. Internet ma bardzo dobrÈ pamiÚÊ
dïugotrwaïÈ. Stworzono go nie tylko do zabawy i pogaduszek, lecz równieĝ po
to, aby moĝna byïo nawet po latach znaleěÊ stare strony, do których kiedyĂ utwo-
rzyïo siÚ ïÈcze, a które zostaïy przeniesione na nowy serwer (pod warunkiem
ĝe ich twórcy sÈ dobrymi obywatelami internetu i stosujÈ poprawne przekierowa-
nia HTTP 301). JeĂli bÚdziemy uĝywaÊ znaku # w adresach URL jako tym-
czasowego rozwiÈzania dla waĝnych informacji, to moĝe siÚ okazaÊ, ĝe nigdy siÚ
ich nie pozbÚdziemy11. Poniewaĝ znaki # z adresów URL nigdy nie sÈ wysyïane
do serwera, nasza aplikacja musiaïaby dalej przekierowywaÊ ruch po tym, gdy
funkcja pushState() stanie siÚ standardem.
Uzbrojeni w tÚ nowÈ wiedzÚ, zobaczmy, co trzeba zrobiÊ, aby nasza niekoñczÈca
siÚ strona z produktami uzyskaïa ĂwiadomoĂÊ swojego stanu.
Parametry, które trzeba ĂledziÊ
Poniewaĝ nie wiemy, na którÈ stronÚ uĝytkownik wejdzie za pierwszym razem,
bÚdziemy ĂledziÊ zarówno stronÚ startowÈ, jak i bieĝÈcÈ. JeĂli uĝytkownik wejdzie
od razu na trzeciÈ stronÚ, to chcemy, aby przy kolejnych wizytach mógï na niÈ
wróciÊ. JeĂli odwiedzajÈcy skorzysta z kóïka myszy na stronie trzeciej i wczyta
kilka kolejnych stron, np. do strony siódmej, to równieĝ chcemy o tym wiedzieÊ.
Potrzebujemy sposobu na zapamiÚtanie strony startowej i koñcowej, aby w przy-
padku odĂwieĝenia uĝytkownik nie musiaï przewijaÊ wszystkiego od poczÈtku.
Musimy teĝ znaleěÊ sposób na wysyïanie startowej i koñcowej strony z klienta.
Najbardziej oczywistym rozwiÈzaniem w tym przypadku wydaje siÚ dodanie tych
parametrów do adresu URL w ĝÈdaniu GET. Przy pierwszym wczytaniu strony
ustawimy parametr page adresu na bieĝÈcÈ stronÚ i przyjmiemy zaïoĝenie, ĝe
uĝytkownik chce obejrzeÊ tylko tÚ stronÚ. Gdy klient przekaĝe dodatkowo para-
metr start_page, bÚdziemy wiedzieÊ, ĝe uĝytkownik chce obejrzeÊ kilka stron, od
11 http://danwebb.net/2011/5/28/it-is-about-the-hashbangs
Rozdziaï 2. • Interfejs uĝytkownika
93
start_page do page. WracajÈc do poprzedniego przykïadu, gdybyĂmy byli na
stronie siódmej, ale zaczÚli przeglÈdanie od strony trzeciej, to nasz adres URL
wyglÈdaïby nastÚpujÈco http://localhost:8080/products?start_page=3 page=7.
Te parametry powinny nam wystarczyÊ do odtworzenia listy produktów i po-
kazania uĝytkownikowi takiej samej strony za kaĝdym razem.
statefulpagination/stateful_pagination.js
function getParameterByName(name) {
var match = RegExp( [? ] + name + =([^ ]*) )
.exec(window.location.search);
return match decodeURIComponent(match[1].replace(/\+/g, ));
}
var currentPage = 0;
var startPage = 0;
$(function() {
startPage = parseInt(getParameterByName( start_page ));
if (isNaN(startPage)) {
startPage = parseInt(getParameterByName( page ));
}
if (isNaN(startPage)) {
startPage = 1;
}
currentPage = startPage - 1;
if (getParameterByName( page )) {
endPage = parseInt(getParameterByName( page ));
for (i = currentPage; i endPage; i++) {
getNextPage(true);
}
}
observeScroll();
});
Skrypt ten sprawdza parametry start_page i page, a nastÚpnie wysyïa ĝÈdanie
odpowiednich stron do serwera. UĝyliĂmy funkcji bardzo podobnej do getNext
´Page() z poprzedniej receptury, tylko z obsïugÈ wielu ĝÈdañ naraz. W odróĝ-
nieniu od sytuacji, gdy uĝytkownik korzysta z kóïka myszy i chcemy zapobiec
nakïadaniu siÚ ĝÈdañ, w tym przypadku nie przeszkadza nam to, poniewaĝ wiemy
dokïadnie, których stron ma dotyczyÊ ĝÈdanie.
Podobnie jak ĂledziliĂmy wczeĂniej wartoĂÊ zmiennej currentPage, teraz bÚdziemy
ĂledziÊ startPage. Parametr ten bÚdziemy pobieraÊ z adresu URL, dziÚki czemu
bÚdziemy mogli wykonywaÊ ĝÈdania stron, które nie byïy jeszcze wczytywane.
Liczba ta nie bÚdzie siÚ zmieniaÊ, ale musi byÊ dodawana do adresu URL i byÊ
w nim przy kaĝdym ĝÈdaniu nowej strony.
94
Web development. Receptury nowej generacji
Aktualizowanie adresu URL w przeglÈdarce
Do aktualizacji adresu URL w przeglÈdarce napiszemy funkcjÚ o nazwie update
´BrowserUrl(), wywoïujÈcÈ funkcjÚ pushState() oraz ustawiajÈcÈ parametry start_
´page i page. Naleĝy przy okazji pamiÚtaÊ, ĝe nie wszystkie przeglÈdarki obsïu-
gujÈ funkcjÚ pushState(), i przed jej wywoïaniem sprawdzaÊ, czy jest zdefinio-
wana. W tych przeglÈdarkach nasze rozwiÈzanie po prostu nie bÚdzie dziaïaÊ, ale
powinniĂmy przygotowywaÊ nasze aplikacje z myĂlÈ o przyszïoĂci.
statefulpagination/stateful_pagination.js
function updateBrowserUrl() {
if (window.history.pushState == undefined) return;
var newURL = ?start_page= + startPage + page= + currentPage;
window.history.pushState({}, , newURL);
}
Funkcja pushState() przyjmuje trzy parametry. Pierwszy jest obiekt stanu, który
jest w istocie obiektem w formacie JSON. Argument ten moglibyĂmy wykorzystaÊ
do przechowywania informacji dotyczÈcych stanu, poniewaĝ w wyniku przewija-
nia od serwera otrzymujemy dane wïaĂnie w formacie JSON. Poniewaĝ jednak
nasze dane sÈ proste i ïatwe do pobrania z serwera, wydaje siÚ, ĝe nie jest to warte
zachodu. Drugi argument to tytuï nowego stanu. Nie jest on na razie szeroko
obsïugiwany przez przeglÈdarki, ale w naszym przypadku to nie problem, bo
i tak byĂmy tego nie potrzebowali. W zwiÈzku z tym przekazujemy w nim pusty
ïañcuch.
W koñcu dochodzimy do najwaĝniejszego elementu funkcji pushState(). Trzeci
parametr okreĂla, co ma siÚ zmieniÊ w adresie. Moĝe to byÊ zarówno bezwzglÚdna
Ăcieĝka, jak i zestaw parametrów, które majÈ zostaÊ zmienione na koñcu adresu.
Ze wzglÚdów bezpieczeñstwa nie moĝna zmieniaÊ domeny, ale wszystko, co
znajduje siÚ za niÈ — tak. Poniewaĝ nas interesuje tylko zmienianie parametrów
adresu URL, na poczÈtku trzeciego parametru funkcji pushState() wpisaliĂmy
znak ?. NastÚpnie wpisaliĂmy ustawienia parametrów start_page i page. JeĂli
parametry te bÚdÈ znajdowaÊ siÚ w adresie, funkcja sama je zaktualizuje.
statefulpagination/stateful_pagination.js
function updateContent(response) {
loadData(response);
updateBrowserUrl();
}
Na koniec, aby nasz mechanizm paginacji zaczÈï rozpoznawaÊ swój stan, doda-
liĂmy wywoïanie funkcji updateBrowserUrl() do funkcji updateContent(). Od tej pory
uĝytkownicy mogÈ bez przeszkód uĝywaÊ przycisku Wstecz, aby wyjĂÊ ze strony,
i przycisku Dalej, aby na niÈ wróciÊ do tego samego miejsca. Takĝe odĂwieĝenie
Rozdziaï 2. • Interfejs uĝytkownika
95
strony niczego nie zepsuje. Co jednak najwaĝniejsze, teraz odwiedzajÈcy mogÈ
wysyïaÊ znajomym linki do naszych stron. DziÚki ciÚĝkiej pracy programistów
przeglÈdarek internetowych udaïo nam siÚ sprawiÊ, aby nasza strona indeksowa
byïa dobrym obywatelem internetu.
Kontynuacja
DodajÈc kolejne skrypty JavaScript i Ajax do swoich stron, powinniĂmy mieÊ
ĂwiadomoĂÊ dziaïania uĝywanych interfejsów. Metoda pushState() HTML5
i API History pozwalajÈ nam przywróciÊ normalne dziaïanie kontrolek, do któ-
rych uĝytkownicy sÈ przyzwyczajeni. Warstwy abstrakcji, takie jak History.js12,
jeszcze to uïatwiajÈ, poniewaĝ dostarczajÈ eleganckich rozwiÈzañ awaryjnych
dla starych przeglÈdarek.
Opisane przez nas rozwiÈzania zaczynajÈ teĝ byÊ implementowane w bibliotekach
JavaScript, jak np. Backbone.js, co oznacza, ĝe moĝemy spodziewaÊ siÚ jeszcze
lepszej obsïugi przycisku Wstecz nawet na najbardziej skomplikowanych jedno-
stronicowych aplikacjach.
Zobacz równieĝ
Receptura 10.: „Tworzenie szablonów HTML przy uĝyciu systemu
Mustache”
Receptura 14.: „Organizacja kodu przy uĝyciu biblioteki Backbone.js”
Receptura 13.
Tworzenie interaktywnych
interfejsów uĝytkownika
przy uĝyciu biblioteki Knockout.js
Problem
ProgramujÈc nowoczesne aplikacje sieciowe, staramy siÚ, aby w reakcji na dzia-
ïania uĝytkownika aktualizowana byïa jak najmniejsza czÚĂÊ interfejsu. Odwoïania
do serwera sÈ zawsze czasochïonne, a odĂwieĝanie caïej strony moĝe spowodowaÊ
zniechÚcenie uĝytkownika.
12 http://plugins.jquery.com/plugin-tags/pushstate
96
Web development. Receptury nowej generacji
Niestety, kod JavaScript uĝywany do implementacji tych mechanizmów czÚsto
szybko wymyka siÚ spod kontroli. Na poczÈtku obserwowanych jest tylko kilka
zdarzeñ, ale z czasem dodajemy kolejne funkcje zwrotne do aktualizowania róĝ-
nych obszarów strony i utrzymanie tego wszystkiego w ryzach staje siÚ bardzo
kïopotliwe.
Knockout to prosta, a zarazem bardzo funkcjonalna biblioteka, pozwalajÈca wiÈ-
zaÊ obiekty z interfejsem i automatycznie aktualizowaÊ jednÈ czÚĂÊ interfejsu,
podczas gdy zmieniana jest inna czÚĂÊ, bez potrzeby uĝywania wielu zagnieĝdĝo-
nych procedur obsïugi zdarzeñ.
Skïadniki
Knockout.js13
RozwiÈzanie
Knockout.js uĝywa modeli widoków, które zawierajÈ logikÚ widoku zwiÈzanÈ
ze zmianami interfejsu. WïasnoĂci tych modeli moĝna wiÈzaÊ z elementami
interfejsu.
Chcemy, aby uĝytkownicy naszej strony mogli zmieniaÊ liczbÚ elementów
w koszyku i od razu otrzymaÊ zaktualizowanÈ naleĝnÈ za nie sumÚ. Do budowy
ekranu aktualizacji naszego koszyka moĝemy wykorzystaÊ modele widoków Knock-
out. W koszyku kaĝdy produkt bÚdzie przedstawiony w postaci wiersza tabeli.
W kaĝdym wierszu bÚdzie siÚ znajdowaÊ pole na liczbÚ egzemplarzy danego
produktu oraz przycisk pozwalajÈcy usunÈÊ ten produkt z koszyka. Gdy zmieni siÚ
liczba egzemplarzy któregoĂ z produktów, aplikacja natychmiast obliczy nowÈ
sumÚ czÈstkowÈ oraz sumÚ za wszystkie towary. Gdy skoñczymy pracÚ, finalny
efekt bÚdzie wyglÈdaï jak na rysunku 2.9.
Rysunek 2.9.
Interfejs koszyka
13 http://knockoutjs.com
Rozdziaï 2. • Interfejs uĝytkownika
97
Podstawy Knockout
Modele widoków Knockout to zwykïe obiekty JavaScript z wïasnoĂciami i meto-
dami. Oto prosty obiekt Person z metodami dla imienia, nazwiska oraz imienia
i nazwiska.
knockout/binding.html
var Person = function(){
this.firstname = ko.observable( Jan );
this.lastname = ko.observable( Kowalski );
this.fullname = ko.dependentObservable(function(){
return(
this.firstname() + + this.lastname()
);
}, this);
};
ko.applyBindings( new Person );
Metody i logikÚ tego obiektu z elementami interfejsu wiÈĝemy przy uĝyciu atry-
butu data- jÚzyka HTML5.
knockout/binding.html
p ImiÚ: input type= text data-bind= value: firstname /p
p Nazwisko: input type= text data-bind= value: lastname /p
p ImiÚ i nazwisko:
span aria-live= polite data-bind= text: fullname /span
/p
Gdy zmienimy imiÚ albo nazwisko w jednym z pól, pod spodem zostanie wyĂwie-
tlone zaktualizowane imiÚ i nazwisko. Poniewaĝ aktualizacja odbywa siÚ dyna-
micznie, moĝe sprawiaÊ problemy osobom niewidomym, które korzystajÈ z czyt-
ników ekranu. RozwiÈzaniem tego problemu jest uĝycie atrybutu aria-live, infor-
mujÈcego czytniki, ĝe ta czÚĂÊ strony dynamicznie siÚ zmienia.
To byï bardzo prosty przykïad, wiÚc teraz pokopiemy trochÚ gïÚbiej i utworzymy
jeden wiersz danych naszego koszyka, w którym po zmianie liczby produktów
bÚdzie odpowiednio zmieniaïa siÚ suma naleĝna. Póěniej na tej bazie zbudujemy
caïy koszyk. Zaczniemy od utworzenia modelu danych.
Pojedynczy wiersz bÚdzie reprezentowaÊ obiekt JavaScript o nazwie LineItem
majÈcy wïasnoĂci name i price. Utwórz stronÚ HTML i doïÈcz do niej bibliotekÚ
Knockout.js w sekcji nagïówkowej:
knockout/item.html
!DOCTYPE html
html
head
title Aktualizacja liczby produktów /title
script type= text/javascript src= knockout-1.3.0.js /script
/head
98
body
/body
Web development. Receptury nowej generacji
/html
Na dole strony, nad znacznikiem /body , wstaw element script i wpisz w nim
nastÚpujÈcy kod:
knockout/item.html
var LineItem = function(product_name, product_price){
this.name = product_name;
this.price = product_price;
};
W JavaScripcie funkcje sÈ konstruktorami obiektów, a wiÚc moĝna ich uĝywaÊ
do naĂladowania klas. W tym przypadku konstruktor egzemplarza LineItem przyj-
muje nazwÚ i cenÚ.
Teraz musimy poinformowaÊ Knockout, ĝe chcemy uĝyÊ tej klasy LineItem jako
naszego modelu widoku, aby jej wïasnoĂci byïy widoczne dla kodu HTML.
W tym celu do skryptu dodajemy poniĝszy kod.
knockout/item.html
var item = new LineItem( Macbook Pro 15 , 1699.00);
ko.applyBindings(item);
Tworzymy nowy egzemplarz obiektu LineItem, ustawiajÈc w nim nazwÚ i cenÚ
produktu, i wywoïujemy na nim metodÚ Knockout applyBindings(). Póěniej
to zmienimy, ale na razie wystarczy nam zakodowanie danych na staïe.
MajÈc obiekt, moĝemy zbudowaÊ interfejs i pobraÊ z tego obiektu dane. Koszyk
zbudujemy na bazie tabeli HTML z elementami strukturalnymi thead i tbody .
knockout/item.html
div role= application
table
thead
tr
th Produkt /th
th Cena /th
th Liczba /th
th Suma /th
/tr
/thead
tbody
tr aria-live= polite
td data-bind= text: name /td
td data-bind= text: price /td
/tr
/tbody
/table
/div
Rozdziaï 2. • Interfejs uĝytkownika
99
Poniewaĝ wiersze tabeli sÈ aktualizowane danymi wprowadzanymi przez uĝytkow-
nika, wierszom tym nadaliĂmy atrybut aria-live, aby czytniki ekranu wiedziaïy,
ĝe naleĝy siÚ w nich spodziewaÊ zmian. Caïy koszyk dodatkowo umieĂciliĂmy
w elemencie div z atrybutem HTML5-ARIA application, który informuje
czytniki, ĝe jest to aplikacja interaktywna. WiÚcej informacji na temat tych atry-
butów moĝna przeczytaÊ w specyfikacji HTML514.
SzczególnÈ uwagÚ naleĝy zwróciÊ na poniĝsze dwa wiersze kodu:
knockout/item.html
td data-bind= text: name /td
td data-bind= text: price /td
Teraz nasz egzemplarz LineInstance jest widoczny globalnie na caïej stronie, a wiÚc
tak samo widoczne sÈ jego wïasnoĂci name i price. Te dwa wiersze kodu ozna-
czajÈ, ĝe tekst (text) tych elementów chcemy pobieraÊ z wïasnoĂci o okreĂlonych
nazwach.
Gdy teraz wczytamy naszÈ stronÚ w przeglÈdarce, to zauwaĝymy, ĝe zaczyna
nabieraÊ ksztaïtu oraz ĝe pola nazwy i ceny produktu sÈ wypeïnione!
Teraz dodamy pole, w którym uĝytkownik bÚdzie mógï zmieniÊ liczbÚ produktów
w zamówieniu.
knockout/item.html
td input type= text name= quantity
data-bind= value: quantity, valueUpdate: keyup
/td
W bibliotece Knockout do odwoïywania siÚ do pól danych w postaci elementów
HTML sïuĝy parametr text, ale elementy formularzy HTML takie jak input
majÈ atrybut value. Dlatego tym razem zwiÈzaliĂmy atrybut value z wïasnoĂciÈ
quantity.
WïasnoĂÊ quantity sïuĝy nie tylko do wyĂwietlania danych, lecz równieĝ do ich
ustawiania. A gdy ustawimy dane, musimy teĝ uruchomiÊ zdarzenia. Do tego celu
uĝywamy funkcji Knockout ko.observable() jako wartoĂci wïasnoĂci quantity
naszej klasy.
this.quantity = ko.observable(1);
Funkcji ko.observable() przekazaliĂmy domyĂlnÈ wartoĂÊ, aby po wyĂwietleniu
strony po raz pierwszy pole tekstowe coĂ juĝ zawieraïo.
Teraz moĝemy juĝ wpisaÊ liczbÚ, ale chcielibyĂmy jeszcze dodatkowo wyĂwietliÊ
sumÚ czÈstkowÈ dla kaĝdego wiersza. Dodamy do tabeli kolumnÚ na tÚ kwotÚ:
14 http://www.w3.org/TR/html5-author/wai-aria.html
100
Web development. Receptury nowej generacji
knockout/item.html
td data-bind= text: subtotal /td
Podobnie jak w przypadku kolumn nazwy i ceny, tekst komórki ustawiamy na
wartoĂÊ wïasnoĂci subtotal modelu widoku.
To doprowadziïo nas do jednej z najwaĝniejszych czÚĂci biblioteki Knockout.js,
metody dependentObservable(). WïasnoĂÊ quantity zdefiniowaliĂmy jako obser-
wowalnÈ, co oznacza, ĝe gdy pole zmieni wartoĂÊ, zmiana ta bÚdzie zauwaĝona
przez inne elementy. Deklarujemy metodÚ dependentObservable(), która bÚdzie
wykonywaÊ kod w reakcji na zmianÚ wartoĂci obserwowanego pola, oraz przypi-
sujemy tÚ metodÚ do wïasnoĂci naszego obiektu, aby moĝna jÈ byïo zwiÈzaÊ
z naszym interfejsem uĝytkownika.
this.subtotal = ko.dependentObservable(function() {
return(
this.price * parseInt( 0 +this.quantity(), 10)
); // label id= code.subtotal
}, this);
Ale skÈd metoda dependentObservable() wie, które pola obserwowaÊ? PrzeglÈda
obserwowalne wïasnoĂci, które wymieniamy w definiowanej funkcji. Poniewaĝ
mnoĝymy cenÚ i liczbÚ, Knockout Ăledzi obie te wïasnoĂci i wykonuje kod, gdy któ-
rakolwiek z nich siÚ zmieni.
Metoda dependentObservable() przyjmuje takĝe drugi parametr, który okreĂla
kontekst dla wïasnoĂci. Ma to zwiÈzek ze sposobem dziaïania funkcji i obiektów
w JavaScripcie. WiÚcej na ten temat moĝna przeczytaÊ w dokumentacji biblioteki
Knockout.js.
To wszystko, jeĂli chodzi o pojedynczy wiersz tabeli. Gdy zmienimy liczbÚ pro-
duktów w zamówieniu, cena zostanie natychmiast zaktualizowana. Teraz roz-
budujemy uzyskanÈ strukturÚ, aby utworzyÊ koszyk na wiele produktów, wyĂwie-
tlajÈcy dodatkowo sumy czÈstkowe i ogólnÈ sumÚ naleĝnoĂci.
WiÈzania przepïywu sterowania
WiÈzanie obiektów z elementami HTML jest bardzo wygodne, ale w koszyku
rzadko kiedy jest tylko jeden produkt, a duplikowanie caïego tego kodu byïoby
bardzo ĝmudne, nie mówiÈc juĝ o dodatkowych komplikacjach zwiÈzanych z wiÚk-
szÈ liczbÈ obiektów LineItem. Musimy coĂ zmieniÊ.
Zamiast obiektu LineItem w roli modelu widoku, do reprezentowania koszyka
uĝyjemy innego obiektu. Nadamy mu nazwÚ Cart i bÚdziemy w nim przechowy-
waÊ wszystkie obiekty LineItem. WiedzÈc, jak dziaïa metoda dependentObservables(),
Rozdziaï 2. • Interfejs uĝytkownika
101
moĝemy w obiekcie Cart utworzyÊ wïasnoĂÊ obliczajÈcÈ sumÚ naleĝnoĂci, gdy
zmieni siÚ którykolwiek z elementów koszyka.
A co z kodem HTML dla pojedynczego produktu? Duplikowania kodu moĝemy
uniknÈÊ dziÚki uĝyciu tzw. wiÈzania przepïywu sterowania (ang. control
flow binding) i nakazujÈc Knockout wyrenderowanie kodu HTML dla kaĝdego
produktu w koszyku.
Najpierw zdefiniujemy tablicÚ elementów, których uĝyjemy do napeïnienia
koszyka.
knockout/update_cart.html
var products = [
{name: Macbook Pro 15 inch , price: 1699.00},
{name: PrzejĂciówka Mini Display Port na VGA , price: 29.00},
{name: Magic Trackpad , price: 69.00},
{name: Klawiatura bezprzewodowa Apple , price: 69.00}
];
W realnej aplikacji dane te pobieralibyĂmy z usïugi sieciowej lub wywoïania Ajax
albo generowalibyĂmy je na serwerze podczas serwowania strony.
Teraz utworzymy obiekt Cart do przechowywania produktów. Zdefiniujemy go
w taki sam sposób, jak obiekt LineItem.
knockout/update_cart.html
var Cart = function(items){
this.items = ko.observableArray();
for(var i in items){
var item = new LineItem(items[i].name, items[i].price);
this.items.push(item);
}
}
Musimy zmieniÊ wiÈzanie z LineItem na klasÚ Cart.
knockout/update_cart.html
var cartViewModel = new Cart(products);
ko.applyBindings(cartViewModel);
Produkty sÈ zapisywane w koszyku za pomocÈ metody observableArray(), która
dziaïa tak samo jak observable(), ale ma wïaĂciwoĂci tablicy. Gdy utworzyliĂmy
nowy egzemplarz naszego koszyka, przekazaliĂmy do niego tablicÚ danych. Nasz
obiekt iteruje po elementach danych i tworzy nowe egzemplarze LineItem, które
sÈ zapisywane w tablicy produktów. Poniewaĝ tablica ta jest obserwowalna,
interfejs zmieni siÚ po kaĝdej zmianie zawartoĂci tej tablicy. OczywiĂcie, teraz
mamy wiÚcej niĝ jeden produkt, a wiÚc musimy zmodyfikowaÊ nasz interfejs.
102
Web development. Receptury nowej generacji
JaĂ pyta:
Jak wyglÈda sprawa dostÚpnoĂci
w przypadku biblioteki Knockout?
Interfejsy w duĝym stopniu oparte na JavaScripcie czÚsto bardzo sïabo
wypadajÈ pod wzglÚdem dostÚpnoĂci, jednak uĝycie tego jÚzyka samo
w sobie o niczym jeszcze nie Ăwiadczy.
W tej recepturze uĝyliĂmy ról i atrybutów HTML5 ARIA, aby pomóc czyt-
nikom ekranu w zrozumieniu dziaïania naszej aplikacji. Jednak kwestie
dostÚpnoĂci dotyczÈ nie tylko czytników. W dostÚpnoĂci chodzi ogólnie
o umoĝliwienie dostÚpu do treĂci jak najszerszemu gronu odbiorców.
Knockout to rozwiÈzanie napisane w JavaScripcie, a wiÚc dziaïa tylko wów-
czas, gdy obsïuga tego jÚzyka jest wïÈczona. Trzeba to braÊ pod uwagÚ.
Najlepiej jest najpierw napisaÊ aplikacjÚ, która jest uĝyteczna bez Java-
Scriptu, a nastÚpnie za pomocÈ biblioteki Knockout dodaÊ róĝne ulepszenia.
W naszym przykïadzie zawartoĂÊ koszyka jest renderowana za pomocÈ
biblioteki Knockout, ale gdybyĂmy uĝyli którejĂ z technologii serwerowych,
moglibyĂmy renderowaÊ kod koszyka i stosowaÊ wiÈzania Knockout do
wyrenderowanego kodu HTML. DostÚpnoĂÊ aplikacji zaleĝy przede wszyst-
kim od sposobu jej zaimplementowania, a nie od konkretnej uĝytej do jej
budowy biblioteki.
NastÚpnie zmodyfikujemy naszÈ stronÚ HTML i nakaĝemy Knockout utworzyÊ
wiersz tabeli dla kaĝdego produktu za pomocÈ wywoïania data-bind na elemencie
tbody .
knockout/update_cart.html
h tbody data-bind= foreach: items
tr aria=live= polite
td data-bind= text: name /td
td data-bind= text: price /td
td input type= text name= quantity data-bind= value: quantity /td
td data-bind= text: subtotal /td
/tr
/tbody
NakazaliĂmy bibliotece Knockout wyrenderowaÊ zawartoĂÊ elementu tbody dla
kaĝdego elementu tablicy items. Nie musimy nic wiÚcej zmieniaÊ.
Teraz na naszej stronie moĝe byÊ wyĂwietlanych wiele wierszy tabeli i dla kaĝ-
dego z nich bÚdzie wyĂwietlana suma czÈstkowa. Zajmiemy siÚ obliczaniem caï-
kowitej kwoty do zapïaty i usuwaniem elementów.
Rozdziaï 2. • Interfejs uĝytkownika
103
Kwota do zapïaty
Sposób dziaïania metody Knockout dependentObservable() poznaliĂmy juĝ przy
okazji obliczania sumy czÈstkowej dla kaĝdego produktu. DodajÈc jÈ do obiektu
Cart, moĝemy jej uĝyÊ takĝe do obliczania sumy caïkowitej.
this.total = ko.dependentObservable(function(){
var total = 0;
for (item in this.items()){
total += this.items()[item].subtotal();
}
return total;
}, this);
Kod ten zostanie uruchomiony za kaĝdym razem, gdy zmieni siÚ którykolwiek
z produktów. Aby wyĂwietliÊ sumÚ caïkowitÈ, musimy oczywiĂcie jeszcze dodaÊ
jeden wiersz do tabeli. Poniewaĝ ma to byÊ suma caïkowita naleĝnoĂci za wszyst-
kie produkty, wiersz ten umieĂcimy poza elementem tbody , w elemencie tfoot
umieszczonym bezpoĂrednio pod zamykajÈcym znacznikiem /thead . Umiesz-
czenie stopki nad treĂciÈ tabeli pomaga niektórym przeglÈdarkom i technologiom
pomocniczym w szybszym rozpracowaniu struktury tabeli.
knockout/update_cart.html
tfoot
tr
td colspan= 4 NaleĝnoĂÊ /td
td aria-live= polite data-bind= text: total() /td
/tr
/tfoot
Gdy odĂwieĝymy stronÚ i zmienimy liczbÚ przy którymĂ z produktów, nastÈpi
automatyczna aktualizacja sumy czÈstkowej i caïkowitej. Teraz przejdziemy do
przycisku usuwania produktów.
Usuwanie produktów
Na zakoñczenie musimy jeszcze dodaÊ przycisk Usuñ obok kaĝdego produktu,
sïuĝÈcy do jego usuniÚcia z koszyka. DziÚki caïej wykonanej do tej pory pracy
zadanie to jest juĝ bardzo ïatwe. Najpierw dodamy przycisk do tabeli.
td
button
data-bind= click: function() { cartViewModel.remove(this) } Usuñ
/button
/td
Tym razem zamiast wiÈzaÊ dane z interfejsem, wiÈĝemy zdarzenie i funkcjÚ.
Przekazujemy this do metody remove() wywoïywanej na rzecz egzemplarza
cartViewModel. Przycisk ten jednak nie dziaïa, poniewaĝ jeszcze nie zdefiniowa-
liĂmy metody remove(). Jej definicja znajduje siÚ poniĝej:
104
Web development. Receptury nowej generacji
¿yj w zgodzie z serwerem!
Coraz wiÚkszÈ popularnoĂÊ zdobywajÈ koszyki na zakupy, których aktu-
alizacja odbywa siÚ w caïoĂci wyïÈcznie po stronie klienta. Czasami po pro-
stu niemoĝliwe jest wysyïanie ĝÈdañ Ajax za kaĝdym razem, gdy uĝytkow-
nik zmieni coĂ w interfejsie.
StosujÈc to podejĂcie, musisz zadbaÊ o synchronizacjÚ danych w koszyku
na kliencie z danymi na serwerze. Przecieĝ nie chciaïbyĂ, aby ktoĂ zmieniaï
ceny produktów za Ciebie!
Gdy uĝytkownik przechodzi do kasy, naleĝy przesïaÊ zaktualizowane war-
toĂci na serwer i tam obliczyÊ sumy przed sfinalizowaniem transakcji.
knockout/update_cart.html
this.remove = function(item){ this.items.remove(item); }
To wszystko! Poniewaĝ tablica items jest obserwowalna (observableArray), aktuali-
zowany jest caïy interfejs, wraz sumÈ caïkowitÈ!
Kontynuacja
Biblioteka Knockout jest doskonaïym narzÚdziem do tworzenia dynamicznych
jednostronicowych interfejsów, a dziÚki temu, ĝe nie jest zwiÈzana z ĝadnym
frameworkiem sieciowym, moĝemy jej uĝywaÊ, gdzie tylko chcemy.
Co waĝniejsze, modele widoków uĝywane w tej bibliotece sÈ zwykïym kodem
JavaScript, dziÚki czemu moĝna jej uĝywaÊ do implementowania wielu czÚsto
potrzebnych funkcji interfejsu uĝytkownika. Na przykïad przy uĝyciu Ajaksa
z ïatwoĂciÈ moĝna by byïo utworzyÊ funkcjÚ wyszukiwania bieĝÈcego, zbudowaÊ
kontrolki do edycji danych przesyïajÈce dane na serwer w celu ich zachowania,
a nawet aktualizowaÊ zawartoĂÊ jednego menu rozwijanego na podstawie wartoĂci
wybranej w innym.
Zobacz równieĝ
Receptura 14.: „Organizacja kodu przy uĝyciu biblioteki Backbone.js”
Rozdziaï 2. • Interfejs uĝytkownika
105
Receptura 14.
Organizacja kodu
przy uĝyciu biblioteki Backbone.js
Problem
W odpowiedzi na rosnÈce wymagania uĝytkowników w kwestii niezawodnoĂci
i interaktywnoĂci aplikacji dziaïajÈcych po stronie klienta, programiĂci ciÈgle
tworzÈ nowe niesamowite biblioteki JavaScript. Jednak im bardziej skompliko-
wana jest dana aplikacja, tym bardziej jej kod wyglÈda jak pobojowisko peïne
porozrzucanych bez ïadu i skïadu rozmaitych bibliotek, wiÈzañ zdarzeñ, wywo-
ïañ Ajax jQuery i funkcji przetwarzajÈcych dane w formacie JSON.
Potrzebna jest nam metoda tworzenia aplikacji dziaïajÈcych po stronie klienta
w taki sam sposób, jak od lat tworzymy aplikacje serwerowe. Krótko mówiÈc,
potrzebujemy jakiegoĂ frameworku. MajÈc solidny framework JavaScript, utrzy-
mamy porzÈdek w programie, zredukujemy powtarzalnoĂÊ kodu oraz zastosujemy
standard zrozumiaïy takĝe dla innych programistów.
Poniewaĝ Backbone to skomplikowana biblioteka, ta receptura jest
znacznie dïuĝsza i bardziej skomplikowana od innych.
Skïadniki
Backbone.js15
Underscore.js16
JSON2.js17
Mustache18
jQuery
QEDServer
15 http://documentcloud.github.com/backbone
16 http://documentcloud.github.com/underscore/
17 https://github.com/douglascrockford/JSON-js
18 http://mustache.github.com/
106
Web development. Receptury nowej generacji
RozwiÈzanie
To zadanie moĝemy wykonaÊ przy uĝyciu wielu róĝnych frameworków, ale
Backbone.js jest jednym z najpopularniejszych, dziÚki swojej elastycznoĂci,
niezawodnoĂci i ogólnie wysokiej jakoĂci kodu. W chwili pisania tych sïów byï
jeszcze wzglÚdnie nowy, a miaï juĝ wielu uĝytkowników. Przy uĝyciu Backbone
moĝemy wiÈzaÊ zdarzenia w podobny sposób, jak to robiliĂmy przy uĝyciu
Knockout w recepturze 13., „Tworzenie interaktywnych interfejsów uĝytkownika
przy uĝyciu biblioteki Knockout.js”, ale teraz otrzymujemy modele wspóïpracujÈce
z serwerem oraz system trasowania ĝÈdañ, za pomocÈ którego moĝna ĂledziÊ zmiany
w adresach URL. DziÚki Backbone otrzymujemy bardziej niezawodny zrÈb apli-
kacji, który moĝe doskonale sprawdziÊ siÚ w przypadku skomplikowanych aplikacji
serwerowych, ale stanowiÊ przerost formy nad treĂciÈ w przypadku prostszych
programów.
Uĝyjemy Backbone do poprawienia wraĝliwoĂci interfejsu naszego sklepu inter-
netowego, tzn. sprawimy, ĝe bÚdzie ĝywiej reagowaï na dziaïania uĝytkownika.
Z naszych logów i badañ zachowañ uĝytkowników wynika, ĝe odĂwieĝanie strony
trwa zbyt dïugo, a wiele rzeczy, które wykonuje siÚ za poĂrednictwem serwera,
moĝna by byïo zrobiÊ na kliencie. Nasz kierownik zasugerowaï, abyĂmy caïy
interfejs zarzÈdzania produktami zmieĂcili na pojedynczej stronie, na której bÚdzie
moĝna dodawaÊ i usuwaÊ produkty bez odĂwieĝania strony.
Zanim rozpoczniemy budowÚ naszego interfejsu, bliĝej poznamy Backbone
i dowiemy siÚ, jak za pomocÈ tej biblioteki moĝemy rozwiÈzaÊ nasz problem.
Podstawy Backbone
Backbone to dziaïajÈca po stronie klienta implementacja wzorca model-widok-
-kontroler, na powstanie której duĝy wpïyw miaïy serwerowe frameworki, takie
jak ASP.NET MVC i Ruby on Rails. Backbone ma kilka komponentów poma-
gajÈcych dobrze zorganizowaÊ kod zwiÈzany z komunikacjÈ z serwerem.
Modele reprezentujÈ dane i mogÈ wspóïpracowaÊ z naszym zapleczem za poĂred-
nictwem Ajaksa. Ponadto sÈ doskonaïym miejscem na wpisanie logiki biznesowej
i kodu sprawdzajÈcego poprawnoĂÊ danych.
Widoki w Backbone nieco róĝniÈ siÚ od widoków w innych frameworkach. SÈ nie
tyle warstwÈ prezentacji, co raczej „kontrolerami widoku”. W interfejsie typowej
aplikacji dziaïajÈcej po stronie klienta moĝe byÊ wiele zdarzeñ. Kod wywoïywany
przez te zdarzenia jest przechowywany wïaĂnie w tych widokach. NastÚpnie mogÈ
one renderowaÊ szablony i modyfikowaÊ nasz interfejs uĝytkownika.
Rozdziaï 2. • Interfejs uĝytkownika
107
Routery ĂledzÈ zmiany adresu URL i mogÈ wiÈzaÊ ze sobÈ modele i widoki.
Gdy chcemy pokazaÊ w interfejsie róĝne „strony” lub karty, do obsïugi ĝÈdañ
i w celu wyĂwietlania róĝnych widoków moĝemy uĝyÊ routerów. W Backbone
obsïugujÈ one takĝe przycisk Wstecz przeglÈdarki.
W koñcu w Backbone dostÚpne sÈ kolekcje, dziÚki którym moĝemy ïatwo pobie-
raÊ zbiory egzemplarzy modeli, z którymi chcemy pracowaÊ. Na rysunku 2.10
pokazano, jak poszczególne elementy Backbone ze sobÈ wspóïpracujÈ oraz w jaki
sposób uĝyjemy ich do budowy naszego interfejsu do zarzÈdzania produktami.
Rysunek 2.10. Skïadniki Backbone
DomyĂlnie modele Backbone komunikujÈ siÚ z serwerowÈ aplikacjÈ RESTful
przy uĝyciu metody ajax() z biblioteki jQuery i formatu JSON. Zaplecze musi
akceptowaÊ ĝÈdania GET, POST, PUT i DELETE oraz rozpoznawaÊ dane w formacie
JSON w treĂci tych ĝÈdañ. SÈ to jednak tylko ustawienia domyĂlne, które moĝna
zmodyfikowaÊ. W dokumentacji Backbone znajdujÈ siÚ informacje na temat tego,
jak zmodyfikowaÊ kod dziaïajÈcy po stronie klienta tak, aby wspóïpracowaï
z róĝnymi rodzajami zapleczy.
Nasze zaplecze bÚdzie obsïu
Pobierz darmowy fragment (pdf)