Ajax on Java 9788324611102, 832461110X, 9788324659517, 832465951X

Technologia Ajax oparta na kodzie Java gwarantuje uzyskanie prawdziwej interaktywności witryny internetowej, wysoce komf

207 49 38MB

Polish Pages 232 [221] Year 2007

Report DMCA / Copyright

DOWNLOAD PDF FILE

Recommend Papers

Ajax on Java
 9788324611102, 832461110X, 9788324659517, 832465951X

  • 0 0 0
  • Like this paper and download? You can publish your own PDF file online for free in a few minutes! Sign Up
File loading please wait...
Citation preview

T/i'oje aplikacje będą jeszcze bardziej interaktywne!

H f i- HELION

O’REILLY4

Stere/1 Douglas Olso/i

Spis treści Przed m ow a ......................................................................................................................................7 1.

2.

3.

4.

P rzy g o to w a n ia .................................................................................................................. 13 W ymagania

13

Instalowanie serwera Tomcat

14

Instalowanie Ant

15

JavaScript i A ja x .................................................................................................................17 Tworzenie aplikacji

18

Uruchamianie przykładu

24

Prosty serwlet A ja x ........................................................................................................... 25 Budowanie i instalow anie aplikacji Ajax

27

Uruchamianie przykładu

29

XM L oraz JSON i A j a x ....................................................................................................... 31 Aplikacja dekodująca znaki

5.

6.

31

Przygotow ujem y prosty dokument XM L

32

W racam y do klienta — analiza kodu XM L

40

Budowanie aplikacji

45

Uruchamianie aplikacji na serwerze Tomcat

47

Przesyłanie danych z użyciem formatu JSON

48

Podsumowanie

51

Pobieranie potrzebnych d a n y c h .....................................................................................53 W ypełnianie formularza za pomocą Ajaksa

53

Tworzenie pola sugerującego nazw y użytkow ników

62

Biblioteki i zestaw y n a rzę d zio w e ...................................................................................75 Korzystanie z biblioteki Dojo Toolkit

76

Korzystanie z biblioteki Rico Toolkit

81

Korzystanie z biblioteki DW R

87

Przeciąganie i upuszczanie z wykorzystaniem bibliotek Scriptaculous i Prototype

92

5

7.

Znaczniki A ja x ..................................................................................................................111

8.

Tworzenie biblioteki znaczników

111

Biblioteki znaczników oferowane przez innych dostawców

121

Ajax i S tru ts .....................................................................................................................145

9.

Biblioteka Struts-Layout

145

Implem entowanie funkcji Ajax w Struts z użyciem biblioteki DW R

157

Ajax i Struts — czego dowiedzieliśm y się w tym rozdziale?

170

Ajax i JavaServer Fa ce s...................................................................................................171

10.

Cykl życia JSF

172

Pisanie własnego komponentu JSF

172

Tworzenie w łasnego znacznika JSF

177

Obsługiwanie danych JSF poprzez rozszerzanie klasy H tm llnputText

185

Kod JSF w spom agający mechanizm Ajax

186

Podsumowanie

189

Zestaw narzędziow y Google Web T o o lk it ..................................................................191 Zaczynamy pracę z GW T

191

W yszukiwanie błędów w kodzie aplikacji

196

Rozbudowujemy aplikację — kod klienta

200

Udostępnianie usług klientow i

204

Testowanie współdziałania aplikacji ZipCodes z usługą

209

Kontrolki oferowane przez GW T

212

S k o ro w id z.................................................................................................................................... 217

6

|

Spis treści

Przedmowa

— Popatrzcie, jaka cudowna rzecz! — zawołałem do swoich kolegów z pracy. — Co to takiego? — zapytał jeden z nich. — M apy w usłudze Google Maps w ykorzystują Ajax — wyjaśniłem . — Co to takiego Ajax? — To skrót od Asynchroniczny JavaScript i XML. Połączenie działających asynchronicznie skryp­ tów JavaScript i XM L, umożliw iające w ysyłanie zadań ze stron W W W do serwera i w yśw ie­ tlanie otrzymanych wyników bez konieczności wypełniania form ularza HTM L i czekania na odświeżenie strony. — Znakomite! To znaczy, że moja aplikacja W W W będzie mogła działać tak interaktywnie, jak zwykła aplikacja zainstalowana na komputerze! W łaśnie. Do tej pory program iści piszący kod dla stron W W W mieli do wyboru tylko bardzo ubogie w możliwości aplikacje W W W i znacznie bardziej zaawansowane program y, które jednak wymagały instalacji na komputerze użytkownika. W raz z pojaw ieniem się technologii Ajax uzyskali możliw ość pisania aplikacji W W W oferujących podobne bogactw o możliwości, co program y instalowane na komputerach, dzięki czemu nie m uszą już dłużej przejmow ać się instalowaniem na komputerze użytkownika najnow szych aplikacji potrzebnych mu do korzystania ze strony. Dzięki temu mogą teraz pisać napraw dę interaktywne aplikacje inter­ netowe.

Ajax: krótki rys historyczny Na początku powstał HTML i świat stwierdził, że był dobry. Wkrótce potem pojawiły się apli­ kacje W W W i świat zachwycił się możliwością interaktywnego korzystania z danych w inter­ necie. Pośród nich były wyszukiw arki, usługi umożliwiające realizację płatności poprzez sieć, internetowe domy maklerskie, interaktywne gry, wirtualne sklepy i wiele, wiele innych. Pojawia się pytanie: czego brakowało tym wszystkim aplikacjom? Odpowiedz jest prosta: praw­ dziwej interaktywności, czyli zdolności do szybkiego reagowania na działania użytkownika. Moje pierwsze doświadczenie naprawdę intuicyjnej interakcji z komputerem miało miejsce daw­ no temu w roku 1984. Byłem w tedy studentem i w kampusie była pracownia komputerowa zaopatrzona w now y produkt firmy Apple Computer Inc. — komputer Macintosh. Komputery

7

były źródłem nieustannych zachwytów wśród studentów. Zaopatrzone były co prawda w tyl­ ko jed en program : edytor tekstu M acW rite, ale w zupełności m i to w ystarczało. Podobnie jak w przypadku w ielu innych studentów przyjazny i łatwy w obsłudze interfejs program u M acW rite z miejsca zawojował moją duszę. Niestety do niedawna aplikaqe internetowe nie były w stanie zaoferować użytkownikom takiego komfortu, jakiego doświadczali podczas pracy z aplikacjami instalowanym i na komputerach. Oczywiście możliwe było przygotowanie odpowiednich aplikacji dla bogatych klientów. Nie­ mniej obsługa takich klientów wymaga poświęcenia im czasu, co z kolei nie jest możliwe ani praktyczne w przypadku zwykłych klientów korzystających z aplikacji przeznaczonych dla przeglądarek internetowych. Jeśli chodzi o łatwość przygotowania i zaopatrywania użytkowni­ ków w najnow sze wersje oprogramowania, nie ma lepszego rozwiązania niż aplikacje W W W przeznaczone dla przeglądarek internetowych. Ideałem byłyby oczywiście aplikacje współpra­ cujące z przeglądarkami, które dawałyby użytkownikom taki stopień komfortu, jaki dotychczas był udziałem tylko bogatych klientów. Poznajcie Ajax. Niektórzy z Czytelników zetknęli się już pewnie z technologią Ajax, która rozwijała się w in­ ternecie od jakiegoś czasu, choć nie zawsze znana była pod tą nazwą. Termin Ajax (skrót od Asynchronous Java Script and XML — asynchroniczny JavaScript i XM L) w prow adzony został przez Jesse Jamesa Garretta z firmy Adaptive Path w artykule Ajax: A New Approach to Web Ap­ plications („Ajax, now e podejście do aplikacji W W W "; http://www.adaptivepath.com/publications/ essays/archives/000385.php). Wkrótce po publikaqi tego artykułu podniosły się głosy, że w tym po­ dejściu nie ma tak naprawdę nic nowego. W końcu wielu programistów pisało asynchroniczne aplikacje już przed pojawieniem sie interfejsu API XMLHttpRequest. Aplety Javy mimo swo­ ich ograniczeń mogły zaoferow ać podobny stopień interaktywności, co aplikacje instalowane na komputerze. Podobnie jak aplikacje z animacjami Flash. Co w takim razie się zmieniło? Czemu ta technologia jest taką rewolucją? Cóż, przede wszyst­ kim praktyka program owania asynchronicznych aplikacji zyskała chwytliwą, zbiorczą na­ zwę. To na pozór nie tak wiele, ale zawsze tworzy wspólną platformę dyskusji dla zajmujących się tym problem em programistów. Podobnie jak nazw y różnych metod projektow ania w yko­ rzystywane w program istycznym żargonie, tak i nazwa A jax pozw ala od razu zorientować się, o jakiej technice programistycznej mówimy. Od czasu publikacji artykułu Garretta w sieci rozgorzała dyskusja, jak wykorzystać możliwości technologii Ajax i jak radzić sobie z jej ograniczeniami. Wysyp nowych artykułów, narzędzi i in­ formacji na tem at tej technologii spowodował w zrost zainteresowania Ajaksem. Dzięki temu, że informacje na temat technologii Ajax stały się nagle bardziej dostępne, techniki programo­ wania Ajax trafiły do głównego nurtu program owania aplikacji W W W i w krótce zapewne będą traktowane przez użytkowników internetu jako standard. W tym w łaśnie tkwi siła nazwy. Technologia Ajax zmniejszyła lukę pomiędzy rozbudowanymi aplikacjami dla specjalnych (bo­ gatych) klientów a dość prostym i do tej pory aplikacjam i przeznaczonym i dla klientów ko­ rzystających z przeglądarek. Książka nasza jest w łaśnie wprow adzeniem do technologii Ajax, pokazującym, jak wzbogacać o funkcje Ajax aplikacje oparte na serwletach, aplikacje JSP, apli­ kacje JSF itp.

8

|

Przedmowa

Zapraszam więc do tej ekscytującej podróży: nauczmy się, jak uczynić nasze aplikacje bardziej interaktywnymi, atrakcyjniejszymi i bardziej efektywnymi dzięki wyeliminowaniu pracochłon­ nego wpisywania danych przez użytkownika i irytującego oczekiwania na ponowne załado­ wanie się całej strony. Innym i słowy: jak sprawić, aby użytkownicy aplikacji W W W mogli korzystać z nich w równie komfortowy sposób, co użytkownicy aplikacji instalowanych bez­ pośrednio na komputerze. To właśnie obiecuje nam technologia Ajax.

Dla kogo przeznaczona jest ta książka Nasza książka przeznaczona jest dla pragnących się rozwijać programistów języka Java, nieza­ leżnie od ich stopnia doświadczenia w programowaniu. W szczególności dla tych, którzy zaj­ mują się pisaniem aplikaqi WWW. Wspomniałem o chęci rozwoju, ponieważ książka ta pomoże Czytelnikowi podnieść umiejętności programowania na wyższy poziom. Dzięki zdobytej wie­ dzy będziem y mogli dostarczyć naszym użytkow nikom bardziej wygodnych aplikacji, gdy niezgrabny interfejs klasycznej aplikacji W W W zostanie zastąpiony przez znacznie bardziej interaktywny interfejs aplikacji wykorzystującej technologię Ajax. " I " « *

Co powinniśmy wiedzieć Program iści języka Java posiadający doświadczenie w pisaniu aplikacji W W W nie powinni mieć żadnych problem ów ze zrozum ieniem m ateriału prezentow anego w niniejszej książce. Pisząc ją, zakładałem, że Czytelnik będzie miał pewną w iedzę na tem at serwletów Javy, ję ­ zyka H TM L i języka JavaScript. Przydatna, aczkolw iek nie niezbędna, będzie również pewna wiedza na temat analizy dokum entów XML.

Zawartość książki Książka nasza została podzielona na dziesięć rozdziałów: Rozdział 1. „Przygotow ania" Rozdział ten opisuje środowisko programowania, które będzie nam potrzebne, by uruchomić przykłady kodu Ajax prezentow ane w tej książce. Przykłady te korzystają z pojemnika (serw era) Tomcat, niemniej Czytelnicy przyzw yczajeni do innych pojem ników dla kodu J2EE (Java Platform, Enterprise Edition) nie pow inni m ieć problem ów z uruchamianiem przykładów kodu. Rozdział 2. „JavaScript i A jax" Ten rozdział wyjaśnia, w jaki sposób język JavaScript korzysta z funkcji Ajax, oraz pokazuje, w jaki sposób za pomocą języka JavaScript wykonywać asynchroniczne wywołania z uży­ ciem obiektu XMLHttpRequest. Rozdział 3. „Prosty serwlet A jax" W tym rozdziale opowiemy, jak obsługiwać klienta Ajax za pomocą serwletu. W tym wła­ śnie miejscu książka nasza różni się od innych książek poświęconych Ajaksowi: zamiast z innych technologii, takich jak PHP, Perl czy Rails, korzysta z języka Java.

Zaw arto ść książki

|

9

Rozdział 4. „XML oraz JSO N i A jax" Mimo iż mogłoby się wydawać, że XML jest integralną częścią Ajaksa, w istocie nie jest wy­ magany. Ten rozdział pokazuje, jak używ ać języka XML do indeksow ania danych po­ wracających do klienta, i prezentuje format JSON jako atrakcyjną alternatyw ę dla XML. Rozdział 5. „Pobieranie potrzebnych danych" Ten rozdział pokazuje, jak przechowywać dane potrzebne aplikacjom Ajax w bazie danych oraz jak pobierać te dane z bazy. Rozdział 6. „Biblioteki i zestawy narzędziow e" Stworzono wiele ram (środowisk) program owania i zestawów narzędziowych, by pomóc programistom Ajax w tworzeniu pewnych podstawowych funkcji niezbędnych do obsługi mechanizmów Ajax. Ten rozdział omaw iać będzie niektóre z tych środowisk i zestawów narzędziowych, takie jak Dojo, Rico, Prototype, DW R czy Scriptaculous. R ozd ział 7. „Z naczniki A jax " Technologia JavaServer Pages (JSP) oferuje możliwość ponownego wykorzystania kodu dzięki zastosowaniu tzw. bibliotek znaczników (ang. tag libraries). Ten rozdział opisuje, jak tworzyć dla JSP znaczniki kodu Ajax. Rozdział 8. „Ajax i Struts" Rozdział omawia integrow anie technologii Ajax ze środowiskiem Struts. Rozdział 9. „Ajax i JavaServer Faces" Rozdział pokazuje, jak korzystać z technologii Ajax w połączeniu ze środowiskiem JavaServer Faces. Rozdział 10. „Zestaw narzędziowy Google W eb Toolkit" Zestaw narzędziowy Google Web Toolkit umożliwia nie tylko dokładne debugowanie kodu Ajax (usuwanie błędów z kodu Ajax), ale ponadto jest również fascynującym wprowadze­ niem do technologii Ajax w języku Java. Ten rozdział jest przew odnikiem po tym zna­ komitym zestawie narzędziowym przygotow anym przez Google dla program istów Ajax.

Konwencje stosowane w książce W naszej książce korzystamy z następujących konwencji typograficznych: Kursywa Używamy jej w nazwach klawiszy (takich jak Alt lub Ctrl) i skrótach klawiaturowych. Używamy jej dla wyróżnienia nowych terminów, a ponadto w tytułach i poleceniach menu, opcjach menu, tekstach na przyciskach, adresach URL, adresach e-mail, nazw ach plików, rozszerzeniach plików, ścieżkach do plików i katalogów, nazwach katalogów i nazwach program ów narzędziowych systemu Unix. Czcionka o s t a ł e j s z e r ok o ś c i Służy do wyróżniania poleceń, opcji i przełączników programowych, zmiennych, atrybutów, kluczy, funkcji, typów, klas, przestrzeni nazw , metod, modułów, właściw ości, param e­ trów, wartości, obiektów, zdarzeń, procedur obsługi zdarzeń, znaczników XML, znaczni­ ków HTM L, zawartości plików oraz wyników zw racanych przez polecenia.

10

|

Przedmowa

Pogrubiona czcionka o stałej szerokości W yróżnia polecenia lub inny tekst, który musi zostać w pisany przez użytkownika lite­ ralnie (tak jak jest podany w książce). Ta ikona oznacza wskazówkę, sugestię lub użyteczną uwagę.

Ta ikona oznacza ostrzeżenie.

Korzystanie z przykładów kodu Podręcznik ten został napisany po to, aby ułatwić życie programistom. Dlatego też Czytelnicy mogą swobodnie korzystać z kodu prezentowanego w tej książce w swoich programach i do­ kumentacji. Nie ma potrzeby kontaktowania się z nami, by uzyskać zgodę na wykorzystywanie fragmentów kodu, o ile nie zamierzacie użyć znaczącej porcji kodu. Dla przykładu: jeśli ktoś pisze program, który będzie w ykorzystyw ał kilka fragm entów kodu z tej książki, nie ma po­ trzeby uzyskiw ania naszej zgody. Natom iast odsprzedawanie lub rozpow szechnianie płyt CD-ROM z przykładam i kodu, dołączanych do niektórych książek wydawanych przez w y­ dawnictwo O 'Reilly, już takiej zgody wymaga. Odpowiadanie na pytania innych użytkow ni­ ków Perla, podpierając się cytatami z tej książki, nie wymaga naszej zgody. Natomiast dołączanie do dokumentacji własnego komercyjnego produktu znaczącej porcji przykładów z tej książki wymaga naszej zgody. Będziemy wdzięczni za podanie źródła wykorzystywanego kodu, aczkolwiek tego nie wyma­ gamy. Zazwyczaj informacja taka powinna zawierać tytuł książki, autora, wydawcę i ISBN. Na przykład: „Ajax on Java, wydanie II, autor Steven Douglas Olson, wydawnictwo Helion, 2007, ISBN: 978-83-246-1110-2".

Podziękowania W dzięczny jestem za wszelką pomoc, którą otrzymałem podczas pisania tej książki. W styczniu 2004 roku, kiedy przeczytałem sławny już artykuł Jesse Jam essa Garretta, po raz pierwszy opisujący tę nową technologię i wprowadzający termin Ajax, poczułem, że jest to początek re­ wolucji w program owaniu stron aplikacji dla stron W W W . Mimo iż co bardziej innowacyjni tw órcy stron W W W korzystali z technik program owania Ajax już wcześniej, by zaoferować użytkownikom internetu interesujące usługi, dopiero w roku 2004 ten nieśmiały ognik innowacji eksplodował wielkim płomieniem technologicznej rewolucji. Wdzięczny jestem szczególnie armii program istów, którzy stworzyli środowiska takie jak DW R (Joe W alker), Dojo, Rico (Richard Cowen, Bill Scott, Darren Jam es) i Scriptaculous (Thomas Fuchs). Ponadto chciałbym również podziękować zespołowi, który stworzył zestaw narzędziowy Google W eb Toolkit oraz Edowi Burnsowi, Gregowi Murray i Torowi Norbye za pracę, jaką włożyli w zintegrowanie technologii Ajax ze środowiskiem JavaServer Faces.

P o d zię ko w an ia

|

11

Należy również pamiętać, że nowa technologia nie rozpowszechniłaby się tak szybko bez wielu zaangażowanych apostołów. Wspaniałym źródłem informacji jest na przykład witryna Ajaxian.com prowadzona przez Bena Galbraitha i Diona Almaera. Dzięki tej witrynie programiści uzyskali łatwy dostęp do pom ocy i w ielu użytecznych inform acji na tem at technologii Ajax. Chciałbym też podziękować mojemu redaktorowi M ike'ow i Loukidesowi za ogromne wspar­ cie, którego udzielił mi w realizacji tego przedsięwzięcia. Pomógł mi przedstawić wiele naprawdę złożonych problemów w prosty sposób i pomógł mi przerobić niektóre z moich całkowicie nie­ czytelnych zdań na czytelną i zrozum iałą dla zwykłego człowieka prozę. Trudno przecenić jego w kład w pracę nad tą książką. Wyrazy wdzięczności należą się też komentatorom i korektorom książki. Michael Davis przejrzał w iększość prezentow anego kodu i pom ógł mi wykryć pojaw iające się tu i ówdzie problemy. David Lakes pom ógł mi zorganizow ać pracę i zadbał, by zawartość książki była strawna dla przeciętnego czytelnika. W reszcie Vim al Kansal zbadał praw dziw ość informacji technicznych prezentow anych w książce. Na koniec oczywiście pragnąłbym złożyć podziękowania mojej rodzinie, która wspierała mnie podczas realizacji tego projektu. Dziękuję więc moim dzieciom Jordanowi, Erikowi, Stefani, Mat­ thew i Kyrze. Nie poradziłbym sobie też z tym wszystkim bez pomocy i wsparcia, którego udzie­ liła mi moja żona Erin. Właśnie jej przede wszystkim należą się moje podziękowania i wyrazy miłości.

— Steven Douglas Olson Listopad 2006

12

I

Przedmowa

ROZDZIAŁ 1.

Przygotowania

Pierwsza rzecz, którą m usimy zrobić, to przygotow anie odpowiedniego środowiska progra­ mowania, które umożliwi nam program owanie i analizowanie przykładów kodu Ajax pre­ zentowanych w tej książce. Środowisko to będzie się różnić znacznie od środowisk przygo­ towywanych dla potrzeb innych technologii programowania.

Wymagania Na początek należy wyjaśnić, że Ajax nie jest językiem program owania ani pakietem opro­ gramowania. Innymi słowy, nie ma jednego źródła technologii Ajax. Jako program iści języka Java będziem y mieli do w yboru wiele różnych narzędzi umożliwiających implementację funkcji Ajax. Przyjrzyjmy się minimalnemu zestawowi narzędzi, który będzie nam potrzebny, by pisać aplikacje Ajax w języku Java: Przeglądarka internetowa Potrzebna nam będzie przeglądarka obsługująca język JavaScript (taka jak Internet Explorer, Safari, M ozilla, Opera, Firefox itp.). Kompilator Java Development Kit Ponadto kom pilator języka Java wraz z odpowiednim i bibliotekam i kodu, najlepiej dla w ersji Java 5 lub Java 6 języka. Apache Ant Potrzebować będziem y również narzędzia do budow ania kodu Apache Ant. M oglibyśmy sobie oczyw iście poradzić bez pom ocy Ant, ale tylko w tedy, gdybyśm y mieli skłonności masochistyczne. (Alternatywnym narzędziem jest Maven. Przykłady w tej książce zakładają, że Czytelnik korzysta z Apache Ant, niemniej przystosow anie ich do potrzeb M aven nie powinno być trudne). Serwer aplikacji Rolę tę może pełnić każdy serwer aplikacji, który potrafi obsługiwać serwlety języka Java i kom unikować się za pośrednictw em protokołu HTTP. Przykłady prezentow ane w tej książce zostały przygotow ane przy użyciu środowiska program owania JDK 1.5 firmy Sun i serwera Apache Tomcat 5.0, niemniej istnieje wiele innych w ygodnych serwerów aplikacji (takich jak JRun, JBoss, Resin, WebLogic, W ebSphere czy Glassfish), których rów­ nież można użyć do programowania aplikacji Ajax.

13

Czytelnicy, którzy chcieliby skorzystać z innego pojem nika dla serwletów niż Tomcat, mogą pom inąć akapity pośw ięcone instalacji serwera Tomcat. Niemniej osobiście zalecał­ bym korzystanie na początek w łaśnie z tego serwera. Natom iast gdy już Czytelnik zro­ zumie i uruchomi przykład, będzie go można przenieść na inny serwer.

Instalowanie serwera Tomcat Zaczniemy od pobrania z internetu i zainstalowania najnowszej wersji serwera Tomcat (w tym celu należy zajrzeć pod adres http://jakarta.apache.org/tom cat/ i w sekcji ściągania oprogram o­ wania Downloads w ybrać łącze aktualnych w ersji Current Releases). Czytelnicy, którzy nigdy jeszcze nie korzystali z serwera Tomcat, będą na pewno mile zaskoczeni. Tomcat jest bowiem znakomitym silnikiem obsługi serwletów, tak dobrym, że wykorzystywany jest jako punkt odniesienia przez technologie Java Servlet i JavaServer Pages. Co więcej, Tomcat jest serwerem dostępnym za darmo i w pełni dojrzałym. Rozpowszechniane w ersje produkcyjne tego serwera są równie stabilne, co w ersje produkcyjne komercyjnych serwerów. Projekt Tomcat oferuje ponadto bardzo dobrą dokumentację. Warto do niej zajrzeć, zwłaszcza jeśli dopiero zapoznajemy się z serwerem Tomcat. Innym dobrym źródłem infor­ macji jest książka Jasona Brittaina i Iana Darwina Tomcat: The D efinitive G uide (wydawnictwo O'Reilly).

Krótki przewodnik instalowania serwera Tomcat W system ach typu Linux i Unix należy pobrać ze strony plik tar.gz i zainstalow ać serw er, w pisując polecenie zx vf w katalogu, w którym chcem y um ieścić serw er Tom cat (np. /usr/local/tomcat). W przypadku systemu W indows tw órcy serwera Tom cat przygotow ali sam orozpakow ujący się plik wykonywalny. W ystarczy pobrać go na nasz kom puter i urucho­ mić program instalacyjny setup.exe. Gdy już zainstalujemy serwer Tomcat, należy go uruchomić. W systemach Linux lub Unix należy wpisać polecenie: /< k a ta lo g

in s ta la c y jn y

tomcat>/bin/startup.sh

W systemie W indows natomiast: \ < k a ta lo g i n s t a l a c y j n y

tomcat>\bin\startup.bat

Teraz należy uruchomić przeglądarkę i w pisać w jej okienku adres h t t p : / / l o c a l h o s t : 8080, by wyśw ietlić stronę główną naszego lokalnego serwera Tomcat. M ożna również uruchomić przykładowe serwlety, by upewnić się, że serwer działa prawidłowo. Aby zamknąć serwer Tomcat, należy uruchomić polecenie shutdown.sh (Linux) lub shut down.bat (Windows) w katalogu instalacyjnym serwera Tomcat.

Definiowanie zmiennej TOMCAT_HOME W szystkie przykłady prezentowane w tej książce będą budowane i przygotowywane do dzia­ łania z w ykorzystaniem narzędzia Ant. (Czytelnicy, którzy nie znają jeszcze narzędzia Ant i nie wiedzą, jakie jest zadanie plików build, powinni poświecić trochę czasu, by zaznajomić się

14

|

Rozdział 1. Przygotowania

z tym problemem). Pliki budow y build będą w ym agały odpowiedniego ustawienia zmiennej środowiskowej TOMCAT_HOME, aby było pewne, że gdy już umieścimy naszą aplikację na serwerze, plik build.xml skopiuje wszystkie potrzebne jej informacje do katalogu webapps serwera Tomcat. Aby sprawdzić w artość zmiennej środowiskowej na komputerze z systemem W indow s, nale­ ży w w ierszu poleceń w pisać polecenie s e t . Pośród innych zmiennych środowiskow ych zo­ baczym y również: TOMCAT HOME c:\apps\Tomcat5.0

Zmiennej środowiskowej TOMCAT_HOME powinna być przypisana lokalizacja, w której zain­ stalowaliśmy serwer Tomcat. Jeśli odpowiedni katalog nie został określony, konieczne będzie ustawienie zmiennej TOMCAT_HOME za pomocą ekranu definiowania zm iennych środowisko­ w ych (przyw oływ anego poleceniam i Start/Panel Sterow ania/System /Zaaw ansow ane/Zm ienne środowiskowe). Czytelnicy, którzy nie wiedzą, jak się do tego zabrać, pow inni otw orzyć Pomoc w menu Start i zajrzeć pod hasło „zmienne środow iskow e" (ang. environment variables). W systemie Linux należy natomiast w wierszu poleceń wpisać polecenie s e t | grep TOMCAT. Na ekranie powinna pojawić się informacja o wartości zmiennej TOMCAT_HOME, podobna do tej: TOMCAT HOME /usr/local/tomcat/Tomcat5.0

Tak jak w systemie W indows, zmiennej TOMCATJHOME powinien być przypisany katalog, w któ­ rym zainstalowaliśmy serwer Tomcat. Jeśli tak nie jest, trzeba go będzie określić. Zazwyczaj w ym aga to dodania do pliku zasobów takiego jak .bashrc polecenia export: export TOMCAT HOME /usr/local/tomcat/Tomcat5.0

Instalowanie Ant Aby uruchamiać przykłady prezentowane w tej książce, konieczne będzie również pobranie i zainstalow anie narzędzia Ant. N ajnow szą w ersję m ożna znaleźć pod adresem http://ant. apache.org. Należy upewnić się, że podkatalog bin katalogu, w którym zainstalowaliśm y Ant, znajduje się na naszej systemowej ścieżce przeszukiwania. Teraz można uruchomić narzędzie, wpisując w w ierszu poleceń ant. Program Ant pow inien zwrócić kom unikat „Build file does not exist" („Plik build nie istnieje"). Kom unikat ten oznacza, że program Ant został zainstalowany prawidłowo i kiedy go załadowaliśmy, nie był w stanie znaleźć odpowiedniego pliku build.xml (którego jeszcze nie mamy). Jeśli natomiast nie zainstalowaliśmy programu Ant prawidłowo, zobaczymy komunikat o błędzie w rodzaju „executable file ant not found" („plik wykonywalny ant nie został odnaleziony"). W tym przypadku należy sprawdzić, czy zmiennej środowiskowej ścieżki przeszukiwania PATH został przypisany podkatalog bin w katalogu instalacyjnym Ant. Podobnie jak w przy­ padku zmiennej środowiskowej TOMCAT_HOME użytkownicy W indows mogą określić zmienną środowiskową PATH, korzystając z okna Właściwości systemu (Start/Panel Sterowania/System/ Zaawansowane/Zmienne środowiskowe). Natom iast użytkownicy systemu Linux będą m usieli dodać do pliku inicjującego ich powłokę systemową (zazwyczaj jest to plik .bashrc) następujące wiersze: export TOMCAT HOME /usr/local/tomcat/Tomcat5.0 export PATH $PATH:$TOMCAT HOME/bin

Instalowanie Ant

|

15

Gdy już program Ant zostanie praw idłowo zainstalowany, będziem y mogli wykorzystać go do budowania aplikacji prezentowanych w naszej książce. Więcej informacji na temat Ant można znaleźć w jego dokumentacji na stronie http://ant.apache.org lub w jednym z wielu podręczników na temat Ant. Osobiście korzystam z książki Ant: The Definitive Guide Steve'a Holznera (wy­ dawnictwo O 'Reilly), która całkiem dobrze mi dotąd służyła.

16

|

Rozdział 1. Przygotowania

ROZDZIAŁ 2.

JavaScript i Ajax

Tajemnica technologii Ajax polega na sprytnym wykorzystaniu języka JavaScript. Ajax nie jest szkieletem program owania dla stron W W W tak jak Struts czy Tapestry i pod tym akronimem tak napraw dę nie ukrywa się żadna now a cudowna technologia. Sekret Ajax polega na bez­ pośrednim komunikowaniu się za pom ocą języka JavaScript z serwerem stron W W W , dzięki czemu unika się cyklu zatwierdzenie danych - odpowiedz, tak dobrze znanego wszystkim użytkownikom stron W WW. Programiści języka Java zazwyczaj unikają języka JavaScript. Z różnych powodów, lepszych i gorszych. Oczywiście dodanie kolejnej w arstw y skryptowej do strony JSP zwiększa tylko zamieszanie. Niemniej kod JavaScript w ykonyw any jest bezpośrednio przez przeglądarkę internetową i dlatego jest bardzo szybki. Nie ma potrzeby oczekiwania, aż serwer w ygene­ ruje odpowiedz: kod JavaScript jest w stanie prawie natychm iast w ygenerować w ynik i od­ powiednio aktualizować stronę. Technologia Ajax dodaje tu interakcję z serwerem, jednak bez konieczności zatwierdzania (i wy­ syłania) danych przyciskiem Submit. Kiedy potrzebne są nowe dane od serwera, strona W W W z kodem JavaScript po prostu wysyła żądanie, a serwer odsyła z powrotem odpowiednie dane — tym razem nie jest to jednak nowa strona w kodzie HTML. Serwer zwraca dane, które kod JavaScript będzie mógł wyśw ietlić na bieżącej, już załadowanej stronie. Efekt jest taki, że na­ sza aplikacja WWW zaczyna bardziej przypominać zwykłą aplikację instalowaną na komputerze. M ówiąc w skrócie, korzystając z technologii Ajax, m ożemy osiągnąć n a naszych stronach W W W poziom interaktyw ności zbliżony do tego znanego z profesjonalnych aplikacji insta­ lowanych na komputerze. Celem tej książki nie jest nauczenie Czytelnika programowania w języku JavaScript ani nawet omawianie jego w ad i zalet. Zakładam tutaj, że każdy z Czytelników ma już jakieś doświad­ czenie z językiem JavaScript. Ci, dla których jest on now ością, pow inni zajrzeć do książki JavaScript. Przewodnik programisty autorstw a Davida Flanagana (wydawnictwo RM). Jest to najlepszy obecnie dostępny przew odnik po języku JavaScript. M im o iż język JavaScript różni się od Javy, niemniej program iści języka Java nie pow inni m ieć w iększych problem ów ze zrozumieniem kodu JavaScript. Jak łatwo się będzie przekonać, kod JavaScript zaprezentowany w tym rozdziale jest dość prosty. Dopóki składnia języka jest dla Czytelnika zrozum iała, nie ma potrzeby dokładnego studiow ania języka JavaScript.

17

Tworzenie aplikacji Zaczniemy od przygotowania kompletnego kodu HTML i JavaScript naszej pierwszej aplikacji. Będzie to prosta strona W W W wyśw ietlająca liczbę dziesiętną odpowiadającą każdem u zna­ kowi. Następnie oddzielimy kod JavaScript od kodu HTM L i przyjrzym y się mu dokładnie. Kod HTM L ukazany został na listingu 2.1. Listing 2.1. index.html



Ajax on Java, Rozdział 2 przykład

AJAXOWY DEKODER ZNAKÓW Wciśnij klawisz, by poznać jego kod liczbowy.

Tu podaj klawisz ->


Wciśnięty klawisz:

Kod dziesiętnie


W większości jest to standardowy kod HTML. Zawiera on tylko dwa odwołania do kodu Java­ Script: do funkcji f o c u s I n O i convertToDecimal Q. Funkcja f o c u s I n ( ) po prostu umieszcza kursor od razu w odpowiednim polu służącym do wprow adzania danych, dzięki czemu użytkow nik nie m usi go tam sam przesuw ać myszą. Funkcja convertToDecimal() będzie natomiast naszą bramą do świata technologii Ajax. Listing 2.2 prezentuje kod JavaScript obsługujący naszą stronę W W W , przechow yw any w pliku ajax.js. Listing 2.2. ajax.js var req; function convertToDecimal( ) { var key document.getElementById("key");

18

|

Rozdział 2. JavaScript i Ajax

var keypressed document.getElementById("keypressed"); keypressed.value key.value; var url "/ajaxdecimalcodeconverter/response?key " + escape(key.value); if (window.XMLHttpRequest) { req new XMLHttpRequest( ); } else if (window.ActiveXObject) { req new ActiveXObject("Microsoft.XMLHTTP"); } req.open("Get",url,true); req.onreadystatechange callback; req.send(null); } function callback( ) { if (req.readyState 4) { if (req.status 200) { var decimal document.getElementByld('decimal'); decimal.value req.responseText; } } clear( ); } function clear( ) { var key document.getElementById("key"); key.value ""; } function focusIn( ) { document.getElementById("key").focus(

);

}

Przyjrzyjmy się funkcji convertToDecimal () , która w kodzie HTML jest naszym punktem wejścia do tego pliku z kodem JavaScript. Najważniejszym obiektem JavaScript, którego bę­ dziemy używać, jest obiekt XMLHttpRequest. Niestety podstawowy problem z językiem Java­ Script polega na tym, że kod tego języka skryptowego nie będzie taki sam dla w szystkich przeglądarek. W przeglądarkach M ozilla, Firefox i Safari now y obiekt XMLHttpRequest tw o­ rzymy w następujący sposób: new XMLHttpRequest();

W przeglądarce Internet Explorer natom iast m usim y użyć obiektu Active X: new ActiveXObject("Microsoft.XMLHTTP");

Ponieważ nie jesteśm y w stanie z góry przew idzieć, z jakiej przeglądarki internetowej będą korzystać użytkow nicy odwiedzający naszą stronę W W W , m usim y przygotow ać kod, który będzie w spółpracow ał z w szystkim i najważniejszym i przeglądarkam i. Po pierw sze, musimy ustalić, czy użytkownik korzysta z przeglądarki Internet Explorer, czy może jakiejś innej, takiej jak na przykład Firefox czy Mozilla. Zajmuje się tym następujący fragm ent kodu: if (window.XMLHttpRequest) { req new XMLHttpRequest(

);

} else if (window.ActiveXObject) { req new ActiveXObject("Microsoft.XMLHTTP"); }

Kod ten po prostu tw orzy (w zależności od przeglądarki) odpowiedni obiekt req, który w y­ korzystam y do zbudow ania naszej strony Ajax.

Tw o rze n ie aplikacji

|

19

Przyjrzyjmy się teraz części kodu, która w ykonuje rzeczywistą pracę. W kolejnym rozdziale będziem y korzystać z kodu prezentow anego tutaj w pliku ajax.js, przyjrzyjm y się m u więc uważnie i zbadajm y m echanizm kom unikacji z serwerem. Ponieważ jesteśm y program istam i Javy, program, z którym kod JavaScript się komunikuje, będzie serwletem, niemniej dla strony W W W nie ma to znaczenia. Funkcja convertToDecimal () najpierw pobiera z form ularza łańcuch (St ri ng) , a następnie przypisuje zmiennej u rl wartość " / a j a x d e c i m a l c o d e c o n v e r t e r / r e s p o n s e ? k e y = . . . " . Na koniec w ysyła ten adres URL serwerow i (w naszym przypadku serwletowi) i oczekuje na odpowiedź (którą będzie dziesiętna w artość kodu przypisana klawiszowi). Inaczej niż na zwykłej stronie nie wysyłamy danych serwerowi dopiero po wciśnięciu przycisku zatwierdza­ jącego Submit. Tym razem wysyłamy dane w sposób asynchroniczny (to znaczy gdy tylko użytkownik wciśnie klawisz, którego kod chcemy wyświetlić). Po bloku i f . . . e l s e , w którym ustalamy, z jakiej przeglądarki korzysta użytkow nik i po przygotowaniu odpowiedniego obiektu req, otwieramy połączenie z serwerem za pomocą następującego wywołania: req.open("Get",url,true);

Przyjrzyjmy się trzem param etrom użytej tu funkcji r e q . o p e n ( ) : "Get" Pierwszy parametr informuje JavaScript, czy wysyłać serwerowi żądanie za pomocą funkcji HTTPPost(), czy HTTPGet(). Metoda HTTPPost() ukrywa parametry w żądaniu, natomiast metoda HTTPGet() umieszcza parametry w adresie URL tak, że są widoczne dla każdego. W tym przykładzie wybrałem funkcję HTTPGet(), ponieważ łatwiej wtedy zorientować się, jakie parametry zostały przesłane serwerowi, a parametrów jest niezbyt wiele. Gdybyśmy w ysyłali długi i złożony zestaw param etrów , skorzystałbym z m etody „P ost"1. u rl Drugi param etr to adres URL, który przesyłamy serwerowi. Adres ten przygotowaliśmy w cześniej w naszej metodzie. t r ue Ostatni param etr określa, czy mamy do czynienia z w yw ołaniem asynchronicznym , czy nie. Kiedy param etrow i tem u zostanie przypisana w artość t r u e , żądanie w ysyłane jest w sposób asynchroniczny. Podczas tworzenia aplikacji Ajax zawsze będziemy przypisywać temu znacznikowi wartość true. W uproszczeniu mówiąc, oznacza on „niczego nie za­ trzymuj, po prostu poinformuj mnie, kiedy dane pow rócą".

W racam y do klienta — a n a liza kodu X M L

|

41



Tu podaj klawisz ->

name test.dir" value "test"/> name war.dir" value "war"/> name db.dir" value "db"/> name class.dir" value "${war.dir}/WEB-INF/classes"/> name test.class.dir" value "${test.dir}/classes"/> name lib.dir" value "${war.dir}/WEB-INF/lib"/> name webapp.dir" value "${env.TOMCAT HOME}/webapps/ajaxcodeconverter-lab2"/>











46

I

R o z d zia ł 4. X M L o ra z JSO N i A jax









Uruchamianie aplikacji na serwerze Tomcat Jeśli uruchamiamy naszą aplikację na serwerze Tomcat, m ożemy skorzystać z narzędzia zarzą­ dzania aplikacjami W W W o nazw ie Tom cat Web Application M anager (dostępnego pod ad­ resem http://localhost:8080/manager/html), by sprawdzić, czy nasza aplikacja została zainstalowana w odpowiednim miejscu. Okno tego m enedżera aplikacji pokazane zostało na rysunku 4.3. Jak widać, zainstalowane zostały dwie wersje programu konwertującego klawisze. Obie aplikacje działają, a katalog, w którym zainstalowaliśmy aplikacje, znajduje się na odpowiedniej ścieżce.

Rysunek 4.3. Program Tomcat Web Application Manager

Uruchamianie aplikacji na serwerze Tomcat

|

47

Kliknięcie łącza w kolumnie Applications otwiera okno przeglądarki, umożliwiające zajrzenie w prost do katalogu danej aplikacji.

Przesyłanie danych z użyciem formatu JSON Teraz, gdy już w iem y, w jaki sposób przesyła się dane za pom ocą dokumentów XM L, warto w spomnieć o w adach formatu XML. Jednym z głównych problem ów jest szybkość działania. Format XM L wymaga dwóch znaczników dla każdego elementu danych plus dodatkowych znaczników dla węzła głównego oraz ew entualnie innych węzłów. Konieczność przesyłania tych wszystkich dodatkowych danych spowalnia wymianę danych między klientem a serwerem. Bez trudu m ożemy w ten sposób stworzyć bardzo długi dokument, który zawierać będzie tylko kilka bajtów danych. Ponadto samo konstruowanie dokum entu XML może być dość złożonym procesem mogącym pow ażnie obciążać pam ięć serwera. Na szczęście istnieje też inny sposób przesyłania danych do klienta. Dzięki zastosowaniu tego sposobu dane są bardziej zwięzłe i łatwiejsze do analizowania. Ten alternatywny sposób po­ lega na skorzystaniu z formatu JSON . Obiekty w formacie JSON są zazwyczaj mniejsze od przesyłających te same dane dokumentów XML, a ponadto mniej obciążają pamięć komputera. Kolejną wielką zaletą formatu JSON jest możliwość analizowania obiektów za pomocą funkcji e v a l ( ) języka JavaScript. Nie są do tego potrzebne żadne dodatkow e biblioteki i nie musimy się przejmować dostosowywaniem kodu do różnych przeglądarek. Jeśli tylko dana przeglądar­ ka obsługuje język JavaScript oraz jego funkcję e v a l ( ) , nie będzie m iała żadnych problemów z interpretow aniem danych. Oczywiście w razie potrzeby nadal m ożem y korzystać z formatu XML (wiemy już, jak się to robi), niemniej jeśli tylko m am y wybór, wiele argumentów przemawia za używaniem for­ matu JSON . W w iększości przypadków korzystanie z formatu JSO N jest znacznie lepszym rozwiązaniem. Oto w jaki sposób nasze dane reprezentowane będą w formacie JSON: {"conversion":! "decimal": "120", "hexadecimal": "78", "octal": "170", "hyper": "&0x78", "binary": "1111000B"} }

Istnieją specjalne program istyczne techniki budow ania obiektów JSON , niemniej by przykład nasz był możliwie jak najprostszy, skorzystamy ponow nie z klasy S t r i n g B u f f e r i połączym y razem łańcuchy tekstu, tworząc gotow y obiekt. Listing 4.10 pokazuje, w jaki sposób będzie­ my tworzyć obiekt w serwlecie. Listing 4.10. AjaxJSONServlet.java package com.oreilly.ajax.servlet; import java.io.IOException; import javax.servlet.ServletException; import j a vax.servlet.http.HttpServlet;

48

|

Rozdział 4. XM L oraz JSON i Ajax

import j a vax.servlet.http.HttpServletRequest; import j a vax.servlet.http.HttpServletResponse; public class AjaxResponseServlet extends HttpServlet { private static final long serialVersionUID

1L;

public void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { // key to p a ra m etr przesyłany w zm iennej JavaS cript // o nazwie url (patrz index.html) String key req.getParameter("key"); if (key ! null) { // p o biera m y pierw szy znak z p aram etru key // j a k o liczbę całkow itą (int) i konwertujemy g o na łańcuch String int keyInt key.charAt(0); // przygotow ujem y odpow iedź res.setContentType("text/xml"); res.setHeader("Cache-Control", "no-cache"); // wysyłamy nasz obiekt JSO N String outString createStringBufferJSON(keyInt); res.getWriter( ).write(outString); } else { // je ś li p a ra m etr key m iał w artość null, zw racam y znak zapytania res.setContentType("text/xml"); res.setHeader("Cache-Control", "no-cache"); res.getWriter( ).write("?"); } } public String createStringBufferJSON(int keyInt) { StringBuffer returnJSON new StringBuffer("\r\n{\"conversion\":{"); returnJSON.append("\r\n\"decimal\": \""+ Integer.toString(keyInt)+"\","); returnJSON.append("\r\n\"hexadecimal\": \""+ Integer.toString(keyInt,16)+"\","); returnJSON.append("\r\n\"octal\": \""+ Integer.toString(keyInt,8)+"\","); returnJSON.append("\r\n\"hyper\": \"&0x"+ Integer.toString(keyInt,16)+"\","); returnJSON.append("\r\n\"binary\": \""+ Integer.toString(keyInt,2)+"B\""); returnJSON.append("\r\n}}"); return returnJSON.toString( ); } }

Kod ten nie różni się bardzo od kodu tworzącego dokum ent XM L zapisywany w obiekcie StringBuffer. Alternatywnym rozwiązaniem jest skorzystanie z biblioteki JSON . W tym celu należy pobrać plik json simple.zip spod adresu http://www.JSON.org/java/json simple.zip i rozpakować go na swoim komputerze. Trzeba skopiować plik json sim ple.jar z katalogu lib do swojego katalogu WEB IN F/lib, a następnie dodać do program u polecenie im portow ania z biblioteki j s o n _ simple: import org.json.simple.JSONObject;

Teraz kod zaprezentow any na listingu 4.10 będzie można przepisać na nowo tak, jak to zo­ stało pokazane na listingu 4.11.

Przesyłanie danych z użyciem formatu JSON

|

49

Listing 4.11. Tworzenie obiektu JSON z użyciem biblioteki json simple public String createJSONwithJSONsimple(int keyInt) JSONObject obj new JSONObject( ); JSONObject obj2 new JSONObject( );

{

obj2.put("decimal",Integer.toString(keyInt)); obj2.put("hexadecimal",Integer.toString(keyInt,16)); obj2.put("octal",Integer.toString(keyInt,8)); obj2.put("hyper","&0x"+Integer.toString(keyInt,16)); obj2.put("binary",Integer.toString(keyInt,2)+"B"); obj.put("conversion",obj2); return(obj.toString( )); }

Pierwszy obiekt o b j klasy JSONObject obudowuje drugi wynik, aby uzyskać pożądany rezultat. Obiekt JSON przygotow any z wykorzystaniem biblioteki json simple.jar będzie w yglądał tak: {"conversion":{"decimal":"103","hyper":"&0x67","octal":"147","hexadecimal":"67", "binary":"1100111B"}}

Używając biblioteki j son_simpl e, możemy bez problemów zagnieżdżać obiekty. Prawdę po­ wiedziawszy, m ożemy zagnieżdżać obiekty w tablicy JSON . W ięcej na temat tablic oraz obiektów JSON opowiem w rozdziale 5. Kolejną bibliotekę JSO N o nazw ie j s o n t o o l s można znaleźć pod adresem http://developer. berlios.de/projects/jsontools/. Podręcznik projektu j s o n t o o l s jest naprawdę znakomitym źródłem inform acji na temat formatu JSON oraz korzystania z samej biblioteki j s o n t o o l s . Dlatego warto pobrać go na swój komputer, aby mieć pod ręką dobrą dokum entację formatu JSON. Ponadto warto oczyw iście pam iętać o dokumentacji formatu JSON dostępnej pod adresem http://www.JSO N.org.

Modyfikacje w kodzie JavaScript umożliwiające pobieranie danych w formacie JSON Teraz przyjrzyjm y się naszem u klientow i napisanem u w kodzie JavaScript. Zastąpiłem dwie funkcje jedną. Mianowicie usunąłem funkqe msPopulate() i noMSPopulate(). Od tej pory wszyst­ kie przeglądarki korzystać będą z funkcji populateJSON() zaprezentowanej na listingu 4.12. Listing 4.12. Funkcja populateJSON() function populateJSON( ) { var jsonData req.responseText; var myJSONObject

eval('(' + jsonData + ')');

var decimal document.getElementById('decimal'); decimal.value myJSONObject.conversion.decimal; var hexadecimal document.getElementById('hexadecimal'); hexadecimal.value myJSONObject.conversion.hexadecimal; var octal document.getElementById('octal'); octal.value myJSONObject.conversion.octal;

50

|

Rozdział 4. XM L oraz JSON i Ajax

var binary document.getElementByld('bin'); binary.value myJSONObject.conversion.binary; var hyper document.getElementByld('hyper'); hyper.value myJSONObject.conversion.hyper; }

Podsumowanie Należy pam iętać, że Ajax w zasadzie nie jest żadną now ą, spójną technologią. To po prostu zestaw pom ysłów i technik program owania, które dowiodły, że połączone razem tworzą zu­ pełnie nową jakość. Jeśli połączym y techniki manipulow ania form ularzam i za pom ocą języka JavaScript z asynchronicznymi funkcjami zwrotnymi takimi jak XMLHttpRequest() i wbudo­ waną obsługą XM L oraz parseram i formatu JSON , to otrzym am y coś zupełnie rewolucyjnego — mimo iż poszczególne elementy były dostępne już od pewnego czasu. Dzięki połączeniu technologii działających po stronie klienta z technologiami opartymi na języ­ ku Java w ykorzystyw anym i po stronie serwera, takim i jak serwlety, Struts czy JSF, otrzym u­ jem y wspaniałą metodę tworzenia zupełnie nowej generacji interaktywnych aplikacji WWW.

Podsum ow anie

|

51

52

I

R o z d zia ł 4. X M L o ra z JSO N i A jax

ROZDZIAŁ 5.

Pobieranie potrzebnych danych

Jedną z pierwszych aplikaqi Ajax było Google Suggest: aplikaqa ta jest nawet starsza niż nazwa Ajax. Google Suggest wprowadziło zupełnie now y sposób wypełniania form ularzy HTML. Zazwyczaj trzeba w pisać dane, kliknąć przycisk Submit (Zatwierdź) i trzymać kciuki. Bardzo często okazuje się bowiem, że wybraliśmy na przykład nazwę użytkownika, która już istnieje, popełniliśmy literówkę w kodzie pocztowym lub jakiś inny błąd, który trzeba będzie poprawić. Google Suggest pozw ala zapomnieć o tych problemach: w miarę jak w pisujem y tekst w polu, pokazuje nam na bieżąco możliwe uzupełnienia wprow adzanych informacji na podstawie danych, które posiada w swojej bazie danych.

Wypełnianie formularza za pomocą Ajaksa Aplikacje umożliwiające składanie zam ów ień nie są zbyt atrakcyjne, niemniej spotykamy je na każdym kroku. Na szczęście technologia Ajax pozw ala uczynić je bardziej przyjaznym i. W świecie aplikacji W W W niezm iernie ważną rzeczą jest prostota działania i łatwość obsługi. Google Suggest pokazuje, jak uczynić życie użytkownika znacznie przyjemniejszym: wzorując się na tej aplikacji jak na modelu, możemy napisać aplikacje W W W , które od razu będą infor­ mować, czy wpisywane przez nich nazw y użytkowników nie zostały już w ybrane przez ko­ goś innego, aplikacje, które będą automatycznie uzupełniały miasto i stan po wpisaniu kodu pocztowego oraz aplikacje ułatwiające w pisywanie nazw produktów (lub im ion klientów), jeśli znajdują się już one w naszej bazie danych. Tym właśnie zajmiemy się w tym rozdziale, poprawiając stronę rejestracji użytkownika, tak aby była bardziej przyjazna i łatwiejsza do zarządzania. Zaczniemy od prostej strony rejestracji klienta zaprezentowanej na rysunku 5.1. Strona ta posiada dwa pola wym agające szczególnej uwagi: pole nazwy użytkownika i pole kodu pocztowego. Nazywał je będą polami sugerującymi. W przypadku pola nazw y użytkownika będziem y od razu inform ow ać klienta, jeśli spróbuje wpisać nazwę użytkownika, która została już przez kogoś wybrana. Dzięki temu nie będzie musiał czekać z wprow adzeniem popraw ek, aż formularz zostanie zatw ierdzony i serwer udzieli odpowiedzi. Wykorzystamy również wartość wprowadzoną w polu kodu pocztowego, by automatycznie w prow adzić odpowiednie informacje do pól miasta i województwa, zaosz­ czędzając w ten sposób użytkow nikow i pracy.

53

10

Z a rz ą d z a n ie k lie n ta m i (A JA X ) - M o z illa F ire fo x

Plik

Edycja

■'.^3 w ^

Widok

Przejdź

-

(JJl

Pierwsze kroki

Zakładki 1

Narzędzia

^ Jp Jx] Pomoc

http://localhost:8Q8Q/chQ5-suqqest/

@

Idź

Aktualności

Z A R Z Ą D Z A N IE K L IE N T A M I (A JA X ) W ybierz nazwę użytkownika i hasło Nazwa użytkownika: | Hasło: | Powtórz kasto: |

W pisz in form acje kontaktow e Email: | Nazwisko: | Adres: Kod pocztowy: | M iasto: |

Stan: |

Rejestracja Obsługa klienta

Zakończono

Rysunek 5.1. Strona rejestracji użytkownika wykorzystująca technologię Ajax

Sprawdzanie nazwy użytkownika Zaczniemy od sprawdzenia nazw y użytkownika, by upewnić się, czy wprowadzonej przez klienta nazw y nie ma już przypadkiem w naszej bazie danych. W tym celu m usim y dla pola nazw y użytkownika zarejestrować zdarzenie onblur języka JavaScript. User Name:



Zdarzenie onblur języka JavaScript uruchamiane jest, gdy pole utraci fokus — to znaczy na przykład w tedy, gdy użytkow nik w ciśnie klaw isz Tab, by przenieść się do kolejnego pola, lub gdy kliknie gdzieś poza obszarem pola nazwy użytkownika. Po uruchomieniu zdarzenia onblur przekaże ono kontrolę funkcji validateUsername() napisanej przez nas w języku Java­ Script:

54

I

R o z d zia ł 5. Pobieranie potrzebnych danych

function validateUsername( ) { var username document.getElementById("ajax username"); var url "/ajax-customer-lab5-1/username?username " + escape(username.value); if (window.XMLHttpRequest) { req new XMLHttpRequest( ); } else if (window.ActiveXObject) { req new ActiveXObject("Microsoft.XMLHTTP"); } req.open("Get",url,true); req.onreadystatechange callbackUsername; req.send(null); }

Kod ten powinien wydawać się Czytelnikom znajomy. Funkcja validateUsername() pobiera z pola nazwę użytkownika i umieszcza ją w adresie URL, przywołując funkcję escape(username. va l u e ) , by upewnić się, że wszystkie znaki specjalne pojaw iające się w nazw ie użytkownika zostaną opatrzone odpowiednim i znakam i ucieczki. Następnie w ysyła ten adres do serwera, używając żądania HTTPGetRequest. Kiedy serwer zwróci odpowiedz na żądanie, przeglądar­ ka przyw oła funkcję zwrotną cal lbackUsername() : function callbackUsername( ) { if (req.readyState 4) { if (req.status 200) { usernameCheck( ); } } }

Funkcja callbackUsername() jest bardzo prosta. Czeka na odpowiedz serwera, a następnie prze­ kazuje kontrolę metodzie usernameCheck(), która sprawdza, czy w prow adzana nazwa użyt­ kownika przypadkiem nie znajduje się już w naszej bazie danych. Jeśli tak, wyświetla użyt­ kownikowi okienko z komunikatem ostrzegawczym, że dana nazw a użytkownika już została wykorzystana: function usernameCheck( ) { // otrzymujemy z pow rotem tylko w artość logiczną, nie trzeba w ięc specjaln ie j e j analizow ać userExists req.responseText; var username document.getElementById("ajax username"); if (userExists "true") { alert("Choose another username, "+username.value+" exists already"); username.value ""; username.focus( ); } }

Nie jest to zbyt eleganckie rozw iązanie, niem niej ilustruje podstaw ow ą zasadę działania naszych sugestii dla użytkow nika. M ożem y popraw ić jeszcze ten kod, b y w yśw ietlał tekst kom unikatu trochę z boku, możemy też uczynić przycisk Zarejestruj się (nasz przycisk Submit) nieaktywnym, dopóki klient nie wprow adzi akceptowalnej nazw y użytkownika, lub też na odwrót: aktywować przycisk Zarejestruj się, gdy zostanie wprowadzona unikatowa nazwa użytkownika. W arto zwrócić uwagę, że serwer wysyła w odpowiedzi po prostu w artość logiczną tr ue (prawda) i f a l s e (fałsz). Ponieważ m usim y przesłać tylko jedną wartość, nie ma sensu obu­ dowyw anie jej za pomocą formatu XML lub JSON . Najlepsze są zazwyczaj rozwiązania naj­ prostsze.

Wypełnianie form ularza za pomocą Ajaksa

|

55

Tworzenie bazy danych Teraz gdy już napisaliśmy kod klienta sprawdzający nazwę użytkownika, musimy zaimporto­ wać nazw y z odpowiedniej bazy danych. Prezentowane tutaj przykłady korzystać będą z baz działających w system ie M ySQL, który je st dostępny za darmo pod adresem http://w w w . mysql.org, niemniej można skorzystać z dowolnej bazy danych posiadającej sterow nik JDBC. Bazę danych dla tego przykładu utw orzym y za pomocą następującego polecenia: mysql> create database AJAX;

Teraz nasza aplikacja potrzebow ać będzie tabeli, w której przechow yw ane będą nazw y użyt­ kownika sugerowane klientom (nie należy mylić ich z nazwą użytkownika bazy danych, którą właśnie utworzyliśmy). Oto kod SQL tworzący tabelę przechow ującą użytkowników: USE AJAX; CREATE TABLE USERS(USERNAME VARCHAR(50) PRIMARY KEY, PASSWORD VARCHAR(50), EMAIL VARCHAR(50), FIRST NAME VARCHAR(50), LAST NAME VARCHAR(50), ADDRESS VARCHAR(50), ZIPCODE VARCHAR(5), CITY VARCHAR(50), STATE VARCHAR(2), JOINED DATE, LAST LOGIN DATE);

Teraz musimy utworzyć kolejną tabelę, która przechowywać będzie kody pocztowe potrzebne naszej aplikacji. Tabela ta pow inna przechow yw ać wszystkie możliwe kody pocztowe w yko­ rzystywane w Stanach Zjednoczonych (Czytelnicy z innych krajów pow inni odpowiednio zmodyfikować tabelę, zakładam jednak, że zależy nam na zdobyciu klientów w USA). Oto kod SQL tw orzący tabelę kodów pocztowych: CREATE TABLE ZIPCODES (ZIPCODE VARCHAR(5) PRIMARY KEY, CITY VARCHAR(50), STATE VARCHAR(2));

Typ VARCHAR(5) wybraliśm y, ponieważ jest to rozmiar w ystarczający dla kodów pocztowych w USA, natomiast typ VARCHAR(2) wystarcza do pomieszczenia dwuliterowych kodów po­ szczególnych stanów. Po uruchomieniu tego przykładu i przyjrzeniu się mu Czytelnicy będą go mogli oczywiście zm odyfikować do w łasnych potrzeb.

—*rŁ'|‘~'‘S

56

|

W systemie Linux wielkość liter używanych w nazwach tabel baz danych MySQL ma znaczenie (inaczej niż w systemie Windows), dlatego też należy zwracać uwagę na wielkość liter w nazwach tworzonych tabel. Jeśli w środowisku Linux utworzy my tabelę o nazwie KLIENCI, to trzeba będzie do niej sięgać za pomocą polecenia SELECT * from KLIENCI. Natomiast jeśli w systemie Windows utworzymy tabelę k l i e n c i , to będzie można do niej sięgać zarówno używając polecenia SELECT * from k l i e n c i , jak i polecenia SELECT * from KLIENCI. Może to być źródłem problemów, jeśli zaczniemy tworzyć projekt w systemie Windows, a następnie prze niesiemy go na komputer z systemem Linux. Aby uniknąć kłopotów, w nazwach wszystkich moich tabel używam wyłącznie wielkich liter.

Rozdział 5. Pobieranie potrzebnych danych

Teraz musimy jeszcze zadbać, abyśmy mogli zalogować się w bazie danych z poziomu aplikacji W W W . Należy w tym celu skorzystać z polecenia GRANT bazy danych, aby utw orzyć nazwę użytkownika i hasło dla naszej aplikacji WWW: mysql> GRANT ALL ON AJAX.* to 'ajax'@'localhost'

IDENTIFIED BY 'polygon';

Polecenie to tworzy nowego użytkownika o nazwie a ja x i pozwala mu łączyć się z bazą danych tylko z lokalnego komputera ( l oc a l ho s t ) . Ponadto przypisuje mu hasło polygon. Informacje te potrzebne nam będą podczas łączenia się z bazą danych za pośrednictwem sterownika JDBC. Aby zapełnić bazę danych informacjami na temat kodów pocztowych, skorzystam y z pliku ZIPCODES.sql, który można pobrać wraz z kodami do tej książki spod adresu http://w w w . helion.pl/ksiazki/ajaxja.htm . Aby załadować te dane do bazy danych, skorzystamy z pomocy na­ rzędzia importowania mysqlimport dołączonego do systemu MySQL. Aby go użyć, należy uru­ chomić je z katalogu bin systemu M ySQL lub też zadbać, aby katalog ten znajdow ał się na naszej systemowej ścieżce przeszukiwania. Samo polecenie im portow ania danych wygląda tak (po wpisaniu polecenia M ySQL prosi o podanie hasła): mysqlimport -d -u root -p AJAX C:\ZIPCODES.sql Enter password: ******

Polecenie to odczyta w szystkie dane z pliku ZIPCO D ES.sql i zapisze je w tabeli ZIPCODES. Opcja d usuwa wszystkie w iersze do tej pory znajdujące się w tabeli. Nie ma to znaczenia podczas pierw szego im portow ania danych do pliku, jeśli jednak z jakichś pow odów za pierw szym razem nam się nie uda, użycie opcji d zapobiegnie przypadkow em u zduplikowaniu wierszy. Usunie bow iem wiersze, które zostały zaim portowane podczas pierwszej, nieudanej próby. Opcja u definiuje użytkownika, z którego konta skorzystamy podczas importowania wierszy, w tym przypadku będzie to użytkow nik root. Opcja p sprawia, że będziem y pytani o hasło. AJAX to nazw a bazy danych, do której dane zostaną zaim portowane, a ostatni param etr to im portow any plik C:\ZIPCODES.sql. W arto zauważyć, że pierw szy człon nazw y pliku ZIPCODES jest taki sam, jak nazwa tabeli. Program mysqlimport wymaga, aby nazwa tabeli odpowiadała nazwie pliku, z którego dane są importowane. Jeśli wszystkie w iersze zostaną zaim portow ane bez problem ów , na ekranie pojaw i się komu­ nikat podobny do tego: Ajax.ZIPCODES:

Records: 41905

Deleted: 0 Skipped: 0 Warnings: 41912

Obsługiwanie żądań Ajax: serwlety Teraz gdy nasza baza danych jest już gotowa, pora pow rócić do pisania kodu. Zajm iem y się serwletami, które tworzyć będą pomost między klientem a bazą danych. Kilka stron wcześniej przygotowaliśmy klienta, który sprawdzał, czy dana nazwa użytkownika już nie została przez kogoś w ybrana. W serwlecie m usimy teraz po prostu pobrać nazw ę użytkownika z żądania klienta i sprawdzić, czy takiej nazw y nie ma już przypadkiem w bazie danych. Postaramy się, aby kod serwletu był jak najprostszy. Po prostu użyjem y połączenia JDBC z bazą danych, nie będziem y tworzyć puli połączeń, w ykonyw ać m apowania między obiektami a relacjami, ani żadnych innych zaawansowanych trików, które można zobaczyć w prawdziwej aplikacji.

Wypełnianie form ularza za pomocą Ajaksa

|

57

Listing 5.1 prezentuje kod, który pobiera nazw ę użytkownika z żądania i sprawdza, czy nie ma jej już w bazie danych. Listing 5.1. AjaxUsernameServlet public class AjaxUsernameServlet extends HttpServlet { public void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { String username req.getParameter("username"); if (username ! null) { if (existsUsername(username)) { res.setContentType("text/xml"); res.setHeader("Cache-Control", "no-cache"); res.getWriter( ).write("true"); } } else { // je ś li p a ra m etr zostanie zw rócony ja k o null, wyświetlamy kom unikat res.setContentType("text/xml"); res.setHeader("Cache-Control", "no-cache"); res.getWriter( ).write("Username null"); } } private boolean existsUsername(String username) { ResultSet result null; try { Statement select DatabaseConnector.getConnection().createStatement( result select.executeOuery("SELECT USERNAME from USERS where USERNAME '" + username + "';"); if (result null || result.next( )) { return true;

);

} } catch (SOLException e) { // używamy log 4 j lub obsługujem y to, j a k nam wygodnie } return false; } }

Metoda doGet() przechwytuje żądanie i sprawdza param etr username. Parametr ten jest przesyłany metodzie e x i st s Us e rn ame () , która sprawdza bazę danych i zwraca wartość tr ue (prawda), jeśli użytkow nik o takiej nazw ie już istnieje. W takim przypadku metoda doGet() odeśle użytkow nikow i łańcuch " t r u e " . W przeciwnym razie odeśle łańcuch "Username n u l l " . Nie korzystamy tutaj z kodu XM L, ponieważ przesyłam y jedynie proste łańcuchy. Klasa DatabaseConnector prezentowana na listingu 5.2 jest klasą typu s i ng l e t o n . Jeśli w polu connecti on nie jest aktualnie przechow yw ane żadne połączenie, to program utworzy, za­ chowa i zwróci nowe połączenie. Jeśli natomiast połączenie już jest, program zwróci już istnie­ jące połączenie. Listing 5.2. Klasa DatabaseConnector public class DatabaseConnector { private static Connection connection; public static Connection getConnection( ) { if (connection ! null) return connection; Connection con null; String driver "com.mysql.jdbc.Driver"; try { Class.forName(driver).newInstance( );

58

|

Rozdział 5. Pobieranie potrzebnych danych

} catch (Exception e) { System.out.println("Failed to load mySOL driver."); return null; } try { con DriverManager.getConnection( "jdbc:mysql:///AJAX?user ajax&password polygon"); } catch (Exception e) { e.printStackTrace( ); } connection con; return con; } }

W ten sposób rozw iązaliśm y problem sprawdzania nazw użytkowników. Aplikacja przej­ rzała bazę danych, by upewnić się, że nowa nazw a użytkownika nie koliduje z już istniejącą. Teraz zajmiemy się kodam i pocztowymi. W ykorzystam y bazę danych, by na podstaw ie kodu pocztowego automatycznie wypełnić w formularzu pola m iasta i stanu.

Uzupełnianie miasta i stanu z wykorzystaniem z kodu pocztowego Załadowaliśmy już do naszej bazy danych kody pocztowe dla Stanów Zjednoczonych. Teraz zrobimy u klienta użytek z tych danych. Będziemy musieli raz jeszcze sięgnąć do bazy danych, by sprawdzić popraw ność kodu pocztowego i pobrać odpowiednie informacje na temat miast i stanu. Postąpimy w podobny sposób jak w przypadku sprawdzania nazw y użytkow ­ nika: najpierw zaczekamy, aż klient wprowadzi do odpowiedniego pola pełny kod pocztowy, a gdy kursor opuści to pole, zapełnimy pola miasta i stanu wartościam i odpowiadającym i podanemu kodow i pocztowemu. (Implementowanie tego pola tak, by uzupełniało resztę ko­ du po wpisaniu początku, nie miałoby sensu. Albo użytkownik zna kod pocztowy, albo nie). Najpierw m usimy dodać do pola kodu pocztowego zdarzenie on b lu r JavaScript, które pełnić będzie funkcję spustu. Kod pocztowy:



Nie ma tutaj niczego nowego. Funkcja r e t r i e v e C i t y S t a t e ( ) przywoływana jest, kiedy pole kodu pocztowego utraci fokus (opuści je kursor). Funkcja ta definiuje żądanie XMLHttpRequest i w ysyła je do serwletu A jaxZ ip C o d esServ let: var req; function retrieveCityState( ) { var zip document.getElementById("zipcode"); var url "/ajax-customer-lab5-1/zipcodes?zip " + escape(zip.value); name.value "?"+name.value; if (window.XMLHttpRequest) { req new XMLHttpRequest( ); } else if (window.ActiveXObject) { req new ActiveXObject("Microsoft.XMLHTTP"); }

Wypełnianie form ularza za pomocą Ajaksa

|

59

req.open("Get",url,true); req.onreadystatechange callbackCityState; req.send(null); }

Ponieważ tym razem przygotowujemy większy zestaw funkcji JavaScript, zbierzemy cały kod JavaScript i umieścim y go w osobnym pliku o nazw ie oreillyAJAZ.js, który będzie następnie ładowany przez plik strony index.html: AJAX Customer Sign-up

Servlet sprawdzający kody pocztowe, zaprezentowany na listingu 5.3 jest bardzo prosty. Meto­ da doGet() pobiera z żądania param etr zip. Parametr ten zawiera kod pocztowy w pisany przez użytkownika. Następnie kod pocztowy przesyłany jest metodzie g e t C i t y S t a t e ( ), która sprawdza w bazie danych, jakie miasto i stan odpowiadają temu kodowi. Warto zwrócić uwagę na format w ykorzystyw any do zw racania danych klientowi. Czy to form at XNL, czy JSON? Listing 5.3. AjaxZipCodeServlet public void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { String responseString null; String zipCode req.getParameter("zip"); if (zipCode ! null) { HashMap location getCityState(zipCode); responseString JSONUtil.buildJSON(location, } if (responseString ! null) { res.setContentType("text/xml"); res.setHeader("Cache-Control", "no-cache"); res.getWriter( ).write(responseString); } else { // je ś li p a ra m etr w róci ja k o null, zw racam y znak zapytania. res.setContentType("text/xml"); res.setHeader("Cache-Control", "no-cache"); res.getWriter( ).write("?"); }

"location");

} private HashMap getCityState(String zipCode) { Connection con DatabaseConnector.getConnection( ); HashMap cityStateMap new HashMap( ); cityStateMap.put("zip", "zipCode"); String queryString ""; try { queryString "SELECT CITY, STATE FROM ZIPCODES where ZIPCODE " + zipCode + ";"; Statement select con.createStatement( ); ResultSet result select.executeOuery(queryString); while (result.next( String city; String state;

)) { // przetw arzam y wyniki wiersz p o wierszu

city result.getString("CITY"); if (result.wasNull( )) { city ""; } cityStateMap.put("city", city); state result.getString("state");

60

|

Rozdział 5. Pobieranie potrzebnych danych

if (result.wasNull( )) { state ""; } cityStateMap.put("state",

state); } } catch (Exception e) { System.out.println("exception caught getting city/state:" + queryString + " " + e.getMessage( )); } finally { if (con ! null) { try { con.close( ); } catch (SOLException e) { } } } return cityStateMap; }

Aby odesłać dane klientow i, użyjem y formatu JSON , ponieważ jest prostszy (i dlatego mniej podatny na błędy) niż form at XML. M ając kod pocztowy, odszukujem y w bazie danych od­ powiednie miasto i stan i zachow ujem y je w tablicy asocjacyjnej HashMap. Następnie tablicę tę przesyłam y metodzie buildJSON(). W tym m omencie I t e r a t o r przegląda w pętli wszystkie pozycje tablicy HashMap i konstruuje łańcuch S t r i n g w form acie JSO N na podstaw ie kluczy i odpowiadających im wartości z tablicy HashMap: public static String buildJSON(HashMap map, String title) { StringBuffer returnJSON new StringBuffer("\r\n{\"" + title + "\":{"); String key ""; String value ""; // przeglądam y wszystkie p ozy cje m apow ania w tablicy Iterator it map.entrySet().iterator( ); while (it.hasNext( )) { Map.Entry e (Map.Entry) it.next( ); value (String) e.getValue( ); key (String) e.getKey( ); returnJSON.append("\r\n\"" + key + "\": \"" + value + "\","); } // usuwamy ostatni p rzecin ek int lastCharIndex returnJSON.length( ); returnJSON.deleteCharAt(lastCharIndex - 1); returnJSON.append("\r\n}}"); return returnJSON.toString( ); }

Nie jest to kod, który nadaw ałby się do profesjonalnej (produkcyjnej) aplikacji: w yjątki nie są obsługiwane w zbyt elegancki sposób, podobnie połączenie z bazą danych, nie ma zapisywania inform acji w dzienniku itd. W prawdziwej aplikacji trzeba by było zająć się tym i słabościami. Niemniej mimo to udało nam się w tej aplikacji zaprogram ować bardzo istotny mechanizm. Po raz pierw szy przygotow aliśm y aplikację Ajax, której nie udałoby się zaim plem entować całkowicie po stronie klienta. Aby spraw dzić kod dziesiętny znaku, nie m usieliśm y bow iem łączyć się z serwerem. N atom iast aby sprawdzić, czy klient nie w ybrał nazw y użytkownika zarezerwowanej już przez kogoś innego lub by ustalić, jakie miasto i stan odpowiadają wpi­ sanem u kodowi pocztowem u, musieliśmy skontaktować się z bazą danych na serwerze. Teraz pora zająć się ambitniejszym zadaniem — zbudujem y pole, które sugerow ać będzie adm inistratorowi, jakie nazw y użytkow ników może w nim wpisać.

Wypełnianie form ularza za pomocą Ajaksa

|

61

Tworzenie pola sugerującego nazwy użytkowników Przygotowanie pola sugerującego, jakie wartości można w nim wpisać, jest najciekawszym, ale jednocześnie najtrudniejszym do przygotowania elementem naszej kolejnej aplikacji. Formularz będzie w yglądał tak jak na rysunku 5.2.

O

1 iJ f l J x J

Z a rz ą d z a n ie k lie n ta m i (A J A X ) - M o z illa Fire fo x

Plik

Edycja

Widok

/js/ajaxtags)%>/js/ajaxtags)%>/js/ajaxtags)%>/js/ajaxtags-



Biblioteka AjaxTags - panel z kartami







Jak łatw o zauw ażyć, karta Użytkownicy nie m a atrybutu p aram eters. Pom inąłem go, aby pokazać, że w tym przypadku atrybut p aram eters nie jest potrzebny i że param etry żąda­ nia dla karty m ożna przesłać w atrybucie adresu URL b a seU rl, na przykład w taki sposób: / ta b co n ten t?tab = U sers. Strona index.jsp załącza bibliotekę znaczników ajaxtags.tld oraz dwa pliki JavaScript biblioteki: prototype-1.4.0.js oraz ajaxtags.js. Ponadto pow iązana jest z ważnym plikiem wspomagającym: ajaxtags-sample.css. Ten plik arkusza stylów CSS znaleźć można w naszej przykładowej apli­ kacji AjaxTags. Tworzone karty zależą w znacznej mierze od tych pom ocniczych plików. Aby je zdobyć, należy przejść do strony http://ajaxtags.sourceforge.net/index.html i pobrać z niej pliki Demo War. Pliki CSS można będzie znaleźć w katalogu web/css. (Można je m odyfikować w e­ dle własnego uznania. W ersje, które można pobrać z mojej strony, po prostu są dobrym punktem wyjścia i um ożliwiają naszej aplikacji działanie).

Pisanie kodu serwletu Gdy już utw orzym y stronę JSP dla naszego panelu kart z zakładkami, potrzebow ać będzie­ my serwletu odpowiadającego na żądania Ajax generowane przez poszczególne karty. Od­ powiedź serwletu pow inna mieć formę łańcucha sform atowanego w kodzie HTM L, który zapełni blok każdego ze znaczników. Serw let nasz nazw iem y T a b C o n ten tS erv le t.

124

|

Rozdział 7. Znaczniki Ajax

Serw let T a b C o n te n tS e rv le t m usi zw racać tekst w ybranej karty. Serw let ten pow inien po­ nadto rozszerzać możliwości bazowego serwletu B aseA ja x S erv let i pokrywać metodę getXMLC o n ten t(). (Serwlet B aseA jax S e rv le t można znaleźć w projekcie biblioteki AjaxTags w jej pli­ ku JAR). Serwlet pobiera z żądania parametr tab i wykorzystuje go, by sprawdzić, która z kart żąda przesłania odpowiedniej zawartości. W przypadku tej aplikacji do sterow ania wszystkim w y­ starczy prosty blok i f / e l s e . Kod serwletu T ab C o n ten tS erv le t został przedstaw iony na li­ stingu 7.10. Listing 7.10. Serwlet TabContentServlet package com.oreilly.ajax.servlet; import j a vax.servlet.http.HttpServletRequest; import j a vax.servlet.http.HttpServletResponse; import org.ajaxtags.servlets.BaseAjaxServlet; import com.oreilly.ajax.ProductManager; import com.oreilly.ajax.ShoppingCartManager; import com.oreilly.ajax.UserManager; public class TabContentServlet extends BaseAjaxServlet { public String getXmlContent(HttpServletRequest request, HttpServletResponse response) throws Exception { String tab request.getParameter("tab"); String returnString "Parametr Tab ma wartość null, sprawdź, czy został przesłany w żądaniu."; if (tab null) { return (returnString); } if (tab.equals("Users")) { returnString UserManager.getUsersView( ); } else if (tab.equalsIgnoreCase("Products")) { returnString ProductManager.getProductsView( ); } else if (tab.equalsIgnoreCase("Carts")) { returnString ShoppingCartManager.getShoppingCartView(

);

} return (returnString); } }

W artość parametru tab została zdefiniowana w naszym kodzie JSP jako część żądania URL. Na przykład dla karty Użytkownicy na końcu adresu URL pojaw iać się będzie następujący łańcuch parametru: tab=U sers:

W yświetlanie danych na kartach Kolejnym krokiem jest przesłanie sformatowanych danych z pow rotem do odpowiedniej karty, na której pow inny zostać wyświetlone. Nie chciałbym zwracać sformatowanego łańcu­ cha HTML z serwletu, który nie powinien zajmować się formatowaniem strony. W przypadku

Biblioteki znaczników oferowane przez innych dostawców

|

125

jednak tej aplikacji będzie to najprostsze i najlepsze rozwiązanie. Znacznik panelu z kartami Tab Panel w bibliotece AjaxTags oczekuje, że zawartość kart będzie zwracana w kodzie HTML. Alternatywne rozwiązanie polega na zwracaniu danych w formacie, który będzie mógł być przetw orzony przez kod JavaScript, byłoby to jednak znacznie bardziej skomplikowane. Jeśli m usimy przygotow ać bardziej złożoną aplikację i oddzielić w niej model od szczegółów for­ matowania, można będzie po prostu zwrócić obiekt w postaci łańcucha XM L lub JSON i albo przeanalizow ać go i przygotow ać odpowiednie formatowane HTML w kodzie JavaScript lub też przełączyć się do Struts i skorzystać z pliku bean.tld oferowanego przez bibliotekę Struts provides. Listing 7.11 prezentuje metodę, która zwraca kod HTM L dla karty Użytkownicy. Kod HTML jest dość prosty, ponieważ w iększość form atow ania w ykonyw ana jest przez arkusz stylów CSS. Dlatego też metoda g etU sersV iew () m usi generować jedynie znaczniki tabeli < ta b le> , , < tr> i . W łaściw ości ograniczone są do nazw klas CSS, dzięki czemu m ożem y w y­ konać w iększość formatowania w pliku CSS. Listing 7.11. Wyciąg z pliku UserManager.java static public String getUsersView( ) { Connection con DatabaseConnector.getConnection( ); String sqlString ""; String userclass ""; int index 0; SimpleDateFormat sf new SimpleDateFormat("MM-dd-yyyy"); StringBuffer htmlStringBuffer new StringBuffer(""); htmlStringBuffer.append("\n"); htmlStringBuffer.append("\n"); htmlStringBuffer.append("\n"); htmlStringBuffer.append("\n"); htmlStringBuffer.append("\n"); htmlStringBuffer.append("\n"); htmlStringBuffer.append("\n"); try { sqlString "select * from USERS"; Statement select con.createStatement( ); ResultSet result select.executeOuery(sqlString); Date tempDate null; while (result.next( )) { // przetw arzam y wiersz p o wierszu if (index++ % 2 0) userclass "UserLight"; else userclass "UserDark"; htmlStringBuffer.append("\n"); htmlStringBuffer.append("\n"); htmlStringBuffer.append("\n"); htmlStringBuffer.append("\n"); htmlStringBuffer.append("\n"); htmlStringBuffer.append("\n"); tempDate result.getDate("JOINED"); if (tempDate ! null) htmlStringBuffer.append("\n"); else htmlStringBuffer.append("\n");

126

|

Rozdział 7. Znaczniki Ajax

tempDate result.getDate("LAST LOGIN"); if (tempDate ! null) htmlStringBuffer.append("\n"); else htmlStringBuffer.append("\n"); htmlStringBuffer.append(""); } } catch (Exception e) { System.out.println("wyjątek podczas pobierania użytkowników" + sqlString + " " + e.getMessage( )); } finally { if (con ! null) { try { con.close( ); } catch (SOLException e) { } } } return htmlStringBuffer.toString(

);

}

Metoda g etU sersV iew () tworzy połączenie z bazą danych i w ykonuje zapytanie, by pobrać informacje o użytkownikach. W yniki zapytania są następnie formatowane do postaci łańcucha HTML. Właściwości c la s s każdego z elementów HTML przypisywana jest inna nazwa, dzięki czemu możemy wykonywać większość formatowania w zewnętrznym pliku CSS, bez koniecz­ ności sięgania do kodu Javy. W ten sposób uzyskujemy najlepsze odseparowanie formatowania od kodu możliwe w bibliotece AjaxTags. Danymi dla innych kart można zarządzać w podobny sposób. W idok karty wózka z zakupami jest zarządzany przez metodę g etShop pingC artV iew (), tak jak to zostało pokazane na listin­ gu 7.12. Listing 7.12. Metoda getShoppingCartView() klasy ShoppingCartManager static public String getShoppingCartView( ) { Connection con DatabaseConnector.getConnection( ); String sqlString ""; String cartclass ""; SimpleDateFormat sf new SimpleDateFormat("MM-dd-yyyy"); StringBuffer htmlStringBuffer new StringBuffer("
KlientImięNazwiskoMiastoStanRejestracjaLogowanie
" +result.getString("USERNAME")+"" +result.getString("FIRST NAME")+"" +result.getString("LAST NAME")+"" +result.getString("CITY")+"" +result.getString("STATE")+"" +sf.format(tempDate)+"N/A " +sf.format(tempDate)+"N/A
"); htmlStringBuffer.append("\n"); htmlStringBuffer.append("\n"); htmlStringBuffer.append("\n"); htmlStringBuffer.append("\n"); try { sqlString "select u.USERNAME," + "sc.START DATE," + "sc.LAST UPDATED," + "sc.ACTIVE " + "from SHOPPING CART sc," + "USERS u " + "where sc.USER ID u.USER ID;"; Statement select con.createStatement( ); ResultSet result select.executeOuery(sqlString); int index 0; while (result.next( )) { // przetw arzam y wyniki wiersz p o wierszu if (index++ % 2 0) cartclass "CartLight"; else cartclass "CartDark";

Biblioteki znaczników oferowane przez innych dostawców

|

127

htmlStringBuffer.append("\n"); htmlStringBuffer.append("\n"); htmlStringBuffer.append("\n"); htmlStringBuffer.append("\n"); if (result.getBoolean("ACTIVE")) { htmlStringBuffer.append("\n"); } else { htmlStringBuffer.append("\n"); } htmlStringBuffer.append(""); } } catch (Exception e) { System.out.println("wyjątek podczas pobierania wózka z zakupami" + sqlString + " " + e.getMessage( )); } finally { if (con ! null) { try { con.close( ); } catch (SOLException e) { } } } return htmlStringBuffer.toString(

);

}

Z wyjątkiem zapytania metoda ta jest podobna do metody g etU sersV iew (). Obie metody w y­ korzystują w pętli w hile wartość indeksu, by przypisać każdemu wierszowi inną klasę CSS. Dzięki temu zawartość panelu z kartami staje się bardziej czytelna, ponieważ poszczególne wiersze wyglądają odrobinę inaczej. Najczęściej stosuje się formatowanie polegające na nadaniu co drugiemu w ierszow i szarego tla: if (index++ % 2 0) cartclass "CartLight"; else cartclass "CartDark";

Metoda g etP ro d u ctsV iew () prezentow ana na listingu 7.13 pobiera informacje na temat pro­ duktów z bazy danych i przesyła je do karty Produkty w postaci łańcucha HTML. Listing 7.13. Metoda getProductsView() static public String getProductsView( ) { Connection con DatabaseConnector.getConnection( ); String sqlString ""; StringBuffer htmlStringBuffer new StringBuffer( "
User NameStart DateLast UpdatedActive
" + result.getString("USERNAME") + "" + sf.format(result.getDate("START DATE")) + "" + sf.format(result.getDate("LAST UPDATED")) + "truefalse
"); htmlStringBuffer.append("\n"); htmlStringBuffer.append("\n"); htmlStringBuffer.append("\n"); htmlStringBuffer.append("\n"); try { sqlString "select * from PRODUCTS"; Statement select con.createStatement( ); ResultSet result select.executeOuery(sqlString);

128

I

Rozdział 7. Znaczniki Ajax

Date tempDate null; while (result.next( )) { // przetw arzam y wyniki wiersz p o wierszu htmlStringBuffer.append("\n"); htmlStringBuffer.append("\n"); htmlStringBuffer.append("\n"); htmlStringBuffer.append("\n"); htmlStringBuffer.append("\n"); htmlStringBuffer.append(""); } } catch (Exception e) { System.out.println("wyjątek podczas pobierania produktów" + sqlString + " " + e.getMessage( )); } finally { if (con ! null) { try { con.close( ); } catch (SOLException e) { } } } return htmlStringBuffer.toString(

);

}

Nie ma tu nic nowego ani szczególnie godnego odnotowania. Metoda ta podobna jest do po­ przednich metod getU sersV iew () i getShoppingCartV iew (). Zrezygnowaliśmy jednak z ko­ rzystania ze zmieniających formatowanie znaczników CSS, więc tym razem nie można zmienić tła co drugiego wiersza, by uczynić tabelę na karcie bardziej czytelną. Kod CSS tego przykładu został osadzony w pliku JSP, niemniej później trzeba go będzie umie­ ścić w innym pliku, szczególnie jeśli aplikacja przeznaczona jest do praktycznego użytku:

Nie korzystamy z wartości C a rtL ig h t i CartDark dla stylu T a b le .C a r t, ponieważ kod Javy nie oznacza niczego za pom ocą tych klas. Ostatnim krokiem jest dodanie do pliku web.xml definicji serwletu T a b C o n ten tS erv le t:

tabcontent com.oreilly.ajax.servlet.TabContentServlet

Biblioteki znaczników oferowane przez innych dostawców

|

129

1

tabcontent /tabcontent

Po zaimplementowaniu tego przykładu warto na próbę zmienić dane w bazie i następnie przyj­ rzeć się kartom w panelu, by przekonać się, w jaki sposób zostały zaktualizowane. Jedną ze wspaniałych rzeczy w aplikacjach W W W jest to, że aktualizacja dokona się już w momencie przeglądania kart, bez konieczności przeładowywania stron i buforowania ich w przeglądarce.

Biblioteka JavaWebParts JavaW ebParts to bardzo rozbudow ana biblioteka, zawierająca wiele osobnych komponentów. Najświeższe informacje na temat biblioteki JavaW ebParts można znaleźć w jej dokumentacji, dostępnej pod adresem http://javawebparts.sourceforge.net. Dokumentacja ta jest bardzo obszerna, niemniej daje dobre pojęcie o tym, co biblioteka ta ma nam do zaoferowania. W arto również zaglądać na forum witryny SourceForge: działa na nim aktywnie Frank Zametti, jeden z twór­ ców znaczników z możliwościami Ajax, który chętnie odpowie na wszystkie pytania związane z korzystaniem z tych znaczników. Aby korzystać z biblioteki JavaWebParts, nie jest wymagane pisanie kodu JavaScript. W ystar­ czy po prostu w staw ić odpowiednie znaczniki do kodu JSP, a następnie napisać kod obsłu­ gujący te znaczniki po stronie serwera w Javie. Dla program istów, którzy nie lubią specjalnie kodu JavaScript, rozwiązanie takie będzie miało oczyw iste zalety, niemniej jego w adą jest to, że jesteśm y ograniczeni do tych znaczników, które zostały już zaim plementowane. Tabela 7.2 prezentuje dostępne kom ponenty biblioteki JavaW ebParts. W arto zauważyć, że udostępnia ona w iele różnych komponentów, nie tylko komponenty im plementujące funkcje Ajax. W szystkie w arte są bliższego przyjrzenia się im. Tabela 7.2. Komponenty JavaWebParts Kom ponent

O pis

Filter

Filtry serwletów.

Listener

Komponenty nasłuchujące dla sesji i kontekstu.

Misc

Komponenty, które nie pasują do żadnej innej kategorii.

Taglib

Biblioteka AjaxTags i inne biblioteki znaczników.

Servlet

Serwlety.

Request

Komponenty zajmujące się żądaniami HTTPRequest.

Response

Komponenty zajmujące się odpowiedziami HTTPResponse.

Session

Komponenty zajmujące się sesjami HTTPSession.

Context

Komponenty kontekstu serwletu.

Zamiast przygotowywać bibliotekę komponentów obsługujących funkcje Ajax, biblioteka Java­ WebParts stosuje inne, bardziej uniwersalne rozwiązanie. Zamiast znacznika o ściśle określonej nazwie umieszczamy bardziej ogólny znacznik < a ja x :e v e n t> po elem encie HTM L, który pow inien uruchamiać zdarzenie Ajax. Następnie w iążem y z nim to zdarzenie w pliku konfi­ guracyjnym , który zazwyczaj nosi nazw ę ajax config.xm l.

130

|

Rozdział 7. Znaczniki Ajax

Aby zrozumieć, jak korzysta się z biblioteki JavaWebParts, przyjrzymy się aplikacji wykorzy­ stującej mechanizmy Ajax do zapełnienia jednego pola wyboru ( s e le c t ) w zależności od war­ tości wybranej w innym tego typu polu. Rysunek 7.2 prezentuje aplikację, którą zamierzam y przygotować.

I

) P rz y k ła d ko du A ja x z b ib lio tek i; J a v a W eb P a rts - M o z illa Fire fo x Plik

Edycja

Widok

Przejdź

Zakładki

. !□ ^ ^

Pierwsze kroki ^

Narzędzia

^ jn jx j

Pomoc

|p^| http://localhost:8080/ch07-JavaWebParts/?

^1

®

Idź

\

'



A *

Aktualności

Dynamiczne wybieranie z Ja v a W eb Parts "W y b ierz s ta n , a J a v a W e b P a r t s z a p e łn i drugie p o le w y b o r u o d p o w ie d n im i m iastam i.

| Select a State

zi Cities:

| Wybierz stan w polu powyżej.

w|

Zakończono

Rysunek 7.2. Zapełnianie jednego pola w zależności od wyboru dokonanego w drugim Kod JSP dla tej aplikacji przygotowuje pola wyboru w taki sposób, aby po wybraniu przez użyt­ kownika stanu do serwera wysyłane było odpowiednie żądanie. Serwer pow inien w odpo­ wiedzi zwrócić listę miast w tym stanie. Lista ta zostanie wykorzystana, by zapełnić znacznik , który z kolei zapełni pole wyboru Miasta:. Kod zaczyna się od dyrektywy ta g lib , która ładuje bibliotekę znaczników JavaW ebParts. Nasz kod JSP wykorzystuje tylko dwa znaczniki związane z mechanizmem Ajax: znacznik < a ja x : event>, który umieszczany jest po polu wy­ boru stanu, oraz znacznik < a ja x :e n a b le />. Poza nim i strona JSP (listing 7.14) nie zawiera żadnego dodatkowego kodu, który wskazywałby, że jest to strona W W W korzystająca z m e­ chanizmów Ajax. Listing 7.14. Plik index.jsp



Przykład kodu Ajax z biblioteka Java Web Parts



Dynamiczne wybieranie z Java Web Parts

Biblioteki znaczników oferowane przez innych dostawców

|

131


Wybierz stan, a JavaWebParts zapełni drugie pole wyboru odpowiednimi miastami.





Wybierz stan Alabama Wyoming

Miasta:

Wybierz stan w polu powyżej.



Znacznik < a ja x :e n a b le /> wstawia kod JavaScript, który obsługiwać będzie wywołania Ajax kierowane do serwera. Aby przekonać się, jak w ygląda kod przygotow any przez znacznik < a ja x :e n a b le />, należy otworzyć tę stronę w przeglądarce i w łączyć podgląd kodu źró­ dłowego. Będzie on w yglądać mniej więcej tak:

Plik ajax config.xml zawiera informacje, które tworzą znaczniki w ykorzystywane w kodzie JSP. Atrybut a ja x R e f znacznika formularza określa nazwę nadaw aną formularzowi w kodzie JSP (w tym przypadku "S ta te S e le c tF o r m "):



JWPSelectServlet

132

|

Rozdział 7. Znaczniki Ajax

state stateSelected

cities



W ewnątrz tego formularza znaczniki < a ja x :e v e n t> określają, które z elementów uruchamiać będą zdarzenia Ajax. W pliku konfiguracyjnym znacznik wraz ze swoim atrybutem a ja x R e f określa, którego elementu zdarzenia chcem y mapować (w tym przypadku element s ta te S e le c tio n C h a n g e w ew nątrz elem entu S ta te S e le c tF o r m ). Znacznik < a ja x :e v e n t> w formularzu posiada atrybut a ja x R ef o wartości "S ta te S e le c tF o rm / sta te S e le c tio n C h a n g e ". W cześniej w pliku konfiguracyjnym atrybut type znacznika określał, jakiego ro­ dzaju zdarzeń oczekujem y (w tym przypadku zdarzeń onchange — zmiany). Podsumowując: szukam y zdarzeń onchange zachodzących w elemencie s ta te S e le c tio n C h a n g e wewnątrz elementu S ta te S e le c tF o rm . Kolejne dwa elementy wewnątrz znacznika określają, co należy zrobić, gdy zajdą wspo­ mniane zdarzenia. Znacznik łączy z żądaniem Ajax program je obsługują­ cy (w tym przypadku nasz serwlet). Znacznik ten definiuje również, że kod JSP w ysyłać b ę­ dzie żądanie "g e t" do serwletu JW P S e le ctS e rv le t, który został zdefiniowany w znaczniku < s e r v le t mapping> w pliku web.xml. Aby przesłać dane z pow rotem do serwletu, musimy przesłać parametry żądania. Parametry te definiuje znacznik . W tym przypadku przesyłamy je jako pary klucza i wartości w żądaniu GET. Klucz nazywa się s t a t e , a jego wartość pobrana jest z elementu w kodzie JSP o identyfikatorze id równym s t a te S e le c t e d . Na koniec wreszcie znacznik definiuje kod JSP, który będzie obsługiwał odpowiedź nadchodzącą od serwletu. Serw let przesyła tekst HTM L, który wstaw iany jest do elementu JSP z identyfikatorem id o w artości c i t i e s za pomocą metody innerHTML. Para­ m etr c i t i e s jest obudow yw any znacznikiem . Plik web.xml (zaprezentowany na listingu 7.15) musi zaw ierać znacznik < co n tex t param>, który przypisuje zmiennej ajaxTagsC onfig plik /WEB-INF/ajax config.xml. W pliku web.xml konfigurowana jest również klasa nasłuchująca nadchodzących zdarzeń (klasa A ja x In it ) oraz serwlet, który przetw arzać będzie żądania i następnie zw racać będzie dane do klienta (JW P S e le c tS e rv le t). Listing 7.15. Plik web.xml przygotowujący mechanizm JavaWebParts



ajaxTagsConfig /WEB-INF/ajax config.xml

javawebparts.taglib.ajaxtags.AjaxInit

Biblioteki znaczników oferowane przez innych dostawców

|

133

JWPSelectServlet com.oreilly.ajax.servlet.JWPSelectServlet

JWPSelectServlet /JWPSelectServlet

Pow iązaliśmy kod nasłuchujący i plik konfiguracyjny oraz skonfigurow aliśm y serwlet, który będzie w ysyłał dane z powrotem do znaczników JavaW ebParts. Kod tego serwletu został za­ prezentow any na listingu 7.16. Listing 7.16. Serwlet JWPSelectServlet public class JWPSelectServlet extends HttpServlet { public void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { doPost(req, res); } public void doPost(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { String state (String) req.getParameter("state"); if (state ! null) { res.setContentType("text/xml"); res.setHeader("Cache-Control", "no-cache"); res.getWriter( ).write(getCities(state)); } } private String getCities(String state) { Connection con DatabaseConnector.getConnection( ); StringBuffer sb new StringBuffer(""); try { Statement statement con.createStatement( ); String sqlString "SELECT DISTINCT CITY FROM ZIPCODES WHERE STATE '" + state + "' ORDER BY CITY;"; ResultSet resultSet statement.executeOuery(sqlString); while (resultSet.next( )) { sb.append("" + resultSet.getString(l) + "\n"); } } catch (Exception e) { System.out.println("wyjątek podczas pobierania miast w stanie " + state); } finally { sb.append(" "); if (con ! null) { try { con.close( ); } catch (SOLException e) { } } } return sb.toString( } }

134

I

Rozdział 7. Znaczniki Ajax

);

Serwlet ten tworzy listę miast dla wybranego stanu, obudowuje każdą nazwę miasta wewnątrz znacznika i następnie wysyła w yniki z powrotem do formularza, gdzie są umiesz­ czane w elemencie określonym przez plik konfiguracyjny ajax config. Zamierzałem uatrakcyjnić tę aplikację, pobierając listę miast i stosując na rozwijanej liście odpowiednie zdarzenie. Chciałem, aby po wybraniu miasta zwracane były wszystkie stany, w których znajduje się miasto o tej nazwie. Niemniej wygląda na to, że biblioteka JavaWebParts nie oferuje żadnej techniki umożliwiającej dynamicznie przygotowanemu polu wyboru ( s e l e c t ) uruchamianie kolejnego zdarzenia Ajax. Na stosowanie zdarzeń na dynamicznie wyświetlanym elemencie wyboru ( s e le c t ) pozwala natomiast biblioteka AjaxAnywhere. Zaraz pokażę, jak to zrobić. Biblioteka JavaWebParts oferuje wiele znaczników i funkcji, które możemy dodawać do naszych aplikacji. Została zaprojektow ana w taki sposób, aby być możliwie wszechstronna. Zam iast oferować kom ponenty o ściśle ustalonym działaniu, dostarcza raczej funkcji, które m ożemy wykorzystać w naszych aplikacjach. Jej głów ną w adą jest dość skąpa dokumentacja. Ponadto, jeśli nie chcemy sami współpracować przy tym projekcie, będziemy musieli pogodzić się z nie zawsze najwygodniejszym sposobem działania tych funkcji. Biblioteka JavaW ebParts to ciągle rozwijający się projekt, cały czas poprawiany i udoskonalany. Sądzę, że z czasem stanie się na­ prawdę w artościow ym narzędziem wartym uwzględnienia w przyborniku program isty Ajax.

Biblioteka AjaxAnywhere Projekt AjaxAnywhere jest dostępny w w itrynie SourceForge już od w rześnia 2005 roku. Bi­ blioteka ta oferuje niezależny od przeglądarki interfejs API języka JavaScript um ożliwiający wysyłanie danych do serwera za pośrednictwem żądań XMLHttpRequest. Gdy już dane zostaną wysłane do serwera, biblioteka AjaxAnywhere używa otrzymanej od niego odpowiedzi, by odpowiednio zaktualizować „strefy" (ang. zones) w naszej aplikacji W W W , wykorzystując otrzymane dane. Strefą taką może być dowolny element HTML posiadający identyfikator, taki ja k na przykład elem ent . Na początek należy odwiedzić stronę projektu AjaxAnywhere pod adresem http://ajaxanywhere. sourceforge.net, przyjrzeć się znajdującej się tam dokumentacji i pobrać odpowiednią bibliotekę (plik .jar). Aplikacja działająca po stronie serwera przywołuje statyczną metodę AAUtils.addZonesToR e fr e s h (), by za jej pomocą skonfigurować, które ze stref pow inny zostać zaktualizowane. Metoda ta jest zazwyczaj przywoływana w serwletach lub plikach JSP. Serwlet wysyła dane z powrotem do pliku JSP zaw ierającego strefę lub strefy strony HTM L, które pow inny zostać zaktualizowane. Filtr przechw ytuje odpowiedz i konwertuje ją na kod XM L, który zawierać będzie wyłącznie kod HTM L niezbędny, by zaktualizow ać klienta. Klient odbiera odpowiedz XML i aktualizuje strefę lub strefy w yznaczone do odświeżenia. W tym miejscu przyjrzymy się aplikacji zawierającej dwa elementy wyboru ( s e le c t ) . Pierwszy z nich zapełniany jest listą stanów (rysunek 7.3). Gdy już użytkow nik kliknie w ybrany stan, drugi element s e l e c t zapełniony zostanie listą miast w danym stanie. Następnie, gdy użyt­ kownik kliknie jedno z miast, trzeci obszar zapełniony zostanie listą stanów, w których można znalezć miasto o takiej nazwie. Kod aplikaqi ilustruje, w jaki sposób odszukujemy element, a na­ stępnie jak wykonywać odwrotne przeszukanie otrzymanych wyników. Chciałem w ten sposób

Biblioteki znaczników oferowane przez innych dostawców

|

135

Rysunek 7.3. Przykład z bibliotekę AjaxAnywhere

wybieramy stan, a potem miasto

zademonstrować możliwości biblioteki AjaxAnywhere — w szczególności zdolność do dyna­ micznego wstawiania do kodu wyzwalaczy Ajax — jako że ta funkcja nie jest jeszcze dostępna w omawianej w cześniej bibliotece JavaW ebParts. Kiedy użytkownik klika jeden ze stanów na liście, przeglądarka wysyła odpowiednie żądanie do serwletu AjaxAnyw hereSupportServlet. Serwlet ten określa, które pole zostało wysłane, przywołując metodę r e q u e s t.g e tP a r a m e te r () . Za pom ocą m etody AAUtils.addZonesToR e fr e s h () zostaje skonfigurow ana odpowiednia strefa i dane tej strefy zapisywane są w sesji w postaci odpowiedniego obiektu. Korzystając z tych danych, przeglądarka wyświetla w dru­ gim polu wyboru listę miast w wybranym stanie. Gdy już użytkownik wybierze odpowiednie miasto, przeglądarka w yśw ietli w polu po prawej listę w szystkich stanów, w których znaleźć można miasta o tej nazwie. Powiedziałem w cześniej, że serwlet obsługujący żądania przechow uje dane niezbędne, by zaktualizować strefę w sesji. W jaki sposób w ykonywana jest taka aktualizacja? Aby wykonać odpowiednie odświeżenie strefy, możemy użyć albo kodu Javy, albo JSP, albo też interfejsu API JavaScript biblioteki AjaxAnywhere. W naszym przykładzie skorzystamy z kodu Javy na stro­ nie JSP.

Korzystanie z biblioteki AjaxAnywhere w kodzie JSP Implementacja interfejsu AjaxAnywhere w pliku index.jsp zaprezentowana została na listingu 7.17.

136

|

Rozdział 7. Znaczniki Ajax

Listing 7.17. Implementacja AjaxAnywhere w pliku index.jsp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62



Przykład z użyciem AjaxAnywhere


Kliknij stan. Środkowa lista wyświetli wszystkie miasta w tym stanie.
Następnie kliknij jedno z miast, by przekonać się, w ilu stanach pojawia się miasto o tej samej nazwie.




Product NameDescriptionFilenamePrice
" + result.getString("PRODUCT NAME") + "" + result.getString("DESCRIPTION") + "" + result.getString("FILENAME") + "" + result.getString("PRICE") + "
Stan Miasto Stany z tym miastem




Pierwszy fragm ent kodu pobiera z sesji listę obiektów A rra y L is t (listę miast c i t y L i s t i listę stanów s t a t e L i s t ) . Za pierwszym razem jednak obie listy A rr a y L is ts będą m iały w artość null. Później w ykorzystam y je, by zapełnić różne pola wyboru. Następnie im portujemy bibliotekę JavaScript AjaxAnywhere w pliku aa.js:

Obsługuje ona bibliotekę znaczników AjaxAnywhere, załączając funkcję submitAJAX(). Na­ stępnie inicjujem y zmienną ajaxAnywhere.formName, przypisując jej nazw ę formularza, który zostanie zatw ierdzony (by w ysłać dane) za pom ocą funkcji su b m itA ja x():

Następnie tworzymy wyzwalacz onchange (w razie zmian) kodu, by przywołać funkcję submitA JA X ('fu n c tio n = s ta te '):

Od tej pory, gdy użytkownik zmieni wybrany stan, mechanizm biblioteki AjaxAnywhere będzie zatwierdzać (wysyłać) zawartość form ularza za pośrednictw em żądania XMLHttpRequest, przesyłając w żądaniu param etr fu n c tio n = s ta te . Tak więc żądanie XMLHttpRequest będzie generowane za każdym razem, gdy użytkow nik w ybierze w pierwszej liście jakiś stan. Dalej w naszym kodzie JSP pojaw ia się kolejne w yw ołanie funkcji su b m itA ja x (), tym razem przesyłającej parametr ' f u n c t io n = c it y ':

Prezentowane tu w yw ołanie generować będzie żądanie XMLHttpRequest, gdy użytkownik wybierze któreś z miast w środkowej liście.

13B

I

Rozdział 7. Znaczniki Ajax

Strefy odśw ieżania Aby zrozum ieć, jak działa biblioteka AjaxAnywhere, trzeba przede wszystkim zrozumieć, czym są strefy odświeżania (ang. refresh zones). Strefa odświeżania definiowana jest przez parę znaczników oraz i zawiera definicję obszaru, który mechanizm biblio­ teki AjaxAnywhere będzie aktualizow ał za każdym razem, gdy w spółpracujący z biblioteką AjaxAnywhere serwlet w yśle odpowiedz przeglądarce. (Konfigurowanie serwletu omówimy dokładniej później). Kiedy serwlet odsyła swoją odpowiedź do funkcji su b m itA jax(), w ykonywany jest kod języka Java znajdujący się wewnątrz znacznika , który przeznaczony jest do odświeżenia. (W arto zauważyć, że to jedyny raz, gdy m am y okazję w idzieć, jak kod Javy, a nie JavaScript aktualizuje komponenty HTML). W tym przypadku serwlet przywołuje funkcję AA U tils.addZonesToRefresh( ), informując, że chce aktualizować strefę c i t i e s L i s t . Kiedy serwer zwróci odpowiedź, kod Javy w wierszach 50 - 64 zapełni pole wyboru miasta opcjam i pobranym i z tablicy A rra y L ist przechowywanej w sesji. Podobnie kod w wierszach 71 - 85 zapełnia listę stanów po prawej stronie okna, po tym jak użyt­ kownik w ybierze miasto. Tak jak poprzednio kod ten jest w ykonyw any tylko w tedy, gdy serwlet przywoła funkcję A A U tils.ad d Z o nesT o R efresh (), by spowodować tym razem od­ świeżenie strefy s t a te s w c it y L is t .

Kod wspom agający bibliotekę AjaxAnywhere Teraz, gdy już przyjrzeliśmy się kodowi JSP, który tworzy kod HTM L strony wyświetlanej przez przeglądarkę, pora rzucić okiem na kod serwletu A jaxAnyw hereSupportServlet. Serwlet ten musi zostać napisany przez program istę, by możliwe było korzystanie z kodu biblioteki AjaxAnywhere w tej konkretnej aplikacji. Nie jest to część pakietu AjaxAnywhere. Serwlet AjaxAnywhereSupportServlet (listing 7.18) obsługuje żądanie XMLHttpRequests wysłane przez przeglądarkę. Najpierw, przywołując funkcję A A U tils.isA ja x R e q u e st(), serwlet spraw­ dza, czy żądanie nadeszło z aplikacji opartej na mechanizmie AjaxAnywhere. Jeśli ta metoda zwróci wartość tru e (prawda), wykonywana jest pozostała część kodu serwletu. Jeśli natomiast nie mamy do czynienia z żądaniem AjaxAnywhere, to oczywiście nie ma sensu zajm ować się strefami i resztą m echanizmu AjaxAnywhere. Listing 7.18. Serwlet AjaxAnywhereSupportServlet public class AjaxAnywhereSupportServlet extends HttpServlet { private static final long serialVersionUID 1L; public void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { doPost(req, res); } public void doPost(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { String state req.getParameter("state"); String city req.getParameter("city"); String function req.getParameter("function"); if (AAUtils.isAjaxRequest(req)) { HttpSession session req.getSession( if (function.equals("city")) {

);

Biblioteki znaczników oferowane przez innych dostawców

|

139

AAUtils.addZonesToRefresh(req, "stateswcityList"); session.setAttribute("stateList", getStates(city)); } else if (function.equals("state")) { AAUtils.addZonesToRefresh(req, "citiesList"); session.setAttribute("cityList", getCities(state)); } } String url "/index.jsp"; ServletContext sc getServletContext( ); RequestDispatcher rd sc.getRequestDispatcher(url); rd.forward(req, res); } }

Jeśli funkqa A A U tils.isA jaxR equ est() zwróci wartość tru e, serwer musi zdecydować, czy zak­ tualizować listę stanów, czy listę miast, w zależności od w artości parametru fu n c tio n , który wywołanie su bm itA jax() dodało do adresu URL żądania. Jeśli parametr fu n c tio n będzie miał wartość " c i t y " , to znaczy, że serwer powinien przesłać listę stanów, w których można znaleźć miasto przesłane w żądaniu. W tym przypadku serwlet najpierw przywołuje funkcję AAU tils. addZonesToRefresh(), by poinformować, że aktualizuje strefę s ta te s w c ity L is t. Następnie przyw ołuje metodę g e t S t a t e s ( ) , która zwraca listę A rra y L is t stanów zaw ierających miasto 0 podanej nazwie. Lista ta jest następnie dodawana do sesji pod atrybutem s t a t e L i s t . M e­ chanizm jest tu podobny jak w sytuacji, gdy żądamy listy miast (tj. jeśli param etr fu n c tio n będzie m iał w artość " s t a t e " ) : serwlet informuje, że chce zaktualizować strefę c i t i e s L i s t 1 przyw ołuje funkcję g e t C i t i e s ( ) , by odnaleźć listę miast w podanym stanie. Następnie lista A rra y L ist dodawana jest do sesji pod atrybutem c i t y L i s t . Dlaczego po prostu nie sprawdzać, czy param etry c i t y lub s t a t e nie mają w artości n u ll? Na pozór jest to całkiem logiczne rozwiązanie, niemniej w praktyce w yw oła błąd. Jeśli przyj­ rzymy się kodowi JSP z listingu 7.17, zobaczymy, że obie strefy znajdują się w tym samym formularzu. Oznacza to, że żądanie zawsze będzie posiadać param etry c i t y i s t a t e . Z tego powodu trudno jest używać tych parametrów w celu ustalenia, który z nich rzeczywiście pole­ ca odświeżyć odpowiadającą mu strefę. Gdy już w ykonam y całą ciężką pracę, serwlet pobierze kontekst S e r v le tC o n te x t i następnie wykorzysta ten kontekst do pobrania R equ estD isp atch er, który z kolei w ykorzysta do w y­ słania odpowiedzi z powrotem do pliku index.jsp. Serwlet AjaxAnyw hereSupportServlet posiada jeszcze dwie inne metody, które ułatwiają mu pobieranie danych miast i stanów. Metoda g e t C it ie s ( ) prezentowana na listingu 7.19 zwraca zbiór łańcuchów zawierających wszystkie miasta w danym stanie. Listing 7.19. Metoda getCities() private Collection getCities(String state) { ArrayList cityList new ArrayList( ); Connection con DatabaseConnector.getConnection( ); try { Statement statement con.createStatement( ); String sqlString "SELECT DISTINCT CITY FROM ZIPCODES WHERE STATE '" + state + "' ORDER BY CITY;"; ResultSet resultSet statement.executeOuery(sqlString); while (resultSet.next( )) {

140

I

Rozdział 7. Znaczniki Ajax

cityList.add(resultSet.getString(1));

} } catch(Exception e) { System.out.println("wyjątek podczas pobierania miast dla stanu " + state); } finally { if (con ! null) { try { con.close( ); } catch(SOLException e) { } } } return cityList; }

Druga metoda potrzebna serwletowi A jaxA nyw hereSupportServlet to m etoda g e t S t a t e s ( ) prezentowana na listingu 7.20. Metoda ta zwraca zbiór łańcuchów zawierających nazwy wszyst­ kich stanów, w których odnaleziono miasto o podanej nazwie. Listing 7.20. Metoda getStates() private Collection getStates(String city) { ArrayList stateList new ArrayList( ); Connection con DatabaseConnector.getConnection( ); try { Statement statement con.createStatement( ); String sqlString "SELECT DISTINCT STATE FROM ZIPCODES where CITY + city + "' ORDER BY STATE;"; ResultSet resultSet statement.executeOuery(sqlString); while (resultSet.next( )) { stateList.add(resultSet.getString(1));

'"

} } catch(Exception e) { S y s t e m . o u t.println("wyjątek w trakcie pobierania stanów z tabeli kodów p o c z t o w y c h " ); } finally { if (con ! null) { try { con.close( ); } catch(SOLException e) { } } } return stateList; }

Filtr AjaxAnywhere Ostatnia rzecz, którą musimy zrobić, zanim nasza aplikacja będzie gotowa do pracy, jest skon­ figurowanie filtra AjaxAnywhere. Filtr ten formatuje kod nadchodzący od serwletu, by urucho­ mić aktualizację odpowiednich stref. Kiedy zaczynałem pisać tę część program u, zapomnia­ łem dodać filtr mapujący dla serwletu i chwilę zajęło mi ustalenie, dlaczego aplikacja nie działa. M orał z tego taki, że trzeba upewnić się, czy m am y w naszym pliku web.xml prawidłowo skonfigurowane m apowanie filtra!

Biblioteki znaczników oferowane przez innych dostawców

|

141

Plik web.xml prezentow any na listingu 7.21 zawiera przykłady różnych mapow ań filtrów, które można stosow ać w bibliotece AjaxAnywhere. Tak napraw dę potrzebujem y jednak tylko mapowania filtra, które mapuje bibliotekę AjaxAnywhere na /AjaxAnywhereSupport. Pozostałe m apowania pozostaw iłem w yłącznie w celach poglądowych. Listing 7.21. Plik web.xml i mapowania filtrów dla biblioteki AjaxAnywhere

AjaxAnywhereSupportServlet

com.oreilly.ajax.servlet.AjaxAnywhereSupportServlet

AjaxAnywhereSupportServlet /AjaxAnywhereSupport

AjaxAnywhere org.ajaxanywhere.AAFilter

AjaxAnywhere /AjaxAnywhereSupport

AjaxAnywhere *.jsp

AjaxAnywhere *.do

index.jsp

Biblioteka znaczników AjaxAnywhere jest bardzo poręcznym narzędziem, gdy zachodzi ko­ nieczność budowania złożonych aplikacji Ajax. Nie należy oczywiście oczekiwać, że znajdzie­ my tam już gotowe aplikacje Ajax, niemniej oferuje ona bardzo użyteczne techniki dodawa­ nia m echanizmów Ajax do już istniejących lub nowych aplikacji i to nie w ymagając ze strony program isty żadnej znajomości kodu JavaScript.

Z której biblioteki znaczników najlepiej korzystać? Dobre pytanie. Próbowałem omówić w tym rozdziale główne z dostępnych bibliotek Ajax. Żadna z nich nie jest oczywistym zw ycięzcą w rywalizacji o tytuł najlepszej biblioteki Ajax. Każda nadaw ać się będzie najlepiej do trochę innych celów.

142

I

Rozdział T. Znaczniki Ajax

Jeśli stwierdzimy, że przydałby się w naszej aplikacji komponent taki jak panel z kartami ofe­ rowany przez bibliotekę AjaxTags, to należy użyć w projekcie w łaśnie tej biblioteki. Jeśli na­ tomiast bardziej odpowiada nam biblioteka JavaW ebParts, należy użyć JavaW ebParts. Jeśli natom iast nie potrzebujem y żadnego określonego kom ponentu, lecz chcielibyśm y m ieć swo­ bodę pisania kodu korzystającego z technik Ajax bez konieczności używania kodu JavaScript, to najlepiej sięgnąć po bibliotekę AjaxAnywhere. Ogólne zalecenie jest takie, żeby korzystać z tej biblioteki, która najlepiej pasować będzie do naszych potrzeb. Teraz gdy już wiemy, jakie są możliwości każdej z bibliotek, mamy możliw ość dokonania najlepszego wyboru. Oczywiście jeśli chcemy użyć kodu naszych znaczników ponownie i nie jesteśmy w stanie zna­ leźć biblioteki, która będzie się najlepiej spraw dzać w naszym przypadku, to zawsze m ożemy napisać w łasną bibliotekę znaczników. W takim jednak przypadku dobrze jest przyłączyć się do któregoś z już istniejących projektów , żeby również reszta społeczności programistycznej mogła skorzystać z owoców naszej pracy. W yrażę chyba nie tylko moje zdanie, jeśli powiem, że to, czego potrzeba społeczności programistów Javy, to więcej w spółpracy, mniej bibliotek, a za to więcej funkcji w każdej z bibliotek. Dzięki temu będziemy mogli mniej czasu poświęcać na uczenie się korzystania z kolejnych bibliotek, a więcej na im plementow anie oferowanych przez nie funkcji w naszym kodzie.

Biblioteki znaczników oferowane przez innych dostawców

| 143

144

|

R o z d zia ł 7. Zn aczn iki A jax

ROZDZIAŁ 8.

Ajax i Struts

Struts to obecnie jeden z najbardziej dojrzałych i najczęściej w ykorzystyw anych szkieletów programowania typu M VC (ang. M odel View Controller), przeznaczony do tworzenia aplikacji W W W w Javie. Stąd też nic dziwnego, że wielu program istów zadaje sobie pytanie: W jaki sposób dodać do aplikacji Struts funkcje Ajax? Jak łatwo się domyślić, istnieje kilka dobrych odpowiedzi na to pytanie. W niniejszym rozdziale przyjrzym y się dwóm podejściom do tego problemu. Pierwsze z nich polega na użyciu biblioteki Struts-Layout, która im plementuje pewne funkcje Ajax. Drugie natom iast na korzystaniu w aplikacjach Struts z biblioteki DWR (omawianej w rozdziale 6.). Należy jednak zastrzec, że rozdział ten nie ma być ani wprow adzeniem, ani przewodnikiem po Struts. Czytelników zainteresowanych Struts odsyłam więc do książek Programming Jakarta Struts Chucka Cavanessa i Jakarta Struts Cookbook Billa Siggelkowa (obie wyszły nakładem wy­ dawnictwa O'Reilly). Ponadto w arto oczywiście zaznajomić się z dokumentacją Struts do­ stępną pod adresem http://struts.apache.org.

Biblioteka Struts-Layout Struts-Layout (http://struts.application-servers.com) to biblioteka znaczników oferująca zestaw już gotowych do użycia komponentów Struts. Dla naszych celów najbardziej użyteczne będą pola sugerujące. M imo iż biblioteka Struts-Layout oferuje również inne znaczniki (służące do tworzenia paneli, pól wprowadzania danych, tabel, widoków drzewa, siatek danych, wyska­ kujących okienek, kalendarzy i wielu innych elementów), pole sugerujące jest jedynym ele­ mentem, który w zbogacony został w funkcje Ajax, i zostało zaim plem entow ane tak dobrze, że w arte jest omówienia. Również inne znaczniki biblioteki Struts-Layout są w arte uwagi, niemniej zapoznanie się z nimi pozostawiam już Czytelnikowi. Twórcy biblioteki Struts-Layout postarali się, by program ista korzystający z niej mógł tworzyć strony W W W naw et bez zna­ jom ości kodu HTML. Było to napraw dę trudne zadanie, niemniej tw órcy Struts-Layout w y­ konali napraw dę kaw ał dobrej roboty, by obudow ać różne użyteczne funkcje w tejże b i­ bliotece znaczników. Rysunek 8.1 pokazuje prostą aplikację wykorzystującą pole sugerujące oferowane przez Struts-Layout. N aw et ten prosty przykład daje nam pojęcie o m ożliwościach znaczników Struts-Layout. W idok prezentowany na stronie tworzony jest z wykorzystaniem wyłącznie czterech znaczników Struts-Layout: < lay o u t:h tm l> , < lay o u t:fo rm > , < la y o u t:su g g e st> i < layou t:su bm it> (opowiem o nich więcej za chwilę). Aby więc przygotować pole sugerujące, nie m usimy pisać żadnego kodu JavaScript i tylko minimalną ilość HTML. 145

_=!SJ_xJ Plik

Edycja

Widok

wykorzystuje do zapełniania listy sugerowanych nazw użytkowników. Oczywiście można by było tę aplikację jeszcze poprawie: na przykład m oglibyśmy użyć w y­ wołania Ajax, by przesłać dane do akcji U serA ction, a następnie zapełnić formularz innymi inform acjami, które można uzyskać podając nazwę użytkownika. Pozostawiam to jednak ja ­ ko ćwiczenie dla Czytelników. Kiedy formularz zostanie już zatw ierdzony (dane zostaną wysłane), Struts przyw ołuje akcję U serA ction, która pobiera nazwę użytkownika z odpowiedniego formularza Struts (User Form), a następnie przyw ołuje metodę g e tU s e r In fo (). Z kolei metoda g e tU s e r In fo () od­ szukuje w bazie danych im ię i nazwisko wybranego użytkownika. Odnalezione w artości są przypisyw ane odpowiednio zmiennym lastName i firstN am e dla potrzeb następnej strony JSP, która będzie z nich korzystać: ((UserForm)form).setFirstName(firstName); ((UserForm)form).setLastName(lastName);

Kiedy ponownie przyw ołam y plik results.jsp, będziem y mieli gotowy obiekt UserForm zapeł­ niony danymi przeznaczonym i dla pola username.

Zapełnianie listy sugerowanych nazw użytkowników Klasa U sernam eSuggestA ction obsługuje w yw ołanie Ajax zw iązane ze znacznikiem suge­ rującym nazw y użytkow ników < la y o u t: su gg est> . Klasa ta m usi być rozszerzeniem klasy f r .im p r o v e .s t r u t s .t a g lib .la y o u t .s u g g e s t .S u g g e s t A c t io n oraz pow inna im plem ento­ w ać metodę zw racającą kolekcję C o lle c t io n . Zawartość tej kolekcji C o lle c tio n zostanie w ykorzystana, by wyśw ietlić sugerowane nazw y użytkow ników w polu < la y o u t:su g g e st> . Lista tych sugestii pow inna być przechow yw ana jako tablica A rra y L ist typu S tr in g . M etoda w ykorzystyw ana, by pobrać kolekcję C o lle c tio n , definiowana jest przez param etr su gg estA ctio n znacznika < la y o u t:su g g e st> . Wcześniej w pliku usermanager.jsp przypisali­ śm y temu param etrow i wartość "/ g e tS u g g e s tio n L is t", która odpowiada nazw ie metody wykorzystywanej w akcji Usernam eSuggestAction (listing 8.8).

Biblioteka Struts-Layout

|

155

Listing 8.8. Klasa UsernameSuggestAction public class UsernameSuggestAction extends SuggestAction { public Collection getSuggestionList(HttpServletRequest in request, String in word) { Collection usernames getUserSuggestions( ); ArrayList suggestions new ArrayList( ); if (in word ! null && in word.length( ) > 0) { Iterator iter usernames.iterator( ); while(iter.hasNext( )) { String currentWord (String) iter.next(

);

if (currentWord.toLowerCase().startsWith(in word.toLowerCase( suggestions.add(currentWord);

)))

} } return suggestions; } private static Collection getUserSuggestions(

) {

ArrayList arrayList null; Connection con DatabaseConnector.getConnection( ); try { Statement statement con.createStatement( ); String sqlString "select username from users;"; ResultSet resultSet statement.executeOuery(sqlString); arrayList new ArrayList( ); while (resultSet.next( )) { arrayList.add(resultSet.getString(1)); } } catch (Exception e) { System.out.println("wyjątek podczas pobierania nazw użytkowników"); } finally { if (con ! null) { try { con.close( ); } catch (SOLException e) { } } } return arrayList; } }

W w iększości przypadków w artości prezentowane na liście sugerowanych uzupełnień po­ chodzić będą z jakiejś składnicy danych (np. bazy danych). Tak też jest w naszym przykła­ dzie: lista zapełniana jest poprzez przyw ołanie prywatnej m etody g e tU se rS u g g e s tio n s(). W spom niana metoda g e tS u g g e s tio n L is t() jest przeróbką metody demonstrującej działanie znacznika < la y o u t:su g g e st> z przykładu prezentow anego w w itrynie biblioteki.

156

|

Rozdział 8. Ajax i Struts

Dlaczego warto korzystać z biblioteki Struts-Layout? Jak mieliśmy właśnie okazję się przekonać, przygotowywanie pola sugerującego możliwe uzu­ pełnienia jest dziecinnie proste, gdy korzystamy z pakietu Struts-Layout. Jest to jeden z kilku dostępnych obecnie szkieletów program owania Ajax, który nie w ym aga od nas praktycznie żadnego program owania Ajax: nie musieliśmy w ogóle pisać kodu JavaScript. Niestety obec­ nie oferuje on tylko jed en znacznik posiadający funkcje Ajax, jestem jed nak przekonany, że w przyszłości biblioteka będzie oferowała znacznie więcej znaczników z funkcjami Ajax. Warto ją uważnie obserwować, ponieważ kryje w sobie bardzo duży potencjał.

Implementowanie funkcji Ajax w Struts z użyciem biblioteki DWR Dodawanie mechanizmów Ajax do aplikacji Struts nie w ym aga jakiejś szczególnej wiedzy. Gdy już nauczym y się, w jaki sposób im plementować funkcje Ajax w zwykłej aplikacji WWW, przekonamy się, że dodawanie mechanizmów Ajax do aplikacji Struts przebiega według tych samych reguł: mamy plik JSP, który albo przyw ołuje specjalistyczną bibliotekę oferującą funkcje Ajax taką jak Prototype lub DW R, albo też zawiera własne im plem entacje funkcji Ajax napisane w kodzie JavaScript. Korzystanie z już gotowej biblioteki takiej jak DWR jest bardziej efektywnym rozwiązaniem niż pisanie własnego kodu Ajax, ponieważ kod oferowany przez gotowe biblioteki został już zazwyczaj przetestowany pod kątem współpracy z różnymi przeglądarkami i często obsługuje również te z przeglądarek, których nie mieliśmy okazji sami testować. W rozdziale 6. skorzystaliśmy z biblioteki Scriptaculous, żeby zbudować aplikację dla sklepu internetowego. Aplikacja ta nie oferow ała jednak żadnego sposobu dodawania now ych pro­ duktów. Teraz przyjrzym y się, w jaki sposób, wykorzystując w Struts bibliotekę DW R, moż­ na dodawać now e produkty do tabeli PRODUCTS w naszej bazie danych. Rysunek 8.4 pokazuje, jak będzie w yglądać strona zarządzania produktam i, gdy już zaim­ plem entujemy cały kod przykładu prezentowany w tej części rozdziału. Biblioteka DWR, którą omawialiśmy już w rozdziale 6., jest dobrze dostosowana do współpra­ cy ze Struts. Ponadto, ponieważ automatycznie generuje większość kodu JavaScript niezbędne­ go do działania mechanizmów Ajax, korzystanie z biblioteki DW R pozw ala ograniczyć ilość kodu JavaScript, który będziem y mogli napisać. Przyjrzyjmy się tabelom bazy danych, z których korzystaliśmy w naszej aplikacji wózka z za­ kupami, prezentowanej w rozdziale 6. Tabele te zostały zaprezentow ane na rysunku 8.5. Rysunek 8.5 przygotowałem przy użyciu sterownika ODBC bazy MySQL. Warto za uważyć, że nazwy tabel zawierają wyłącznie małe litery (np. products). Tak właśnie tabele będą wyglądały, jeśli będziemy uruchamiać bazę MySQL w systemie Microsoft Windows: nazwy tabel zostaną przekształcone tak, aby zawierały wyłącznie małe litery, natomiast w odwołaniach do kolumn w zapytaniach wielkość liter będzie nie istotna.

Implementowanie funkcji Ajax w Struts z użyciem biblioteki DWR

|

157

.J Q JX J

t ) A l AX D W R P r o d u c t M a n a g e r - M o rilla F ire fo x Pjlk

Id y c ja

1 0 / c h 0 9 -S < rJtí-D W R to to * > l< r

©

IdS

\^„

%

P»erwMe krota ta i Aktualności

Zarządzanie produktami (Struts i DW R)

W

Opis

I Nazwa produktu

I

(guitar

iFernandei guitar

[chessboard

|

Cena

I Obraz I ]



H

jE s te s Park lainis*

(iS |l6 99

K'

■a |fi' . .

|Es»i Park Home!

Browse

j ; 1

#

|Home-made C h essbo t |99 99

flaíññes

Zmiana obrazka

■PPM |«0 « -

1

il

|575000 0

Brew s«

I

Browse

J

Brew s«

I

Rysunek 8.4. Widok okna zarządzania produktami

u sers PK

U SER ID USERf-lAUE PASSW ORD FIRST NAME LAST N A tfE E M IT ADDRESS ZIPCODE CITY STATE JOINED LAST LOGIN

s h o p p i n g ^ rt

p ro d u cts PK

PRODUCT ID PRODUCT N A IE DESCRIPTION FILEMAV.E PRICE

PK

¡tems_¡n_cart

CARTID USER ID S T A R f DATE LAST UPDATE U ACTIVE

zip co d ss PK

ITEMID tARriD 0UNT

Z IN O B E CITY STATE

Rysunek 8.5. Tabele bazy danych dla aplikacji wózka z zakupami Napisaliśmy już kod obsługujący wszystkie tabele poza tabelą PRODUCTS. Aby dodać do tej ta­ beli now y produkt, obecnie musimy go dodawać ręcznie, a następnie kopiow ać obrazek pro­ duktu (plik PNG) do odpowiedniego katalogu. Aplikacja, którą teraz przygotujem y, ułatwi nam dodawanie i modyfikowanie produktów. Jeśli chodzi o mechanizmy Ajax, nie ma istotnej różnicy między aplikacją Struts a standardową aplikacją składającą się z serwletu i stron JSP. W obu przypadkach kod JavaScript w w ywoła­ niach na stronie JSP przyw ołuje metody zdefiniowane w modelu. Akcje Struts mogą odpo­ w iadać na w yw ołania Ajax, niemniej korzystanie z nich nie jest konieczne, a tylko zalecane. W tej aplikacji akqe Struts nie będą odpowiadać na wywołania Ajax — skorzystamy z biblioteki DW R, by pow iązać naszego menedżera produktów bezpośrednio z w yw ołaniam i funkcji wykonyw anym i w kodzie JavaScript.

158

I

Rozdział 8. Ajax i Struts

Strona JSP tej aplikacji wyświetlać będzie listę istniejących produktów, jednocześnie umożliwiając użytkow nikow i dodaw anie produktów do bazy danych. Lista produktów ujęta jest w blok . Blok ten będzie dynamicznie aktualizowany za każdym razem, gdy dodawać będziemy now y produkt. Rysunek 8.6 ilustruje sekwencję operacji, które powodują dodanie produktu do bazy danych. Program zarządzający produktam i za pośrednictw em przeglądarki odwołuje się do aplikacji menedżera produktów ProductManager. Przeglądarka w ysyła żądanie, aby uzyskać ścieżkę do obrazka produktu. Będzie to oczyw iście ścieżka do katalogu (na serwerze), w którym przechow yw any jest obrazek. Następnie przeglądarka w ystosow uje żądanie do w arstwy DW R, by pobrać listę produktów. Biblioteka DW R w ykonuje szeregowanie żądania, przesy­ łając je do odpowiedniej instancji klasy ProductManager Javy, która zwraca listę produktów.

Rysunek 8.6. Schemat działania opartej na Struts aplikacji Ajax zarządzającej produktami Kolejny etap polega na wykonaniu odpowiedniego polecenia C reate, Update lub D elete, w za­ leżności od tego, co użytkownik chce zrobić z produktem (utworzyć, zaktualizować czy usunąć). Każde z tych poleceń przesyłane jest do m enedżera produktów ProductManager za pośred­ nictwem biblioteki DWR.

Implementowanie funkcji Ajax w Struts z użyciem biblioteki DWR

|

159

Ostatnie polecenie to ładujące obrazek polecenie UploadImage. Ze względów bezpieczeństw a n ie je st to w yw ołanie Ajax — kod JavaScript nie m oże sięgać do plików w system ie plików serw era. D latego aby um ieścić na serw erze now e obrazki, kod JSP m usi przyw ołać akcję UploadAction Struts.

Ładowanie plików na serwer Szkielet Struts w ykorzystujem y tutaj tylko po to, by inicjować lokalizacje plików z obrazkami produktów i by załadować te obrazki na serwer. Formularz Struts, który przesyła dane ob­ razka do ładującej ten obrazek akcji UploadAction, nosi nazwę UploadForm (listing 8.9). For­ mularz ten wykorzystuje specjalną klasę Struts o nazw ie Form File, która umożliwia formula­ rzom Struts przechowywanie plików. Plikiem tym będzie można później zarządzać za pomocą odpowiedniej akcji (w naszym przypadku akcji UploadAction). Listing 8.9. Plik UploadForm.java import org.apache.struts.action.*; import org.apache.struts.upload.FormFile; public class UploadForm extends ActionForm { private FormFile uploadFile; private int productId; public int getProductId( ) { return productId; } public void setProductId(int productId) { this.productId productId; } public FormFile getUploadFile( ) { return uploadFile; } public void setUploadFile(FormFile uploadFile) this.uploadFile uploadFile;

{

} }

Formularz UploadForm jest prostym ziarnem Javy, które transferuje dane pomiędzy formula­ rzem HTM L a akcją UploadAction (listing 8.10) w ykonującą całą pracę związaną z ładow a­ niem pliku na serwer. Tak jak poprzednio, akcja UploadAction nie może używać mechani­ zmów Ajax, ponieważ JavaScript nie ma dostępu do systemu plików na serwerze. Musimy skorzystać ze znacznika w kodzie JSP i pozwolić, by odszukiwaniem plików zajmował się odpowiedni znacznik HTML. Listing 8.10. Akcja UploadAction public class UploadAction extends Action { static String productsDirectory null; private String getFilePath( ) throws FileNotFoundException, IOException { String resourceFilepath this.getServlet().getServletContext( ).getRealPath( "/shopping.properties"); Properties configs new Properties( ); configs.load(new FileInputStream(resourceFilepath)); String dir this.getServlet().getServletContext( ).getRealPath("")+ "/"+configs.getProperty("products.directory"); return dir; }

160

|

Rozdział 8. Ajax i Struts

public ActionForward execute( ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { UploadForm myForm (UploadForm)form; int productId myForm.getProductId( ); // przetw arzam y form u larz F orm F ile FormFile myFile myForm.getUploadFile( ); String fileName myFile.getFileName( ); byte[] fileData myFile.getFileData( ); FileOutputStream out

new FileOutputStream(getFilePath(

)+"/"+fileName);

out.write(fileData); out.close( ); ProductManager.updateProductById(productId,"FILENAME",fileName); return mapping.findForward("success"); }

Mimo iż nie możemy używ ać mechanizmów Ajax podczas ładowania obrazków na serwer, ten m ankam ent technologii Ajax praktycznie nie ma znaczenia, jeśli uwzględnim y inne zalety technologii Ajax. Na przykład jednym z najczęstszych zadań, które wykonyw ać będzie nasz program zarządzający produktam i, jest zmiana ceny produktu. Po zastosowaniu technologii Ajax operacja ta staje się dziecinnie łatwa: w ystarczy po prostu edytow ać pole ceny PRICE, a następnie przejść do kolejnego pola, co automatycznie uruchomi zdarzenie Ja v a S c rip t:o n change i w prow adzi odpowiednią aktualizację w bazie danych.

Tworzenie strony JSP Teraz gdy już napisaliśm y kod naszych akcji, pora przejść do ciekawszej części przykładu. Strona productmanager.jsp (listing 8.11) na początek załącza kilka plików JavaScript, z których tylko jeden (oreillyProductManager.js) jest odpowiedzialny za zapisywanie danych. Kolejny plik, dwr/interface/ProductManager.js, jest generowany automatycznie przez bibliotekę DWR. Pozo­ stałe dwa pliki (engine.js i util.js) to biblioteki w spom agające DWR. Listing 8.11. Strona productmanager.jsp

AJAX DWR Product Manager





Zarządzanie produktami (Struts i DWR)






Funkcja p o p u lateD ata() przyw oływ ana jest w m omencie pierw szego ładowania strony. Znajduje się na niej przycisk (Dodaj produkt) um ożliw iający dodawanie nowych produktów. Jest tam również blok (id = "p r o d u c ts " ), który zapełnim y listą produktów.

Pisanie pliku konfiguracyjnego DWR Teraz pora zająć się plikiem konfiguracyjnym DWR, dwr.xml (listing 8.12). Tworzenie tego pliku jest tak proste, że aż ekscytujące. (Kto z nas pam ięta, kiedy ostatnio ekscytowało go tw orze­ nie pliku konfiguracyjnego?). Kod JavaScript generow any przez bibliotekę DW R definiowany jest wewnątrz znacznika . Znacznik < crea te> definiuje nazwę pliku JavaScript, który ma zostać wygenerowany (ProductM anager.js) i załączony do kodu JSP. Plik ten powiązany jest z rzeczywistą klasą Javy, która odpowiadać będzie na żądania Ajax. Listing 8.12. Plik konfiguracyjny DWR, dwr.xml









Znacznik < create> definiuje kod JavaScript wspomagający daną klasą Javy. W tym przypadku biblioteka DW R tworzy obiekt ProductManager języka JavaScript, który obsługiwać będzie klasę ProductM anager( c o m .o r e i l l y .a ja x . ProductManager). Znaczniki definiują metody Javy obsługiwane przez kod JavaScript, który w yge­ nerowała biblioteka DWR. Znacznik tworzy z obiektu c o m .o r e illy .a ja x .P r o d u c t ziarno JavaScript, wyko­ rzystując w artości przesłane w znaczniku . W ten sposób um ożliw iam y aplikacji po­ bieranie danych od mechanizmu DW R i wyśw ietlanie ich w przeglądarce.

Wyświetlanie produktów w przeglądarce Nadal pozostało nam napisanie kodu JavaScript, który zapełni blok na stronie productmanager.jsp. Ten kod JavaScript zostanie załączony na początku pliku productm anager.jsp, a przechow yw any będzie w pliku oreillyProductM anager.js.

162

|

Rozdział 8. Ajax i Struts

Najbardziej skomplikowanym elementem w tym kodzie JavaScript jest pętla zapełniająca tabelę produktów. Jeśli tabela produktów zostałaby dodana bezpośrednio do zmiennej innerHTML w bloku , to nasza tabela mogłaby nie działać prawidłowo w niektórych przeglądarkach. Bezpieczniejszym, nie tak uzależnionym od typu przeglądarki sposobem na zapełnienie bloku jest zbudowanie osobnego łańcucha, który zawierać będzie cały kod HTM L tworzący tabelę. Gdy już tabela zostanie zbudowana, będziem y mogli następnie przypisać zmiennej innerHTML łańcuch zawierający pojedynczą instrukcję JavaScript. To właśnie, jak łatwo się przekonać przeglądając listing 8.13, robim y w funkcji u p d a te P ro d u c tL is t(). Listing S.13. Plik oreillyProductManager.js function newProduct( ) { ProductManager.addProduct(populateData) } function populateData( ) { ProductManager.getJSONProducts(updateProductList) ; } function updateProductList(jsonData) { var myJSONObject eval('(' + jsonData + ')'); productsdiv document.getElementById('products'); var output ""+ ""+ ""+ ""+ ""+ ""+ ""; for (i 0;i"; output + "

Nasza aplikacja została już prawie przygotowana do wykonywania wywołań Ajax. M amy plik TLD, klasę obsługującą znacznik oraz renderer. Brakuje nam jedynie pliku ora-ajax.js. Przy­ gotujem y go za chwilę.

Pisanie wspomagającego pliku JavaScript Zawartość pliku ora-ajax.js wstawiana jest do kodu HTML wygenerowanego przez naszą stronę JSP lub przez znacznik < ajax:zip C o d e> . Plik ten został zaprezentow any w listingu 9.8. Listing 9.8. ora-ajax.js function retrieveCityState( ) { var zip document.getElementById("zipcodeId"); // ad res url musi m ieć rozszerzenie .faces, by został zaakceptow any

Tworzenie w łasnego znacznika JSF

| 183

var url "ZipCode-Ajax.faces?zip " + escape(zip.value); if (window.XMLHttpRequest) { req new XMLHttpRequest( ); } else { if (window.ActiveXObject) { req new ActiveXObject("Microsoft.XMLHTTP"); } } req.open("Get", url, true); req.onreadystatechange callbackCityState; req.send(null); } function populateCityState( ) { var jsonData req.responseText; var myJSONObject eval("(" + jsonData + ")"); var city document.getElementById("cityId"); city.value myJSONObject.location.city; var state document.getElementById("stateId"); state.value myJSONObject.location.state; } function callbackCityState( ) { if (req.readyState 4) { if (req.status 200) { if (window.XMLHttpRequest) { nonMSPopulate( ); } else { if (window.ActiveXObject) alert("mspopulate"); msPopulate( );

{

} } } } } function nonMSPopulate( ) { var resp req.responseText; var parser new DOMParser( ); var dom parser.parseFromString(resp, "text/xml"); cityValue dom.getElementsByTagName("city"); var city document.getElementById("cityId"); city.value cityValue[0].childNodes[0].nodeValue; stateValue dom.getElementsByTagName("state"); var state document.getElementById("stateId"); state.value stateValue[0].childNodes[0].nodeValue; } function msPopulate( ) { var resp req.responseText; var xmlDoc new ActiveXObject("Microsoft.XMLDOM"); xmlDoc.async "false"; xmlDoc.loadXML(resp); cityValue xmlDoc.getElementsByTagName("city"); var cityField document.getElementById("cityId"); cityField.value cityValue[0].firstChild.data; stateValue xmlDoc.getElementsByTagName("state"); alert("state" + stateValue); var state document.getElementById("stateId"); state.value stateValue[0].firstChild.data; }

184

|

Rozdziat 9. Ajax i JavaServer Faces

Zaprezentowany tu plik JavaScript nie różni się specjalnie od wspomagających plików Java­ Script, które w ykorzystyw aliśm y w poprzednich rozdziałach. M etoda r e t r i e v e C i t y S t a t e ( ) konfiguruje żądanie XMLHttpRequest, wiążąc je z odpowiednim adresem URL i metodą zwrotną. Przygotowana metoda zwrotna c a llb a c k C it y S ta t e ( ) ustala, czy m amy do czynienia z prze­ glądarką M icrosoftu, czy nie i przywołuje odpowiednią procedurę, która zapełni pola for­ mularza.

Obsługiwanie danych JSF poprzez rozszerzanie klasy HtmllnputText Klasa ZipCode jest po prostu ziarnem Javy transferującym dane pomiędzy stroną JSF a aplikacją. Jest ona rozszerzeniem bazowej klasy HtmlInputText, która po prostu obsługuje tekst otrzymany od elementu HTML takiego jak pole tekstowe. Klasa ZipCode składa się głównie z metod umożliwiających sięganie do różnych właściwości, takich jak c ity Id (identyfikator miasta) czy s ta te Id (identyfikator stanu). Najważniejszą częścią klasy ZipCode jest konstruktor, który przy­ pisuje typowi renderera rendererType wartość "ZipCode", wykorzystując w tym celu odwoła­ nie do metody setR end ererT ype() z klasy nadrzędnej. Określony w ten sposób typ renderera (rendererType) musi być taki sam, jak typ zwracany przez metodę ZipCodeTag.getRendererT y p e(). Klasy ZipCode i ZipCodeTag w yglądają praw ie identycznie, niemniej obie są potrzebne apli­ kacji. Klasa ZipCode pobiera dane z formularza, podczas gdy klasa ZipCodeTag konfiguruje pola w znaczniku. Ponieważ obie klasy muszą w spółdziałać z tymi samymi polam i form ula­ rza, wiele ich zmiennych pokryw a się ze sobą. Niemniej każda z wym ienionych klas jest roz­ szerzeniem innej klasy bazowej i zawiera inne metody (klasa ZipCode jest rozszerzeniem klasy Htm lInputText, a klasa ZipCodeTag rozszerzeniem UIComponentTag). Klasa ZipCode jest pow iązana z resztą aplikacji za pośrednictwem pliku faces-config.xm l, który musi definiować komponent obsługujący kody pocztowe. Element w pliku faces-config.xml zawiera znacznik , któremu przypisana jest nazwa klasy " o r e i ll y . a ja x .Z ip C o d e ". Klasa ZipCodeTag posiada metodę zwaną getComponentType(), która musi zwracać odpowiedni typ komponentu — łańcuch " o r e i l l y . a ja x .Z ip C o d e ". Element jest po prostu identyfikatorem typu komponentu, któremu m ożemy przypisać dowolną wartość, pod warunkiem że pasuje ona do łańcucha zw racanego przez m etodę Zip CodeTag.getCom ponentType(). Kod klasy ZipCode został zaprezentow any w listingu 9.9. Listing 9.9. Plik ZipCode.java public class ZipCode extends HtmlInputText { private String zipcodeId "0"; private String stateId ""; private String cityId ""; private String url ""; public ZipCode( ) { super( ); setRendererType("ZipCode"); } public String getCityId( return cityId;

) {

Obsługiwanie danych JSF poprzez rozszerzanie klasy HtmllnputText

| 185

} public void setCityId(String cityId) { this.cityId cityId; } public String getStateId( ) { return stateId; } public void setStateId(String stateId) { this.stateId stateId; } public String getUrl( return url;

) {

} public void setUrl(String url) { this.url url; } public String getZipcodeId( return zipcodeId;

) {

} public void setZipcodeId(String zipcodeId) this.zipcodeId zipcodeId; }

{

}

Kod JSF wspomagający mechanizm Ajax Kiedy żądanie Ajax dociera do aplikacji JSF, musi przejść przez cały cykl życia JSF. W odpo­ wiedniej fazie tego cyklu życia żądanie przekazywane jest właściwej usłudze, która je obsłuży. W aplikacji JSF klasa nasłuchująca przypisana do danej fazy służy do przekazyw ania żądań usługom wykonującym swoje zadanie w odpowiednim punkcie tego cyklu życia. Klasa n a ­ słuchująca w yłapuje zdarzenia zachodzące w różnych fazach cyklu życia JSF i gdy pojaw i się określone zdarzenie, przyw ołuje właściw ą usługę. Klasa nasłuchująca w fazie (ang. phase listener) to speqalna klasa implementująca interfejs Phase L isten e r. W ym aga to korzystania z metod b e fo re P h a s e () i a f te r P h a s e () . Szkielet JSF przywołuje je odpowiednio przed i po każdej zmianie fazy. Wykorzystamy ten mechanizm, by zaimplementować klasę ZipC odePhaseListener, która będzie wykonywać po stronie serwera kod w spom agający mechanizm Ajax (listing 9.10). Listing 9.10. Klasa ZipCodePhaseListener public class ZipCodePhaseListener implements PhaseListener { public void afterPhase(PhaseEvent event) { String viewId event.getFacesContext().getViewRoot().getViewId( if (viewId.indexOf("Ajax") ! -1) { handleAjaxRequest(event); } } private void handleAjaxRequest(PhaseEvent event) { FacesContext context event.getFacesContext( ); HttpServletResponse response (HttpServletResponse) context.getExternalContext().getResponse( ); Object object context.getExternalContext().getRequest( if (!(object instanceof HttpServletRequest)) { // obsługuje tylko ż ąd an ia H ttpServletRequest return; }

186

I

Rozdział 9. Ajax i JavaServer Faces

);

);

HttpServletRequest request (HttpServletRequest) object; String zipcode request.getParameter("zip"); Location location ZipcodeManager.getZipcode(zipcode); // w rzeczywistości renderow ane z a p o m o c ą kodu XML StringBuffer returnXML null; returnXML new StringBuffer("\r\n"); returnXML.append("\r\n"+ location.getCity( )+""); returnXML.append("\r\n"+ location.getState( )+""); returnXML.append("\r\n"); response.setContentType("text/xml"); response.setHeader("Cache-Control", "no-cache"); try { response.getWriter().write(returnXML.toString( event.getFacesContext().responseComplete( );

));

} catch (IOException e) { e.printStackTrace( ); } } public void beforePhase(PhaseEvent arg0) { // z tej m etody nie korzystamy, a le p od ajem y j ą d la wygody kom pilatora } public PhaseId getPhaseId( ) { return Phaseld.RESTORE VIEW; } }

W szystko co niezbędne do przetwarzania żądania zostało obudow ane w zdarzeniu Phase Event. Nie potrzebujem y metody b e fo re P h a s e (), więc zostawiam y ją pustą. M etoda a f t e r P h a se() sprawdza zdarzenie PhaseEvent, by ustalić, kiedy należy rozpatrzyć żądanie Ajax. Metoda a f t e r P h a s e ( ) przyw oływ ana jest po każdej fazie żądania i jeśli wykryje, że zmiennej viewId przypisany jest łańcuch "A jax ", będzie wiadom o, że należy zająć się przetworzeniem żądania Ajax, które trzeba przesłać do odpowiedniej usługi. Identyfikator viewId pobierany jest za pom ocą m etody g etV iew Id (), zwracającej identyfi­ kator id widoku, który w ysłał ostatnie żądanie. Aby sprawdzić, czy żądanie było żądaniem Ajax, w ykonujem y test sprawdzający, czy zmienna viewId zawiera łańcuch "A ja x ": if (viewId.indexOf("Ajax")

!

-1) {

Na przykład warto przyjrzeć się adresowi URL w funkcji r e t r ie v e C it y S t a t e ( ) prezentowanej w listingu 9.8. Jej adres URL to ZipCode A ja x .f a c e s i właśnie ten łańcuch zawierać będzie zmienna viewId. Dzięki temu sprawdzając źródło żądania, które przechowywane jest w zmiennej view Id, nasza aplikacja JSF może ustalić, czy obsługiwane żądanie jest żądaniem Ajax. Prezentowany tu sposób ustalania, czy żądanie jest żądaniem Ajax, wymaga, aby programista używał adresów URL zawierających łańcuch "A jax" tylko dla żądań Ajax. Jeśli programista utworzy zwykłą stronę JSF, której nazwa zawierać będzie tekst „Ajax", pojawi się problem za każdym razem, gdy strona ta wyśle żądanie, nasza aplikacja będzie próbowała obsłużyć żądanie Ajax. Kiedy trzeba obsłużyć żądanie Ajax, metoda a f te r P h a s e () przekazuje kontrolę metodzie h an d leA jaxR equest(). Z kolei metoda h and leA jaxR equest() pobiera obiekt FacesC ontext ze zdarzenia, a następnie uzyskuje z kontekstu odpowiedź serwera S e rv le tR esp o n se . Mając tę

Kod JSF wspomagający mechanizm Ajax

|

187

odpowiedz S e rv le tR esp o n se , metoda h an d leA jaxR eq u est() m oże przyw ołać m etodę Zip codeM anager.getZipcode(), by odnaleźć miasto i stan dla kodu pocztowego. Następnie używa tych danych, by zbudować odpowiedź XML, którą należy odesłać z powrotem do klienta. Od­ syłanie odpowiedzi do klienta sprowadza się po prostu do użycia obiektu S e rv le tR esp o n se , by określić typ MIME i odpowiednie nagłów ki, a następnie użycia klasy W rite r, by odesłać kod XM L z pow rotem do przeglądarki. Klasa ZipcodeManager (listing 9.11) jest pomocniczą klasą odszukującą stan i miasto opowia­ dające podanemu kodowi pocztowemu. W ykonuje proste przeszukanie bazy danych — nie ma tu nic szczególnie ciekawego i oczywiście w bardziej zaawansowanej aplikacji m echa­ nizm ten można będzie odpowiednio poprawić. Listing 9.11. Klasa ZipcodeManager public class ZipcodeManager { static public Location getZipcode(String zip) { Location location null; Connection con DatabaseConnector.getConnection( ); String sqlString ""; location new Location) ); location.setZipCode(zip); // tu umieszczamy oryginalny kod p ocztow y try { sqlString "SELECT CITY,STATE,ZIPCODE FROM ZIPCODES WHERE ZIPCODE '"+zip+"';"; Statement select con.createStatement( ); ResultSet result select.executeOuery(sqlString); if (result.next( )) { // przetw arzam y wyniki wiersz p o wierszu location.setCity(result.getString(1)); location.setState(result.getString(2)); location.setZipCode(result.getString(3)); } } catch (Exception e) { System.out.println("wyjątek podczas logowania"+e.getMessage( } finally { if (con ! null) { try { con.close( ); } catch (SOLException e) { } } } return location;

));

} }

Aby przesłać stan i miasto z powrotem do nadawcy żądania, skorzystamy z prostego ziarna Javy o nazwie L o catio n . Klasa ta została przedstaw iona w listingu 9.12. Listing 9.12. Ziarno Javy Location.java public class Location { private String city; private String state; private String zipCode; public String getCity( ) { return city; }

188

|

Rozdział 9. Ajax i JavaServer Faces

public void setCity(String city) { this.city city; } public String getState( ) { return state; } public void setState(String state) { this.state state; } public String getZipCode( return zipCode;

) {

} public void setZipCode(String zipCode) this.zipCode zipCode; }

{

}

Podsumowanie Rozdział ten tylko musnął pow ierzchnię problem ów zw iązanych ze szkieletem program o­ wania JavaServer Faces. Technologia JSF jest bardzo złożona i dokładne jej omówienie w y­ kracza poza zakres tego rozdziału. Niemniej mimo złożoności jej architektury, zaprojektowanej, by umożliwić pisanie komponentów, które będzie można swobodnie wykorzystywać w zinte­ growanym środowisku programowania IDE, dodawanie do tej architektury funkcji Ajax nie jest, jak mogliśm y się przekonać, aż tak trudne. Przyjrzeliśm y się tylko jednej metodzie im­ plementowania mechanizmów Ajax w aplikacjach JavaServer Faces. Czytelnicy zaintereso­ wani dalszym zgłębianiem technologii JSF pow inni zajrzeć do książki Hansa Bergstena Java­ Server Faces (wydanej przez O'Reilly).

Podsumowanie

| 189

^0

I RozdziałS.Ajax iJavaServer Faces

ROZDZIAŁ 10.

Zestaw narzędziowy Google Web Toolkit

W czerwcu 2006 roku na konferencji JavaOne firma Google ogłosiła, że wprowadza zupełnie now y produkt: zestaw narzędziowy Google W eb Toolkit (GWT). Zestaw narzędziowy GWT jest przykładem zupełnie nowego podejścia do tworzenia aplikacji Ajax — zamiast pisać kod HTML (lub też strony JSP czy JSF) i JavaScript, współpracujący z działającymi po stronie serwera serwletami Javy lub komponentami, GW T umożliwia nam napisanie całej aplikacji (zarówno po stronie klienta, jak i po stronie serwera) w kodzie Javy. GWT generuje automatycz­ nie odpowiedni kod JavaScript, bazując na napisanym przez nas kodzie Javy. Nadal będziemy musieli napisać odrobinę kodu HTML, niemniej GWT dostarczy nam szkieletowego kodu HTML, od którego będziem y mogli zacząć. Możliwość napisania całego kodu w Javie jest oczywiście wielką wygodą, lecz to jeszcze nie wszystkie zalety GWT. W końcu pisanie kodu HTML czy JavaScript nie jest znowu aż takie trud­ ne. Kolejną ważną zaletą zestawu narzędziowego GWT jest to, że obsługuje on za nas wszystkie sprawy związane z dostosowaniem kodu do potrzeb różnych przeglądarek. Jakby tego było jesz­ cze mało, oferuje narzędzia ułatwiające wyszukiw anie błędów w kodzie. Oznacza to ni mniej, ni w ięcej, że korzystając z jednego i tego samego środowiska program owania IDE, możemy sprawdzić (debugować) zarówno kod po stronie serwera, jak i kod po stronie klienta! W yszukiwanie błędów w kodzie klienta możliwe jest dzięki tzw. bibliotekom klienta GWT. Zostały one napisane w języku Java, natomiast w momencie kompilacji konwertowane są na od­ powiedni kod JavaScript i HTML. W trybie „hosted" (goszczącym) kod klienta nadal ma for­ mę kodu Javy, dzięki czemu można go wygodnie przeglądać w poszukiwaniu błędów.

Zaczynamy pracę z GWT Aby rozpocząć pracę z zestawem narzędziowym GW T, należy: 1. Pobrać z internetu i zainstalować na komputerze środowisko JDK. 2 . Pobrać z internetu i zainstalować na komputerze zestaw narzędziowy GWT, dostępny pod adresem http://code.google.com/webtoolkit. 3. Napisać odpowiedni kod i zbudować aplikację. Zacznijmy od kroku trzeciego — pierw sze dwa są bow iem na tyle trywialne, że nie powinny sprawić Czytelnikowi problemu. Rysunek 10.1 pokazuje, jak nasza przykładowa aplikacja, którą będziem y przygotow yw ać w tym rozdziale, w yglądać będzie w oknach trzech najważ­ niejszych przeglądarek.

191

I'

A j a x r e s p o n s e - W in d o w s I n t e r n e t E x p lo r e r

^

\p ] http ¡//localhost;8888/cc T | |^t| |X | |Google

Plik

ij

Edycja

Widok

iS f

A ja x

Ulubione

Narzędzia

| Q f T 63 ' ^

Ajax response

i

P

G o o g le W e b

'

Pomoc T

*

-O 5trona -

T o o lk it .

K o d pocztowy: |93710 Miasto:

IFRESNO

Stan:

|CA

zl

I

Gotowe

I

A ja x re s p o n s e

Plik

Edycja

-

I

I

| |

I

I +v 100%

Local intranet

^ in lx l

O p e ra

Widok

Zakładki

r

Widżety

Narzędzia

Pomoc

f 1~~| Nowa i?

A ja x

-t

i

r*

&

S '

@ 8080/chl0-GW Tj ?

-

O

Google

-

G o o g le W e b T o o lk it .

K o d pocztowy: |44444 >

M iasto :

|NEWTON FALLS

Statu

|OH

Rysunek 10.1. Nasza przykładowa aplikacja w przeglądarkach Internet Explorer, Firefox i Opera

192

|

Rozdział 10. Zestaw narzędziowy Google Web Toolkit

Aby otrzymać szkielet naszej aplikacji, należy uruchom ić program ApplicationCreator ze­ stawu narzędziowego GWT. Należy pam iętać, aby uruchamiać program ApplicationCreator z katalogu, w którym zainstalow aliśmy zestaw narzędziow y GW T, lub też przyw oływ ać go, podając pełną ścieżkę do pliku w ykonywalnego tego programu: C:> applicationCreator com.oreilly.client.ZipCodes

Polecenie to tw orzy drzewo katalogu src, zaw ierającego dwa podkatalogi: src/com/oreilly/client i src/com/oreilly/public. Jak dotąd nie ma tu nic zaskakującego — katalogi te zgodne są ze zwykłą konw encją pakietów oprogram ow ania Java. Katalog klienta client zaw iera plik klasy Javy: ZipCodes.java, a katalog public zawiera plik ZipCodes.html. Rysunek 10.2 prezentuje drzewo katalogów przygotow yw ane przez program.

Zipcodes-ccimpile.cmd Zipcodes-shell.cmd . 'l src .

com oreilly Zipcodes.gwt.xml i client

Zipcodes.java . 1 public Zipcodes.html

Rysunek 10.2. Pliki tworzone przez program ApplicationCreator Spoiwem sklejającym aplikację w jedną całość jest kod znajdujący się w pliku ZipCodes.gwt.xml (listing 10.1). Plik ten zawiera informacje na temat tego, w jaki sposób aplikaqa powiązana jest z rdzeniem dostarczanym przez zestaw narzędziow y GWT. Listing 10.1. Plik ZipCodes.gwt.xml



-->

 Nazwa produktuOpisCenaObrazZmiana obrazka
" +" Deklaruje zastąpienia dla klas fabryk. < lif e c y c le > W ym ienia klasy nasłuchujące dla różnych faz cyklu życia.

Pisanie własnego komponentu JSF

|

175

Deklaruje zarządzane ziarno. Ziarno to jest autom atycznie tworzone i inicjow ane przez szkielet JSF. < n av ig atio n ru le> Tworzy regułę dla kodu zajmującego się obsługą nawigacji.

Deklaruje zestaw renderujący lub własne funkcje renderujące w ykorzystyw ane przez domyślny zestaw renderujący. < v a lid a to r> Deklaruje klasę zajmującą się spraw dzaniem danych. Nasz plik faces-config.xm l przedstaw iony został w listingu 9.4. Ta prosta aplikacja korzysta tylko z elementów , i < lif e c y c le > . Listing 9.4. Plik faces-config.xml



com.oreilly.ajax.ZipCodePhaseListener

Implementacja zestawu renderującego dla komponentów JSF Ajax

javax.faces.Input ZipCode com.oreilly.ajax.ZipCodeRenderer

O'Reilly Zip Code oreilly.ajax.ZipCode com.oreilly.ajax.ZipCode

Element < lif e c y c le > zawiera element , definiujący klasę, która może być przyw oływ ana na koniec każdej fazy (w tym przypadku klasę ZipC od ePhaseL istener). Elem ent w tym przykładzie pliku konfiguracyjnego zawierać będzie pojedyn­ czy element , któremu przypisana jest klasa com .o r e illy .a ja x .Z ip C o d e R e n d e r e r . Zestaw renderujący (ang. renderer kit) może zawierać jed en lub więcej rendererów. Domyślny zestaw renderujący zawiera renderery dla kodu HTM L, niemniej można przygotow ać rów­ nież zestawy renderujące dla innych języków opartych na znacznikach. Renderer zajm uje się rysowaniem (wyświetlaniem) określonego komponentu. Na przykład renderer form ularza będzie renderował (przygotowywał do wyświetlenia) element (dokładniej, metoda encodeBegin() renderera będzie renderować znacznik , a metoda encodeEnd() renderować będzie znacznik ). Plik faces-config.xm l prezentowany w listingu 9.4 definiuje renderer, który w ykorzystuje klasę co m .o re illy .a ja x .Z ip C o d e R e n d e re r, by przygotować kod HTM L dla wyświetlanej zawartości, i kod Ajax potrzebny naszem u znacznikow i JSF.

176

|

Rozdział 9. Ajax i JavaServer Faces

Elem ent definiuje kom ponent JSF, który będzie w ykorzystyw any w aplikacji. W tym przypadku klasa c o m .o r e illy .a ja x .Z ip C o d e jest naszym w łasnym komponentem. W ykorzystujem y go do pobierania danych HTML z aplikacji. Rysunek 9.3 pokazuje schem at konfiguracji naszej aplikacji. W arto pam iętać, że jest to bardzo prosty przykład. W prawdziwej aplikacji schem at konfiguracji byłby o wiele bardziej złożony i zawierał znacznie więcej elementów.

Konfiguracja

com.oreilly.ajax.ZipCodePhaseListener

i t

i ♦ PhaseListener

UserBean

Renderer

public class ZipCodePhaseListener implements Phaselistenerf public void afterPhase(){

public final class UserBean extends Object 1 public UserBeanO

public final class ZipCodeRenderer extends Rendererf public void encodeBeginf)

private void handleAjaxRequestO

( -

Rysunek 9.3. Schemat konfiguracji aplikacji JavaServer Faces

Tworzenie własnego znacznika JSF W rozdziale 7. pisaliśm y w łasny znacznik JSP. Teraz zajmiemy się pisaniem w łasnego znacz­ nika JSF. Pomiędzy oboma typami znaczników istnieją pewne podobieństwa. Na przykład za­ równo podczas definiowania własnych znaczników JSP, jak i JSF korzystamy z pliku TLD (ang. Tag Library Definition — plik definiujący bibliotekę znaczników). Podobnie oba typy znaczników obsługiwane są za pomocą odpowiednich klas obsługujących znaczniki. Niemniej są również pom iędzy nim i pewne znaczące różnice: • Przygotowany przez nas znacznik JSP rozwijał będzie klasę j a v a x .s e r v l e t . js p .t a g e x t . TagSupport. Natomiast znacznik JSF jest rozwinięciem klasy ja v a x .fa c e s .c o m p o n e n t. UIComponentBase. • Nasz własny znacznik JSF będzie posiadał kolejną klasę, zwaną rendererem, która zajmo­ wać się będzie umieszczaniem informacji w widoku (w naszym przypadku na stronie JSP). Renderer ten będzie rozszerzeniem klasy ja v a x .f a c e s .r e n d e r .R e n d e r e r .

Tworzenie w łasnego znacznika JSF

|

177

Rysunek 9.4 pokazuje, w jaki sposób klasa znacznika oraz renderer pow iązane są z biblioteką znaczników oraz kodem JSP korzystającym z tejże biblioteki.

TLD

Klasa z n a czn ik a

< ta g llb > aja x< /sh o rtn am e > < u r i> http://ajax.oreill.com /jsf/ajax < ta g > zip C o d e < /zip C o d e >

public final d a ss ZipCodeTag extends UIComponentTag !...

___________ t t ____________ JSP < % @ taglib u r l= http://ajax.oreilly.com/jsf/ajax prefix="ajax" % >

i

t ----------------------------------------R end erer Public final class ZipCodeRenderer extends Renderer! public void encodeDeginf)

!

Rysunek 9.4. Własne pliki znaczników i ich wzajemne interakcje Zacznijmy od podsumowania tego, co już wiemy. Chcemy, aby znacznik wyświetlał informacje na temat miast i stanu w zależności od kodu pocztowego. Najpierw m usimy napisać plik TLD, który definiować będzie znacznik. W spomniany plik TLD musi posiadać główny znacz­ nik o nazwie < ta g lib > . Wewnątrz niego będzie można zdefiniować dowolną liczbę znaczni­ ków . W tym przypadku musimy zdefiniować tylko jeden taki znacznik. Następnie w ele­ mencie < ta g c la s s > zdefiniujem y klasę, która obsługiwać będzie tenże znacznik. W naszym przykładzie będzie to klasa o nazw ie ZipCodeTag.

Pisanie pliku TLD Plik TLD definiujący biblioteką znaczników , zaprezentow any w listingu 9.5, definiuje tylko pojedynczy znacznik zipCode. Znacznik ten zawierać będzie trzy pola: kodu pocztowego (zip code), stanu (state) i miasta (city).

178

I

Rozdział 9. Ajax i JavaServer Faces

Listing 9.5. Plik TLD dla znaczników JSF

1.0 1.1 ajax < uri>http://ajax.oreilly.com/jsf/ajax dodaje znaczniki z funkcjami Ajax do naszych stron JSP

zipCode com.oreilly.ajax.ZipCodeTag empty

zipcodeId true

stateId true

cityId true

url true true



Zaprezentowany tu plik TLD jest praktycznie taki sam, jak plik TLD w ykorzystyw any przez nas w rozdziale 7. do tworzenia znacznika JSP z funkcjami Ajax. Definiuje on cztery informacje pobierane przez ten znacznik jako dane wejściowe: zipcodeId (identyfikator kodu pocztowego), s t a t e I d (identyfikator stanu), c i t y I d (identyfikator m iasta) i u r l (adres URL). Znaczniki wskazują, że wszystkie z wymienionych informacji są niezbędne — żadnej z nich nie można traktować jako opcjonalną. Plik TLD podczas transferowania danych ze strony W W W do aplikacji JSF w spółdziała z kla­ są obsługującą znaczniki oraz rendererem. Teraz przyjrzym y się w łaśnie klasie obsługującej znaczniki.

Pisanie klasy obsługującej znaczniki Teraz, gdy mamy już plik TLD, trzeba napisać klasę obsługującą znaczniki. Klasa ta musi być rozszerzeniem klasy javax.faces.w ebapp.U IC om ponentTag. Będzie ona podobna do znanej już nam klasy obsługującej znaczniki JSP, niemniej będzie musiała zawierać dwie nowe metody: oprócz standardowych metod służących do pobierania i definiowania w artości zmiennych będziemy musieli zaimplementować dodatkowo metody g etren d ererT y p e() i getComponent T y p e(). Pierwsza z nich przesyła nazwę znacznika rendererowi, a druga przesyła stronie W W W typ komponentu. Kod naszej klasy obsługującej znaczniki, ZipCodeTag, zaprezentow any został w listingu 9.6.

Tworzenie w łasnego znacznika JSF

|

179

Listing 9.6. Plik ZipCodeTag.java package com.oreilly.ajax; import java.io.IOException; import import import import import

javax.faces.component.UIComponent; javax.faces.context.FacesContext; javax.faces.el.ValueBinding; javax.faces.render.Renderer; javax.faces.webapp.UIComponentTag;

public final class private String private String private String private String

ZipCodeTag extends UIComponentTag { zipcodeId "0"; stateId ""; cityId ""; url "";

public String getCityId( ) { return cityId; } public void setCityId(String city) { this.cityId city; } public String getStateId( return stateId;

) {

} public void setStateId(String state) this.stateId state; } public String getUrl( ) { return url; } public void setUrl(String url) { this.url url; } public String getZipcodeId( return zipcodeId;

{

) {

} public void setZipcodeId(String zipcodeId) this.zipcodeId zipcodeId;

{

} public String getComponentType( ) { return "oreilly.ajax.ZipCode"; } public String getRendererType( ) { return "ZipCode"; // Z ipC odeR enderer } public void release( ) { zipcodeId null; stateId null; cityId null; url null; } protected void setProperties(UIComponent component) super.setProperties(component); ZipCode input null; try { input

(ZipCode) component;

} catch (ClassCastException cce) { throw new IllegalStateException( "Komponent "

180

|

Rozdział 9. Ajax i JavaServer Faces

{

+ component.toString( ) + " niewłaściwy typ. Prawidłowy typ: ZipCode. Może brakuje znacznika?"); } FacesContext context getFacesContext( ); if (cityId ! null) { if (isValueReference(cityId)) { ValueBinding vb context.getApplication( ).createValueBinding(cityId); component.setValueBinding("cityId", vb); } else { input.setCityId(cityId); } } } }

Klasa ZipCodeTag jest rozszerzeniem klasy UIComponentTag z biblioteki JavaServer Faces. Więk­ sza część kodu klasy ZipCodeTag powinna nam już być znajoma. W ykorzystuje metody s e t i g e t, by zarządzać param etram i klasy: kodem pocztowym , stanem i miastem. Aplikacja JSF wykorzystuje metodę r e l e a s e ( ) , by przywracać początkowe wartości parametrów. Metoda s e t P r o p e r t ie s ( ) wiąże param etry znaczników z w artościam i odsyłanym i z pow rotem przez serwer. Dwie metody, z którym i nie mieliśmy jeszcze okazji się zetknąć, są bardzo proste. Plik faces-config.xml definiuje element i metoda ZipCodeTag.getComponentType() po prostu zwraca w formie łańcucha S tr in g pełną nazwę klasy podanej w tym elemencie. Podobnie metoda g etre n d e re rT y p e () zwraca po prostu nazw ę renderera, z którego będzie­ my korzystać (w tym przypadku po prostu łańcuch "ZipCode"). W arto zauważyć, że tym ra­ zem nie jest konieczne podawanie pełnej nazw y klasy.

Tworzenie rendererów i zestawów rendererów Element w pliku konfiguracyjnym definiuje, w jaki sposób renderowany będzie widok. Komponent nie tworzy bezpośrednio zawartości wyświetlanej na stronie. Zam iast te­ go przyw ołuje odpowiedni renderer w chodzący w skład zestawu rendererów, który zajmie się przygotow aniem danych do wyśw ietlenia (renderowaniem). Dom yślny zestaw rendererów, z którego korzystamy w tym przykładzie, zawiera wyłącznie renderery przeznaczone do tworzenia kodu HTML. Inne zestawy rendererów mogą zaw ierać renderery tworzące kod innych języków opartych na znacznikach i przygotow yw ać zaw artość przeznaczoną do wy­ świetlania w innych urządzeniach niż komputer, takich jak urządzenia przenośne. Renderer dla klasy ZipCodeTag, przedstaw iony w listingu 9.7, wykorzystuje metodę encode B e g in (), by w strzyknąć kod JavaScript do strony JSP. Renderer wstaw ia również potrzebne nam elementy HTM L, takie jak pola dla miasta, stanu i kodu pocztowego. Listing 9.7. Renderer ZipCodeRenderer wyświetlający informacje bezpośrednio na stronie WWW public final class ZipCodeRenderer extends Renderer { public boolean getRendersChildren( ) { return true; }

Tworzenie w łasnego znacznika JSF

|

181

public void encodeBegin(FacesContext context, UlComponent component) throws IOException { ResponseWriter writer context.getResponseWriter( ); Map attributeMap component.getAttributes( ); Object o attributeMap.get("zipcodeId"); writer.startElement("script", component); writer.writeAttribute("type", "text/javascript", null); // to pow inien bye atrybut, t a k ija k cityId String src "scripts/ora-ajax.js"; writer.writeAttribute("src", src, null); writer.endElement("script"); writer.startElement("div", component); writer.writeAttribute("id", "ajaxDivId", null); writer.write("Hej, to blok div"); writer.endElement("div"); writer.write("\n"); writer.startElement("table", component); writer.startElement("tr", component); writer.startElement("td", component); writer.write("Kod pocztowy: "); writer.endElement("td"); writer.startElement("td", component); writer.startElement("input", component); writer.writeAttribute("onblur", "retrieveCityState( writer.writeAttribute("type", "text", null); writer.writeAttribute("id", "zipcodeId", null); writer.endElement("td"); writer.endElement("tr");

);", null);

writer.startElement("tr", component); writer.startElement("td", component); writer.write("Miasto: "); writer.endElement("td"); writer.startElement("td", component); writer.startElement("input", component); writer.writeAttribute("type", "text", null); writer.writeAttribute("id", "cityId", null); writer.endElement("td"); writer.endElement("tr"); writer.startElement("tr", component); writer.startElement("td", component); writer.write("Stan: "); writer.endElement("td"); writer.startElement("td", component); writer.startElement("input", component); writer.writeAttribute("type", "text", null); writer.writeAttribute("id", "stateId", null); writer.endElement("td"); writer.endElement("tr"); writer.endElement("table"); writer.write("\n"); } public void encodeEnd(FacesContext context, } }

182

|

Rozdziat 9. Ajax i JavaServer Faces

UIComponent component)

{

Renderer jest rozszerzeniem klasy ja v a x .f a c e s .ren d er. Renderer i pokrywa jej metody encode B eg in () oraz encodeEnd(). Metoda encodeBegin() wstrzykuje informaqe w miejscu znacznika otwierającego, a metoda encodeEnd() zapisuje informacje na końcu. M y wykorzystamy metodę en co d eB eg in (), by w staw ić odwołanie do naszej biblioteki JavaScript: ora-ajax.js. Moglibyśmy ręcznie wpisać odpowiedni kod JavaScript jako łańcuch typu S t r in g i zapisać go bezpośrednio na stronie dokładnie tak, jak to robiliśmy w rozdziale 7. Rozwiązanie takie ma kilka zalet: mamy pewność, że kod JavaScript zawsze będzie tam, gdzie powinien (nie potrzebujemy żadnego dodatkowego pliku), oraz zabez pieczamy się przed możliwością zmiany kodu JavaScript, co może zaowocować dziwnym zachowaniem strony. Niemniej wady ręcznego pisania kodu JavaScript przeważają nad zaletami. Jedną z głównych niedogodności będzie trudniejsze wpro wadzanie zmian w kodzie JavaScript podczas pisania biblioteki i następnie wyszuki wania błędów w jej kodzie. Zamiast zmieniać plik tekstowy, będziemy musieli mody fikować klasę Javy, co z kolei wymagać będzie ponownego kompilowania aplikacji i po raz kolejny instalowania jej na serwerze. Może to być naprawdę poważnym problemem, jeśli będziemy musieli wprowadzić w pliku więcej zmian. Klasa ja v a x .f a c e s . co n te x t.R e sp o n se W rite r oferuje nam kilka użytecznych metod. M etody takie jak s t a r tE le m e n t () , endElem ent() czy w r it e A t t r ib u t e ( ) umożliwiają nam przygo­ towywanie odpowiedniego kodu (znaczników), który pozwoli wyświetlić zawartość na stronie, bez uwiązania do konkretnej im plementacji. W spółpracując z innym rendererem, m etody te przygotowałyby na przykład kod znacznikowy umożliwiający wyświetlenie zawartości strony w telefonie komórkowym. Z klasy ResponseW riter korzysta się w sposób dość prosty i intuicyjny. W naszym rendererze następujący kod: writer.startElement("td", component); writer.write("State: "); writer.endElement("td"); writer.startElement("td", component); writer.startElement("input", component); writer.writeAttribute("type", "text", null); writer.writeAttribute("id", "stateld", null); writer.endElement("td");

przygotow ałby taki oto zestaw znaczników tabeli: