Przedmowa .................................................................................................................... 7 1. Przygotowania do wdrażania Ajaksa ......................................................................... 15 Technologie ajaksowe Początkowe porządkowanie Przekształcanie tabel na układ strony oparty na CSS Ciąg dalszy zmian — element po elemencie Radzenie sobie ze specyfiką przeglądarek Zrozumienie potrzeb użytkowników Projektowanie szkieletu witryny Stopniowe usprawnianie a remont generalny
17 20 25 29 30 33 36 39
2. Elementy Ajaksa .......................................................................................................... 41 Aplikacje sieciowe Przygotowywanie obiektu do użytku Przygotowywanie i wysyłanie żądania Przetwarzanie ajaksowych odpowiedzi Punkty końcowe, zabezpieczenia języka JavaScript i widgety Bezpieczeństwo Pierwszy rzut oka na wydajność Ostatnie słowo o asynchroniczności i synchroniczności
41 48 50 56 71 74 75 76
3. Narzędzia i pojęcia związane z Ajaksem ................................................................... 79 Prototype script.aculo.us Rico Dojo Inne biblioteki
80 87 90 92 99
3
4. Efekty interaktywne .................................................................................................. 103 Obsługa zdarzeń zgodna z Ajaksem Informacje w trybie JIT Podgląd na stronie Zanikanie kolorów w wyniku sukcesu lub niepowodzenia
104 110 121 126
5. Przestrzeń — ostateczna granica ............................................................................. 135 Przestrzeń w poziomie — accordion Strony z zakładkami Nakładanie
136 159 170
6. Dane dynamiczne .......................................................................................................177 Edycja w miejscu Edycja w miejscu — wydajność, bezpieczeństwo i dostępność Wyróżnianie zmian Jeszcze raz o dostępności aktualizacji na stronie Walidacja na żywo Wydajność i dwuetapowe zatwierdzanie Efekty bibliotek zewnętrznych służące do obsługi danych
178 188 191 202 205 208 211
7. Historia, nawigacja i miejsca w aplikacjach jednostronicowych ............................. 215 Wyzwanie — stronicowana zawartość Zapamiętywanie miejsc Trwałość w starym i nowym stylu — ramię w ramię Nowy wygląd strony Analiza końcowa
216 235 242 249 252
8. Dodawanie zaawansowanych efektów wizualnych ...............................................253 Zaawansowane sztuczki z CSS Skalowalna grafika wektorowa Krótki przegląd języka SVG Mikser — SVG i Ajax Przyszłość grafiki
254 264 269 273 280
9. Witryny typu mashup ................................................................................................ 281 Wyświetlanie map za pomocą Google’a Druga usługa — Flickr Dodawanie usług Technorati do witryny mashup Modyfikowanie witryny mashup Nowa wersja klientów Podsumowanie informacji o witrynach mashup
4
|
Spis treści
282 288 299 307 316 325
10. Skalowanie, infrastruktura i tworzenie witryn od podstaw ................................... 327 Platformy — ścisłe czy luźne powiązanie Usługi sieciowe — zasoby i bezpieczeństwo Biblioteki Ajaksa — własne czy zewnętrzne? Projektowanie aplikacji ajaksowych od podstaw Rekomendowane platformy A więc naprzód z Ajaksem
Ajax — po części rewolucja, po części ewolucja, a zdaniem niektórych tylko slogan reklamowy. Ajax to ogólne pojęcie, które obejmuje szereg technologii: • języki oparte na znacznikach, takie jak HTML, XHTML, XML i SVG; • język JavaScript; • języki CSS i XSLT; • w końcu — choć nie są najmniej istotne — obiekty przeglądarek, włączając w to obiekt
canvas oraz obiekt, który stanowi istotę Ajaksa: XMLHttpRequest.
Choć wzrost zainteresowania Ajaksem miał miejsce stosunkowo niedawno, większość związanych z nim technologii jest dostępna od około 10 lat. Z czego wynika ich obecna popularność? Ajax to coś więcej niż zbiór technologii. Jego zastosowanie wiąże się z rozwojem stron i aplikacji internetowych w nowym kierunku. Programiści planowali to już wcześniej, jednak nigdy nie mieli narzędzi umożliwiających tworzenie takich aplikacji. Obecnie specyfikacje, które 10 lat temu były zupełną nowością, osiągają dojrzałość, a co ważniejsze, są dobrze obsługiwane w przeglądarkach internetowych. Wciąż pojawiają się także nowe specyfikacje, a producenci narzędzi współpracują ze sobą w większym stopniu niż kilka lat wcześniej. Wiele lat temu, kiedy projektanci stron zaczęli po raz pierwszy nieśmiało wspominać o dodawaniu interaktywności do stron internetowych, programiści byli ograniczeni przez działanie przeglądarek, które obsługiwały zupełnie odmienne modele, a czasami nawet inne języki skryptowe. Stosowanie kaskadowych arkuszy stylów (ang. Cascading Style Sheets — CSS) jako uniwersalnej techniki dodawania warstwy prezentacji strony było utrudnione z powodu różnej interpretacji stylów przez poszczególne przeglądarki, nie wspominając już o stosowaniu własnych rozszerzeń. Obecnie obsługa arkuszy CSS jest powszechna, a choć wciąż występują pewne „specyficzne” efekty w niektórych systemach, większość przeglądarek obsługuje niemal wszystkie specyfikacje, a więcej możliwości jest otwartych, niż zamkniętych. W przypadku skryptów prace organizacji standaryzacyjnej ECMA doprowadziły do powstania języka ECMAScript. Jest to wersja języka JavaScript powszechnie przyjęta przez producentów i obsługiwana we wszystkich najważniejszych narzędziach — zarówno internetowych, jak i innych. Języki oparte na znacznikach stały się dużo bardziej złożone i następuje odejście od chaotycznego HTML do bardziej zdyscyplinowanego XHTML. Powstają także nowe aplikacje języka XML, na przykład skalowalna grafika wektorowa (ang. Scalable Vector Graphics — SVG), która 7
umożliwia dodawanie interaktywnej grafiki niezależnej od wtyczek czy obiektów zewnętrznych. Wiąże się z tym wprowadzenie obiektów canvas, które są niezależne od stosowanego języka znaczników. Ich rozwój odbywa się w ramach prac nad językiem HTML5. Ten język to etap pośredni pomiędzy starszymi wersjami HTML a bardziej rygorystycznym językiem XHTML. Wciąż jednak nie zrezygnowano ze starych, niestandardowych obiektów, jednak nie są one implementowane wyłącznie w kilku przeglądarkach, raczej stają się one powszechnie dostępne. Jednym z takich obiektów jest XMLHttpRequest, który umożliwia wywoływanie usług sieciowych bezpośrednio z poziomu stron, a następnie dynamiczne przetwarzanie wyników bez konieczności ponownego ładowania strony. Co to oznacza dla programistów i użytkowników? Możliwość przeczytania artykułu, kliknięcia go i modyfikowania w jednym miejscu — na tej samej stronie. Można także usuwać wiersze z tabeli, a operacja usuwania odbędzie się natychmiast. To samo dotyczy aktualizacji danych. Można też kategoryzować zdjęcia bez konieczności ponownego wczytywania strony i fotografii. Można też rozwinąć zdjęcie, po prostu klikając jego miniaturę. Również stosowanie formularzy znacznie się zmieniło. Można dokonać wyboru na jednej liście, a spowoduje to automatyczne zapełnienie innej listy. Można sortować dane w tabeli, używając przeciągania, zwężać pola, aby zrobić więcej miejsca, a także klikać zakładki w celu wyświetlenia fragmentów dużych formularzy lub innych informacji, a wszystko to bez odświeżania strony. Oczywiście znaczenie pojęcia „strona” zmieniło się, a nowe podejście ma dobre i złe cechy. Trudno jest określić, jak oszacować koszt reklamy za „wyświetlenie strony”, jeśli można wczytać setki stron bez żadnej operacji odświeżania. Silniki wyszukujące mają problemy z obsługą dynamicznie generowanych odnośników, podobnie zresztą jak programy odczytujące tekst z ekranu. W sytuacji kiedy obsługa skryptów jest wyłączona lub w ogóle jej nie ma, Ajax nie będzie działał, ponieważ zależy od języka JavaScript. W tym miejscu na scenę wchodzi stopniowe usprawnianie (ang. progressive enhancement). Termin ten, ukuty przez Stevena Champeona, określa podejście, zgodnie z którym efekty Ajaksa należy dodawać jako usprawnienia istniejących technologii sieciowych, a nie jako zastępnik. Inaczej mówiąc, wciąż należy tworzyć bardziej tradycyjne aplikacje sieciowe, które wykorzystują przesyłanie formularzy w celu aktualizacji tabel, ale następnie można usprawniać je, dodając na przykład możliwość aktualizacji tabeli po zmianie zawartości pola bez konieczności przesyłania formularza. Jeśli obsługa skryptów jest wyłączona, stronę z zakładkami można wyświetlać jako szereg pionowych sekcji. Dane wciąż będą dostępne, jednak uporządkowane w inny sposób. W pokazie slajdów można wyświetlać obrazy, kiedy zażąda tego albo aplikacja w języku PHP znajdująca się na komputerze z systemem Unix, albo wywołanie języka JavaScript z przeglądarki Firefox, Safari czy Internet Explorer. Dzięki zastosowaniu stopniowego usprawniania nie jest istotne, czy skrypty są włączone, czy nie, ponieważ funkcjonalność jest dostępna zawsze, tylko w odmienny sposób. Jednocześnie liczne udogodnienia, które Ajax pozwala dodać do aplikacji sieciowych, wciąż można udostępniać około 80 procentom osób, które używają odpowiednich przeglądarek z włączoną obsługą skryptów. Co jest z tego najlepsze dla Ciebie? Jesteś już w połowie drogi. 8
|
Przedmowa
Odbiorcy Jednym z głównych założeń co do Czytelników książki dotyczącej dodawania Ajaksa jest to, że są oni projektantami stron, którzy chcą przenieść tradycyjne aplikacje sieciowe na wyższy poziom, dodając efekty Ajaksa. Możliwe, że używasz już formularzy sieciowych, stron generowanych po stronie serwera i statycznej zawartości, a przynajmniej wiesz, jak działają tradycyjne aplikacje sieciowe. Masz szczęście — „część biznesowa” aplikacji jest już gotowa. Teraz pora przenieść stronę na następny poziom. Najpierw jednak warto zapoznać się z dokładniejszymi założeniami co do osób, które są odbiorcami tej książki. Została ona napisana dla programistów, którzy: • mają pewne doświadczenie z opartymi na znacznikach językami tworzenia stron interne-
towych, takimi jak HTML czy XHTML; • mają podstawową wiedzę na temat XML i zdają sobie sprawę, że dokumenty w tym języ-
ku muszą być zarówno prawidłowe, jak i poprawnie skonstruowane;
• znają relacyjne bazy danych i używali ich albo w środowisku produkcyjnym, albo po pro-
stu z ciekawości; • stosowali język CSS i potrafią odczytać większość arkuszy CSS; • używali języka JavaScript i znają go dość dobrze, a także wiedzą, jak działają obiektowe
mechanizmy tego języka; • są bardziej zainteresowani usprawnianiem istniejących aplikacji niż tworzeniem tak zwa-
nych bogatych aplikacji internetowych (ang. Rich Internet Applications — RIA). Nie zakładam, że jesteś ekspertem w jednej z wymienionych wcześniej technologii, a jedynie że masz pewne doświadczenie w korzystaniu z nich i z drobną pomocą (albo w postaci materiałów z internetu, albo innej książki, specyficznej dla danej technologii) potrafisz zrozumieć działanie każdej aplikacji. Ostatni element na liście wymagań jest najważniejszy. Ta książka nie obejmuje zagadnień niezbędnych do utworzenia zastępnika programu PowerPoint w postaci strony internetowej lub tworzenia gier sieciowych albo innych rozwiązań opartych całkowicie na skryptach. Ta książka dotyczy wyłącznie rozbudowy istniejących aplikacji przez dodawanie do nich efektów Ajaksa. Nie opisuje ona zastępowania istniejących aplikacji ani tworzenia od podstaw w pełni ajaksowych rozwiązań. Wydawnictwo Helion ma w ofercie inne ciekawe książki, bardziej odpowiednie dla osób zainteresowanych pracą nad następnym arkuszem kalkulacyjnym opartym na przeglądarce WWW. Ta książka ma jedynie pomóc w tworzeniu bardziej żywych, zajmujących, interesujących i w dużo większym stopniu interaktywnych stron internetowych.
Zawartość książki Tej książki nie trzeba czytać od początku do końca. Każdy jej rozdział jest tak niezależny, jak to możliwe. Mimo to występują pewne odwołania do wcześniejszych rozdziałów, zwłaszcza jeśli chodzi o stosowanie biblioteki Adding Ajax, której rozwój jest opisany w książce. Cały materiał jest dostępny w kodzie źródłowym, który można pobrać z internetu. Zalecam zapoznanie się z rozdziałami 1. i 2. przed przejściem do następnych części.
Przedmowa
|
9
Poniżej znajduje się krótki przegląd wszystkich rozdziałów. Rozdział 1. „Przygotowania do wdrażania Ajaksa” Ten rozdział zawiera przegląd technologii Ajaksa, jednak przedstawia także znaczenie przygotowania strategii modyfikowania witryny przed przystąpieniem do pisania kodu. Obejmuje wagę określenia użytkowników oraz zawiera wskazówki dotyczące tego zadania. Opisuje także znaczenie standardów oraz uzyskania przed rozpoczęciem dodawania efektów Ajaksa pewności, że strona internetowa jest prawidłowa i stabilna. Po przeczytaniu tego rozdziału będziesz mógł rozpocząć dodawanie wszystkich efektów Ajaksa opisanych w następnych rozdziałach. Rozdział 2. „Elementy Ajaksa” Ten rozdział przedstawia szczegółowy opis istoty Ajaksa — korzystanie z obiektów XMLHttpRequest. Przedstawiono tu, jak zażądać usług sieciowych przy użyciu żądań GET i POST, a także jak używać dynamicznych skryptów do pobierania danych z innych domen. Rozdział opisuje różne formaty danych: HTML, XML, a także nowszą notację JSON (ang. JavaScript Object Notation). Omówiona jest także asynchroniczna natura Ajaksa, jak również pewne zagadnienia i problemy związane z wydajnością. Rozdział 3. „Narzędzia i pojęcia związane z Ajaksem” Ten rozdział przedstawia kilka z najważniejszych bibliotek Ajaksa, między innymi: Prototype, script.aculo.us, Rico i MochiKit. Choć większość przykładów przedstawionych w tej książce nie używa bibliotek zewnętrznych, w każdym rozdziale znajduje się fragment kodu korzystający z nich, dzięki czemu możesz zaznajomić się z danym efektem, nauczyć się rozszerzać biblioteki i tworzyć własne, a także poznasz problemy związane z tworzeniem rozwiązań przy użyciu wielu bibliotek Ajaksa. Rozdział 4. „Efekty interaktywne” Ten rozdział dotyczy interaktywności, jaką zapewnia Ajax, włączając w to używanie zdarzeń i metod ich obsługi w różnych przeglądarkach, a także techniki działające w przypadku stosowania kilku bibliotek. W tym rozdziale znajdują się także informacje o tworzeniu podpowiedzi, pobieraniu danych z systemu pomocy z zewnętrznych źródeł, przygotowywaniu efektu „zanikania” w celu sygnalizowania zmian, podglądzie w trybie „na żywo” i łączeniu tej ostatniej techniki z aktualizacjami. Rozdział 5. „Przestrzeń — ostateczna granica” Ten rozdział opisuje stronę internetową jako przestrzeń i przedstawia trzy popularne podejścia do zarządzania nią. Jedno z nich polega na stosowaniu kontrolki accordion, w której przestrzeń jest zwijana w pionie. Inne to strona z zakładkami, gdzie strony są wyświetlane po kliknięciu odpowiednich zakładek. Trzecia technika to nakładanie, kiedy to strona jest zakrywana wiadomościami, zdjęciami lub innymi materiałami. Ten rozdział opisuje także, jak przygotować kompletne „efekty”, dzięki czemu można korzystać z efektów z jednej biblioteki w wielu aplikacjach i na licznych stronach. Dowiesz się także, jak można zintegrować efekty z żądaniami usług sieciowych. Rozdział 6. „Dane dynamiczne” W tym rozdziale poznasz wiele szczegółów technicznych. Dotyczy on aktualizowania danych, włączając w to dodawanie nowych informacji, usuwanie ich i wprowadzanie zmian, a wszystko to na jednej stronie. Ten rozdział dotyczy głównie usprawniania istniejących aplikacji sieciowych, dlatego aktualizowanie przy użyciu formularzy i aktualizowanie za
10
|
Przedmowa
pomocą Ajaksa działają równolegle. Przedstawiono tu także technikę „zanikania”, która poprawia styl aplikacji i zapewnia informacje zwrotne użytkownikom. Rozdział obejmuje także zagadnienia związane z wydajnością i bezpieczeństwem wynikające z dostępu do bazy danych za pomocą Ajaksa, na przykład w celu zastosowania aktualizacji „na żywo” i efektów takich jak sortowanie za pomocą przeciągania. Rozdział 7. „Historia, nawigacja i miejsca w aplikacjach jednostronicowych” W tym rozdziale opisany jest wpływ Ajaksa na działanie sieci WWW, włączając w to brak funkcjonalności przycisku Wstecz, utratę historii w przeglądarce, dynamiczne efekty znikające po odświeżeniu strony i możliwość tworzenia odnośników oraz zakładek dla ajaksowych „stron”. Ten rozdział obejmuje podejścia, które można zastosować w celu przywrócenia wielu z utraconych funkcji WWW, a także opisuje, jak rozwinąć w sobie zdolność oceny tego, ile Ajaksa można użyć, aby jednocześnie zachować najbardziej wartościowe cechy środowiska WWW. Rozdział 8. „Dodawanie zaawansowanych efektów wizualnych” Ten rozdział to czysta przyjemność. Ciężko pracowałeś, więc teraz możesz wziąć do ręki pędzel lub zestaw do malowania palcami i trochę się pobawić. W tym rozdziale opisano pewne zaawansowane efekty CSS, włączając w to przeciągane „suwaki”, stronicowanie, korzystanie z języka SVG oraz obiektu Canvas. Choć obsługa tych dwóch ostatnich technik nie jest powszechna, wciąż się poprawia, a zastosowanie ich do usprawnienia bardziej tradycyjnych sposobów prezentacji danych może okazać się ciekawe i skuteczne. Rozdział 9. „Witryny typu mashup” Ten rozdział dotyczy aplikacji typu mashup. Jednym z najciekawszych aspektów Ajaksa jest możliwość dodawania usług sieciowych — własnych i innych producentów — i łączenie danych bezpośrednio na stronach internetowych na wiele różnych sposobów. Przykłady z tego rozdziału oparte są na mapach z Google’a, danych z witryny Flickr oraz informacjach z blogów z Technorati i łączą je przy użyciu eleganckiego interfejsu w postaci strony z zakładkami. Pokazano tu, jak można zaimplementować to rozwiązanie tak, aby działało zarówno z włączoną, jak i wyłączoną obsługą skryptów. Rozdział 10. „Skalowanie, infrastruktura i tworzenie witryn od podstaw” Ten rozdział pozwala wziąć długi oddech po wielu fragmentach kodu, a także po raz drugi przyjrzeć się zagadnieniom dotyczącym wydajności, architektury i bezpieczeństwa. Poruszam tu tematy związane z prywatnością usług sieciowych i wymaganiami stawianymi przez zasoby rozproszone, a także opisuję, jak ściśle należy powiązać komponenty serwerowe z klienckimi. Ten rozdział przedstawia także tworzenie aplikacji ajaksowych od podstaw i zawiera przegląd wielu platform dostępnych w różnych językach, takich jak: Java, języki z rodziny .NET, PHP, Perl, Ruby i Python. PHP to prawdopodobnie najbardziej popularny język programowania używany do tworzenia aplikacji sieciowych, dlatego używam go we wszystkich komponentach działających po stronie serwera aplikacji ajaksowych. Wynika to z tego, że choć niektórzy Czytelnicy mogą znać język Python, inni — Ruby, jeszcze inni — języki z rodziny .NET lub Javę, najwięcej osób zna PHP, a jeśli nawet ktoś nie zna tego języka, może nauczyć się go szybciej niż innych. Ponadto wszystkie znane mi firmy świadczące usługi hostingowe zapewniają obsługę PHP.
Przedmowa
|
11
Konwencje używane w tej książce W tej książce używane są następujące konwencje typograficzne: Pogrubienie Służy do przedstawiania nowych i ważnych pojęć. Kursywa Służy do przedstawiania adresów URL, nazw i rozszerzeń plików. Czcionka o stałej szerokości
Oznacza kod w szerokim znaczeniu tego słowa. Obejmuje to polecenia, opcje, przełączniki, zmienne, atrybuty, klucze, żądania, funkcje, typy, klasy, przestrzenie nazw, metody, moduły, właściwości, parametry, wartości, obiekty, zdarzenia, metody obsługi zdarzeń, znaczniki języków XML i HTML, makra, zawartość plików oraz dane wyjściowe poleceń. Pogrubiona czcionka o stałej szerokości
Przedstawia polecenia i inny tekst, które użytkownik powinien wpisać w dosłownej postaci. Kursywa o stałej szerokości
Określa tekst, który należy zastąpić wartościami podanymi przez użytkownika lub wynikającymi z kontekstu. Ta ikona wyróżnia wskazówkę, sugestię lub ogólny komentarz.
Ta ikona oznacza ostrzeżenie lub uwagę.
W książce wymienione są witryny i strony internetowe, które pomogą Czytelnikom zlokalizować pomocne informacje dostępne w internecie. Zwykle podany jest zarówno adres (URL), jak i nazwa (tytuł lub nagłówek) strony. Niektóre adresy są dość skomplikowane, jednak prawdopodobnie strony można otworzyć łatwiej niż za ich pomocą — przy użyciu ulubionej wyszukiwarki, na podstawie nazwy strony, zwykle wpisując ją w cudzysłowach. Nazwa strony może także pomóc, jeśli znalezienie jej na podstawie adresu okaże się niemożliwe. Strona mogła zostać przeniesiona w inne miejsce, dlatego użycie nazwy może być jedynym sposobem na jej odszukanie.
Korzystanie z przykładowego kodu Ta książka ma pomóc w wykonywaniu zadań programistycznych. Mówiąc ogólnie, możesz korzystać z przykładowego kodu we własnych programach i dokumentacji. Nie musisz kontaktować się z wydawnictwem w celu uzyskania zezwolenia, chyba że powielasz duże fragmenty kodu. Na przykład napisanie programu, który używa kilku fragmentów kodu z tej książki, nie wymaga pozwolenia. Sprzedaż lub dystrybucja płyt CD-ROM z przykładami z książek wydawnictwa O’Reilly wymaga zezwolenia. Odpowiedź na pytanie przez zacytowanie książki
12
|
Przedmowa
i przytoczenie przykładowego kodu nie wymaga pozwolenia. Dołączenie dużych ilości kodu z tej książki do dokumentacji produktu wymaga zezwolenia. Mile widziane, choć niewymagane, jest podawanie pochodzenia kodu. Zwykle dane obejmują tytuł, autora, wydawcę i numer ISBN książki, na przykład: „Adding Ajax, Shelley Powers, prawa autorskie Shelley Powers 2007, 978-0-596-52936-9”. Jeśli chcesz wykorzystać kod w sposób, który wymaga zezwolenia, skontaktuj się z wydawnictwem, pisząc na adres [email protected].
Podziękowania Książki tego typu nie powstają dzięki wyobraźni i pracy samego autora; są one efektem wysiłku wielu osób, włączając w to te, które zdefiniowały omawiane zagadnienia, rozwiązywały problemy i tworzyły biblioteki. Chcę podziękować autorom wszystkich bibliotek wymienionych w tej książce za bezpłatne i chętne ich udostępnienie. Większość z tych programistów nie otrzymuje żadnych korzyści finansowych w zamian za włożoną pracę, a mimo to udostępniają dopracowane witryny, dokumentację, poprawki błędów i nowe wersje, a wszystkie te elementy można pobrać i wykorzystać w aplikacjach. Chcę także podziękować Jonowi Sullivanowi z Public Domain Photos (http://pdphoto.org/About.php) za wszystkie zdjęcia, które bezpłatnie udostępnił, włączając w to fotografię drinka „appletini” wykorzystaną w aplikacji z rozdziału 2. Dziękuję także osobom, które współpracowały ze mną przy tworzeniu tej książki, włączając w to recenzentów: Elaine Nelson, Dare’a Obasanjo, Rogera Johanssona, Jesse Skinnera, Roya Owensa, Anthony’ego Holdera III, Anne Zelenkę i Kathy Sierrę. Chcę także podziękować redaktorowi Simonowi St. Laurentowi. Jest to moja trzecia napisana samodzielnie książka wydana przez wydawnictwo O’Reilly, a ci, którzy mnie znają, niech podziwiają Simona za jego cierpliwość. Dziękuję także osobom, które pomogły mi złożyć tę książkę w jedną całość: Lucie Haskins, Rebie Libby, Laurelowi Rumie i Amy Thomson. Niniejsza książka wiele zyskała dzięki wsparciu, które otrzymałam.
Przedmowa
|
13
14
|
Przedmowa
ROZDZIAŁ 1.
Przygotowania do wdrażania Ajaksa
Ajax umożliwia dodawanie do aplikacji sieciowych natychmiastowych informacji zwrotnych i zapewnianie ich elastyczności na niedostępnym dotąd poziomie. Czasy, kiedy formularze na stronach internetowych dawały wyniki dopiero po przesłaniu ich na serwer, powoli odchodzą w przeszłość. To samo dotyczy statycznych stron internetowych z niezmiennym kodem HTML (ang. Hypertext Markup Language), który umożliwia tylko odczyt i nie reaguje na działania użytkownika. Obecnie projektanci mogą bezpośrednio na stronie udostępniać funkcjonalność, która kiedyś wymagała przesłania strony na serwer i z powrotem. Za pomocą Ajaksa komponenty strony można w razie potrzeby zwijać, rozwijać oraz wypełniać na żądanie, a prezentowanie informacji zwrotnych użytkownikom obejmuje techniki od zanikania kolorów do systemu starannie rozmieszczonych komunikatów. Do kierowania żądań do usług sieciowych można używać wszystkich elementów strony, co prowadzi do tworzenia lepiej reagujących stron i zmniejsza frustrację użytkowników. Efekt to szybciej reagująca i bardziej zwięzła aplikacja. W aplikacjach ajaksowych można także używać szeregu bibliotek przeznaczonych dla Ajaksa, usług sieciowych takich firm, jak Google, Amazon, Adobe, Microsoft czy Yahoo!, a także bibliotek niezależnych, na przykład Prototype, Dojo, Mochikit i jQuery. Często można korzystać z różnych istniejących usług sieciowych, ponieważ albo współdziałają one z Ajaksem, albo można je łatwo zmodyfikować pod tym kątem. Ajax jest wyjątkowy, ponieważ stanowi nowatorskie podejście oparte na dość dojrzałych technologiach. Nawet niektórych dość skomplikowanych efektów można używać wygodnie i bezpiecznie. Programiści mogą pracować spokojnie, wiedząc, że każdy efekt powinien działać w większości współczesnych przeglądarek, jeśli nie we wszystkich. Programowanie za pomocą Ajaksa stawia pewne wyzwania, jednak tak naprawdę jest to technologia typu „80 — 20” — 80 procent osób zainteresowanych tą technologią potrzebuje jedynie około 20 procent jej możliwości. Duża część dyskusji na temat Ajaksa dotyczy granic jego zastosowań. Z tego powodu może się wydawać, że programiści używają bardzo złożonej platformy. Może to przytłaczać, jeśli projektant dopiero zastanawia się nad tym, jak użyć Ajaksa we własnych witrynach, jednak zwykle stosowanie grupy technologii ajaksowych jest dość proste, a obsługi wymaga tylko kilka elementów. Nie trzeba spędzać długich miesięcy, czytając wszystkie książki o Ajaksie i wypróbowując każdą bibliotekę, aby dodać wydajne i ciekawe funkcje Ajaksa, jeśli — a jest to bardzo istotne
15
— programista rozpocznie od prostych efektów, a dopiero potem zacznie używać bardziej rozbudowanych. Ta książka była pisana z założeniem, że jesteś jednym z bardziej szczęśliwych programistów poznających Ajaksa, ponieważ masz już gotową aplikację sieciową lub stronę internetową i chcesz dodać do niej nowe efekty. Pozwala to stawiać programiście pewne kroki na ścieżce stopniowego usprawniania, co pozwala automatycznie stać się dobrym programistą Ajaksa. Pojęcie stopniowego usprawniania zostało po raz pierwszy użyte przez Stevena Champeona w serii artykułów w witrynie Webmonkey i w prezentacji SXSW (więcej informacji historycznych na ten temat zawiera Wikipedia — http://en.wikipedia.org/wiki/Progressive_enhancement). To podejście oznacza, że programista powinien rozpocząć od utworzenia witryny lub aplikacji, która będzie uporządkowana, zgodna ze standardami i powszechnie dostępna, a dopiero potem może dodać efekty specjalne, używając technik takich jak Ajax. Inni programiści i projektanci, na przykład Dave Shea i Jeremy Keith, rozszerzyli to podejście. Artykuł w Wikipedii dotyczący stopniowego usprawniania zawiera następujące wskazówki: • Cała podstawowa treść powinna być dostępna we wszystkich przeglądarkach. • Cała podstawowa funkcjonalność powinna być dostępna we wszystkich przeglądarkach. • Stosowane znaczniki powinny oszczędnie i semantycznie organizować treść. • Zewnętrzne arkusze CSS powinny określać rozbudowany układ. • Dyskretny zewnętrzny kod JavaScript powinien obsługiwać rozszerzone działania. • Należy uwzględniać preferencje ustawione w przeglądarce przez użytkownika końcowego.
Można także użyć wyrażenia „eleganckie ograniczanie” na określenie stopniowego usprawniania. Oznacza ono to samo: strony i aplikacje będą współdziałać ze wszystkimi przeglądarkami, myszami i klawiaturami, a także niezależnie od dostępności programu odczytującego tekst z ekranu, czy włączonych skryptów. Podpisuję się pod podejściem stopniowego usprawniania (eleganckiego ograniczania czy dyskretnych skryptów — nazwa nie ma tu znaczenia), a przykłady w tej książce są z nim zgodne. Choć wprawdzie nie można zastosować wszystkich rodzajów efektów w środowisku bez obsługi skryptów (na przykład efektów ze strony Google Maps, które wymagają Ajaksa), uważam, że nie należy ograniczać dostępu do podstawowej zawartości oraz funkcjonalności witryny tylko ze względu na chęć dodania ładnie wyglądających elementów. Kiedy programista rozpoczyna stosowanie Ajaksa i zastanawia się, których efektów użyć, powinien pamiętać, że najprostsze efekty zwykle dają największe zyski, jeśli uwzględnić czas pracy i włożony wysiłek. Większość projektantów nie próbuje odtworzyć w przeglądarkach działania programów stacjonarnych, a po prostu chce dodać lub rozszerzyć podstawową funkcjonalność istniejących stron internetowych i aplikacji sieciowych. Należą oni do szczęściarzy, ponieważ połowa zadania — udostępnienie podstawowej, nieajaksowej funkcjonalności — została już ukończona. Teraz rozpoczyna się zabawa. Nazwa Ajax została po raz pierwszy użyta przez Jesse Jamesa Garretta w artykule Ajax: A New Approach to Web Applications (http://www.adaptivepath.com/publications/ essays/archives/000385.php). Która wersja jest poprawna — Ajax czy AJAX? Czy jest to jedno słowo, czy akronim od „Asynchronous JavaScript and XML”? Garrett użył pojęcia AJAX jako wygodnej
16
|
Rozdział 1. Przygotowania do wdrażania Ajaksa
nazwy obejmującej zestaw technologii omówionych w niniejszym rozdziale. Jednak to pojęcie wykracza poza samą technologię i wyznacza całkiem nowe spojrzenie na sieć WWW oraz przekraczanie ograniczeń związanych z tradycyjnymi stronami. Obecnie bardzo często używa się nazwy „Ajax” na określenie tego ogólnego podejścia. Dla uproszczenia w tej książce używam nazwy „Ajax”, która faktycznie stała się standardem.
Technologie ajaksowe Ajax nie opiera się na nowych technologiach, a na usprawnieniach i dojrzewaniu istniejących narzędzi oraz środowisk. Kiedy Jesse James Garrett po raz pierwszy definiował to podejście, wymienił następujące powiązane z nim technologie: • XHTML (ang. Extensible HTML) i CSS (ang. Cascading Stylesheets), które służą do określa-
nia układu strony i jej wyświetlania; • XML (ang. Extensible Markup Language) i XSLT (ang. Extensible Stylesheet Language Trans-
formations) — do wymiany danych; • DOM (ang. Document Object Model) — do zapewniania interaktywności; • Wyspecjalizowany obiekt XMLHttpRequest — do obsługi interakcji między klientem a ser-
werem; • Język JavaScript (lub JScript) — do łączenia poszczególnych elementów.
Wszystkie te technologie są dostępne w tej lub innej postaci od wielu lat. Choć największy nacisk w przypadku Ajaksa kładziony jest na żądanie usług sieciowych, to podejście wykracza poza prosty dostęp do danych znajdujących się na stronie. Dotyczy także sposobu traktowania stron internetowych i korzystania z istniejących oraz nowych technologii w celu zwiększania ich interaktywności i skracania czasu reakcji. Obejmuje to nowe spojrzenie na język JavaScript i złożone biblioteki tego języka, jak również świeże spojrzenie na techniki dynamicznego HTML z lat 90. To myślenie wykracza poza ramy strony internetowej, i to niezależnie od tego, czy programista używa tych technik do tworzenia aplikacji odpowiadającej programom stacjonarnym czy — jak w przypadku tej książki — do dodawania użyteczności, iskry i spontaniczności do istniejących stron internetowych.
Naturalny rozwój Ajax to wynik naturalnego rozwoju funkcjonalności po stronie klienta. Gdyby Garrett nie ujął tego podejścia za pomocą jednego słowa, prawdopodobnie zrobiłby to ktoś inny. Społeczność projektantów stron zmierzała już w kierunku eksplozji zainteresowania połączeniem omawianych tu technologii. U zarania internetu cała funkcjonalność aplikacji znajdowała się po stronie serwera. Netscape wprowadził skrypty uruchamiane po stronie klienta, udostępniając język JavaScript, który umożliwiał pewną interaktywność po stronie klienta, jednak same strony internetowe wciąż były statyczne. Następne przeglądarki, na przykład Internet Explorer Microsoftu, miały własną implementację skryptów, aż w końcu wysiłki skupiły się na utworzeniu minimalnej definicji standardów skryptów, co zaowocowało powstaniem języka ECMAScript.
Technologie ajaksowe
|
17
Jednak dopiero praca W3C nad specyfikacjami DOM i CSS pozwoliła na tworzenie bardziej dynamicznych stron i umożliwiła używanie skryptów do modyfikowania, przenoszenia, ukrywania, tworzenia i usuwania poszczególnych elementów stron internetowych. W tym czasie efekty dynamicznych stron internetowych określono mianem dynamicznego HTML (DHTML), a obecnie techniką, która pozwala stosować to podejście, jest Ajax. Obok usprawnień w zakresie skryptów i warstwy prezentacji trwały także prace nad utworzeniem języka znaczników, który byłby niezależny od wszelkich specyficznych języków znacznikowych i umożliwiałby rozszerzanie, a jednocześnie jego stosowanie nie byłoby zbyt skomplikowane. Efekt tych prac, XML, został przedstawiony w 1998 roku. Od tego czasu postęp miał miejsce w wielu kierunkach i nastała era wielkiego rozwoju funkcjonalności. Ta dywergencja w naturalny sposób prowadziła do braku zgodności między przeglądarkami, a niektóre z niespójności występują do dziś. Wśród obiektów specyficznych dla przeglądarek był jeden, który stał się istotą Ajaksa — obiekt ActiveX z przeglądarki Internet Explorer 5 Microsoftu, Microsoft.XMLHTTP. Choć mógł on pozostać obiektem specyficznym dla przeglądarki Internet Explorer, przyciągnął zainteresowanie innych producentów przeglądarek, a na potrzeby Mozilli utworzono odmianę tego obiektu — XMLHttpRequest. Obiekt XMLHttpRequest jest wyjątkowy, ponieważ umożliwia bezpośredni dostęp do asynchronicznych usług sieciowych z poziomu strony internetowej. Zamiast przesyłać dane na serwer za pomocą formularza, a następnie wyświetlać wyniki na odrębnej stronie, dzięki obiektom XMLHttpRequest skrypty mogą wywoływać funkcje znajdujące się na serwerze i przetwarzać zwracane przez nie wyniki bez konieczności odświeżania strony. Asynchroniczny aspekt usług sieciowych oznacza, że strona nie jest zablokowana w czasie oczekiwania na odpowiedź usługi. Garrett wspominał także o zastosowaniu języka XSLT dla danych XML w celu wyświetlania odpowiedzi na żądania skierowane do usług sieciowych. Jednak bardziej popularne podejście polega na manipulowaniu istniejącymi elementami strony i obsłudze odpowiedzi na żądania za pomocą modelu DOM, nowej notacji obiektowej, na przykład JSON (ang. JavaScript Object Notation), oraz danych XML. Podsumowując — rozwój funkcjonalności stron internetowych doprowadził od prostych, statycznych stron do pełni możliwości dostępnych obecnie za pomocą Ajaksa. Na tej drodze znajdowało się kilka przystanków: • statyczne strony internetowe, na których do obsługi prezentacji i formatowania służył ję-
zyk HTML; • interaktywna funkcjonalność zapewniana przy użyciu języka JavaScript i jego odmian, na
przykład JScript; • rozdzielenie warstwy prezentacji od znaczników za pomocą arkuszy CSS; • umożliwienie dynamicznego manipulowania elementami strony za pomocą skryptów; • rozszerzalne znaczniki dostępne w XML; • żądania kierowane do usług sieciowych i przetwarzanie danych XML na stronie.
Technologie — spojrzenie książkowe W czasie pisania tej książki w większości aplikacji ajaksowych stosowany był język JavaScript oparty na specyfikacji ECMA 262 języka ECMAScript. Jest to w mniejszym lub większym stop18
|
Rozdział 1. Przygotowania do wdrażania Ajaksa
niu odpowiednik języka JScript Microsoftu z przeglądarek Internet Explorer 6.x i 7. Przypomina on także język JavaScript 1.5 z przeglądarek opartych na Gecko, takich jak Firefox oraz Camino, i jest obsługiwany w przeglądarkach Opera (9.x), Safari 1.2, Safari WebKit (odmiana rozwojowa przyszłych wersji Safari), Konqueror i innych współczesnych przeglądarkach graficznych. Ta odmiana języka JavaScript jest także obsługiwana w innych agentach klienckich, na przykład w urządzeniach BlackBerry i iPhone. Następna technologia, DOM, umożliwia dostęp do poszczególnych elementów strony oraz ich atrybutów. Techniki i przykłady prezentowane w tej książce są oparte na modelu BOM (ang. Browser Object Model), który zwykle traktuje się jako model DOM, poziom 0, a także na modelu DOM, poziom 1 i 2. Te modele są obsługiwane, choć z różnym powodzeniem i w odmiennym zakresie, przez wymienione wcześniej przeglądarki. Analizując Ajaksa, zwrócę uwagę na różnice występujące pod tym względem między poszczególnymi przeglądarkami. Ponieważ ta książka dotyczy głównie dodawania Ajaksa do istniejących witryn i aplikacji (po stronie klienta), postaram się ograniczyć analizę strony serwera do minimum. Jednak tam, gdzie to konieczne, do przedstawiania funkcjonalności używanej po stronie serwera posłuży język PHP. Jest to jeden z najprostszych i najczęściej używanych języków działających po stronie serwera. Wszystkie przykłady w tej książce są zgodne z językiem XHTML. Wyjątkiem są sytuacje, kiedy XHTML uniemożliwia zastosowanie efektu Ajaksa. Na przykład Google Maps nie współdziała z XHTML, a format strony to HTML 4.01 Strict. Ponadto wszystkie przykłady są uruchamiane w trybie standardowym (a nie quirks) przez dołączenie do strony nagłówka deklaracji DOCTYPE — albo HTML 4.01 Strict, albo XHTML 1.0 Strict. Tryb quirks umożliwia zachowanie zgodności wstecz ze starszymi przeglądarkami i arkuszami stylów. Na przykład Internet Explorer użyje starszego modelu „pudełkowego” do obsługi stylów CSS, jeśli programista nie poda nagłówka DOCTYPE lub ten nagłówek nie określa standardowego typu dokumentu.
Zmiana rozszerzenia strony na .xhtml i dodanie nagłówka DOCTYPE może nie wystarczyć, aby uruchomić pewne przykłady w niektórych przeglądarkach. Na przykład Internet Explorer nie obsługuje typu MIME application/xhtml+xml, a Firefox używa domyślnie typu HTML. W środowisku Apache’a można zastosować pewną sztuczkę, aby poprawnie wyświetlać dokumenty XHTML w różnych przeglądarkach. Wymaga to wprowadzenia w pliku .htaccess następujących zmian: AddType text/html .xhtml RewriteEngine on RewriteBase / RewriteCond %{HTTP_ACCEPT} application/xhtml\+xml RewriteCond %{HTTP_ACCEPT} application/xhtml\+xml\s*;\s*q=0 RewriteCond %{REQUEST_URI} \.xhtml$ RewriteCond %{THE_REQUEST} HTTP/1\.1 RewriteRule .* - [T=application/xhtml+xml]
Jeśli plik .htaccess jest zastrzeżony lub nieobsługiwany na danym serwerze sieciowym, można określić typ MIME w aplikacjach, które zwracają strony. Na przykład można wprowadzić następujące zmiany w kodzie PHP:
Technologie ajaksowe
|
19
} else { header("Content-type: text/html"); } ?>
Inny sposób umożliwiający poprawne funkcjonowanie przykładów to po prostu użycie rozszerzenia .html. To rozwiązanie działa w przypadku wszystkich przykładów oprócz plików z kodem PHP oraz zagnieżdżonym kodem SVG (ang. Scalar Vector Graphics). Język SVG jest opisany w rozdziale 8. Wszystkie przykłady używają CSS 1 i 2. Aby uniknąć stosowania obejść niezbędnych ze względu na specyfikę pewnych przeglądarek, w przykładach zastosowano funkcje CSS obsługiwane przez wszystkie przeglądarki. Aby zagwarantować, że przykłady i znaczniki HTML oraz XHTML są prawidłowe, kod został sprawdzony za pomocą następujących narzędzi: • Total Validator (http://www.totalvalidator.com), • walidatora kodu XHTML i HTML w witrynie W3C (http://validator.w3.org), • usług walidacji arkuszy CSS w witrynie W3C (http://jigsaw.w3.org/css-validator), • walidatora dostępności w witrynie Cynthia Says, zgodnie ze wskazówkami ze specyfika-
cji Section 508 (http://contentquality.com). Jeśli do tworzenia stron używasz przeglądarki Firefox, z wszystkich tych usług możesz skorzystać poprzez pasek Web Developer Toolbar for Firefox lub za pomocą rozszerzenia Total Validator. Choć przykłady działają we wszystkich docelowych przeglądarkach (opisanych dalej), na potrzeby tworzenia stron zalecam i zakładam używanie Firefoksa z zainstalowanym rozszerzeniem Firebug jako głównej przeglądarki. Ta przeglądarka działa we wszystkich środowiskach docelowych i nie znalazłam innego narzędzia tak wydajnego w zakresie rozwoju i diagnozowania kodu jak Firebug. Jestem nim tak zachwycona, że uważam je za kluczowe przy tworzeniu rozwiązań opartych na Ajaksie. Przeglądarkę Firefox można pobrać pod adresem http://www.mozilla-europe.org/pl/. Rozszerzenie Firebug jest dostępne na stronie http://getfirebug.com/.
Początkowe porządkowanie Dodawanie Ajaksa do witryny to okazja do od dawna planowanego uporządkowania witryny, na które jednak ciągle brakowało czasu. Dynamiczne efekty i stosowanie przestarzałego kodu HTML nie idą w parze, szczególnie jeśli programista chce przenosić obiekty, zwijać i rozwijać kolumny lub umożliwić edycję oraz wyświetlanie pomocy w miejscu. Nat Torkington na blogu O’Reilly Radar opisał podjętą przez Marka Lucovsky’ego próbę dodania tej samej funkcjonalności Ajaksa do dwóch bardzo odmiennych witryn internetowych. Jedna z nich była uporządkowana, podczas gdy druga miała bardzo poważny „bagaż” (oryginalny tekst jest dostępny na stronie http://radar.oreilly.com/archives/2006/08/the_value_of_web_ standards.html):
20
|
Rozdział 1. Przygotowania do wdrażania Ajaksa
W czasie konferencji OSCON Mark Lucovsky z Google’a udostępnił fragment kodu HTML, który umożliwiał dodanie do witryny internetowej OSCON-u prostej kontrolki do wyszukiwania obiektów na mapie. Dzięki temu uczestnicy konferencji mogli łatwo znaleźć pobliskie restauracje, hotele, parki, bary itd. Był to doskonały pomysł, a przy okazji elegancka demonstracja ajaksowego interfejsu API do wyszukiwania, nad którym Mark pracował. Następne wystąpienie Marka miało miejsce na konferencji Search Engine Strategies (SES), dlatego Mark chciał wykorzystać moduł „znajdź obiekty w okolicach sali konferencyjnej”. Jednak szybko natrafił na nieuporządkowany kod witryny internetowej SES. Choć dodanie kodu JavaScript do strony internetowej O’Reilly było kwestią sekund, zastosowanie go w witrynie SES okazało się prawdziwym wyzwaniem. Było to ukoronowaniem dużej ilości pracy, którą zespół projektowy w O’Reilly włożył w zmianę projektu stron pod kątem zgodności z XHTML i CSS. Jest to także potwierdzenie wartości standardów. Czasem programiści tracą szerszą perspektywą, próbując odnaleźć drogę w labiryncie podobnych do siebie przestrzeni nazw. Celem stosowania standardów jest nie tylko zagwarantowanie, że przeglądarka będzie mogła wyświetlić dane strony. Standardy umożliwiają także utworzenie za pomocą stron platformy, na której można rozwijać witrynę. Posklejana naprędce platforma prowadzi do powstawania kruchych i wrażliwych na błędy rozszerzeń.
Ajax w dużym stopniu zależy od CSS, a w jeszcze większym — od modelu DOM. W aplikacjach ajaksowych można uzyskać dostęp do poszczególnych elementów i przenosić je, tworzyć oraz usuwać na żywo. Z powodu zależności od modelu DOM nie można po prostu zagnieździć skryptu na stronie, dodać kilku atrybutów ID do elementów i oczekiwać, że efekty będą funkcjonować. Ajax naprawdę wymaga uporządkowania strony przed rozpoczęciem dodawania efektów. Zanim programista przystąpi do pracy i zacznie zmieniać kod, aby dodać efekty Ajaksa, warto, aby najpierw sprawdził stronę w kilku walidatorach dostępnych w witrynie W3C (i w innych miejscach). Pozwoli to wykryć i rozwiązać istniejące problemy. Walidacja może pomóc w określeniu, ile pracy wymagają strony, a także pozwolić przygotować plan wprowadzania zmian. Nawet jeśli programista zamierza zmienić projekt strony, odkryje, że łatwiej jest przejść z jednego projektu do innego, niż zacząć wszystko od nowa.
Walidatory kodu XHTML i HTML Przodkiem wszystkich walidatorów jest Markup Validation Service z witryny W3C. Aby go użyć, należy wpisać adres URL lub przesłać stronę internetową, a usługa wyświetli szczegółowy spis wszystkich nieprawidłowych komponentów (a także poprawnych, ale niezalecanych). Markup Validation Service obsługuje wiele typów dokumentów, na przykład XHTML 1.0 Transitional, HTML 4.01 Strict, XHTML 1.1, a nawet typy niestandardowe. Zwykle typ dokumentu jest określony w samym dokumencie. Służy do tego następująca składnia:
Można zmienić typ dokumentu w usłudze walidacyjnej, aby zobaczyć, czy strona spełnia warunki bardziej ścisłych specyfikacji. Można wybrać opcję wyświetlania kodu źródłowego z numerami wierszy (przydatne przy diagnozowaniu) lub użyć trybu pełnego, aby uzyskać bardziej szczegółowe informacje. Rysunek 1.1 przedstawia dane wyjściowe z omawianego walidatora zawierające kilka błędów.
Początkowe porządkowanie
|
21
Rysunek 1.1. Markup Validation Service z witryny W3C informuje o poprawności znaczników strony
Inny walidator to Total Validator Andy’ego Halforda. To narzędzie działa wolniej niż walidator z witryny W3C, ale w formularzu Advanced można podać adres e-mail, pod który zostaną przesłane wyniki. Przyczyną tak długiego działania walidatora Total Validator jest niezwykle rozbudowany zestaw opcji. Oprócz wykrywania błędów witryna wyszukuje także nieprawidłowe lub brakujące odnośniki i literówki (jest możliwość określenia języka i dostosowania działania programu do własnych potrzeb). Jest to wygodny sposób na dokładne sprawdzenie strony. Jedną z możliwości jest sprawdzanie dostępności witryny. Można wybrać jeden z wielu poziomów, włączając w to U.S. Section 508 i trzy poziomy W3C Web Accessibility Initiative (WAI). Inna opcja umożliwia określenie liczby sprawdzanych stron (do 20) oraz głębokości testów (zgodnie z mapą witryny). Można także pominąć określone ścieżki w obrębie witryny. To narzędzie wyświetla także zrzuty na podstawie przeglądarki, systemu operacyjnego i rozdzielczości ekranu. Jest to bardzo wygodna funkcja, jeśli programista nie ma dostępu do określonej przeglądarki w konkretnym systemie operacyjnym. Rysunek 1.2 przedstawia efekt uruchomienia omawianego walidatora dla strony głównej witryny wydawnictwa Helion. Zrzut został uzyskany przy użyciu przeglądarki Konqueror v3.4 w systemie Linux.
Walidatory kodu CSS Zwykle do przeglądania stron korzystam z Firefoksa i jest to także główna przeglądarka, której używam do testów. Wybrałam ją z powodu licznych rozszerzeń, włączając w to narzędzia do projektowania stron. Spośród nich warto wymienić wspomniane już rozszerzenie Firebug, które jest często używane w tej książce. Następne przydatne narzędzie to Web Developer Toolbar (https://addons.mozilla.org/firefox/60). 22
|
Rozdział 1. Przygotowania do wdrażania Ajaksa
Rysunek 1.2. Stosowanie wielu opcji walidatora Total Validator umożliwia dokładne sprawdzenie strony internetowej
Pasek Web Developer udostępnia między innymi listę rozwijaną z opcjami walidacji, którą można przeprowadzić dla dowolnej obecnie wyświetlanej strony. Opcja Validate HTML bazuje na omówionym w poprzednim punkcie walidatorze Markup Validation Service z witryny W3C. Dostępna jest także opcja, która umożliwia sprawdzenie arkuszy stylów danej strony za pomocą narzędzia W3C CSS Validator. Walidator kodu CSS przyjmuje adres URL witryny lub przesłany plik. Jeśli programista chce wypróbować kod CSS przed umieszczeniem go w arkuszu stylów i nie jest pewien, czy składnia jest prawidłowa, może po prostu wpisać ten kod i nakazać jego sprawdzenie. Jest to wygodna opcja. Wypróbowałam ją dla poniższego kodu, który pochodzi z arkusza stylów jednej z moich witryn: .comment-number { text-align: right; display: inline; font-size: 48pt; opacity: .3; filter: alpha(opacity=30); z-index: 3; padding-bottom: 0px; margin-bottom: 0px; }
Wyniki walidacji przedstawia rysunek 1.3. Jak widać, walidator odrzuca niestandardowe ustawienia przezroczystości.
Początkowe porządkowanie
|
23
Rysunek 1.3. Przetestowanie bloku CSS przy użyciu walidatora W3C CSS Validator pozwala sprawdzić, czy kod jest zgodny ze standardami
Sprawdzanie dostępności Dlaczego dostępność jest tak istotna? Słyszałam opinie, że jedynie 5 procent programów odczytujących tekst strony zostaje dotkniętych przez funkcjonalność stron, których autorzy nie stosują się do wytycznych dotyczących dostępności. Jednak pomijając zagadnienia etyczne, dostępność staje się w coraz większym stopniu wymuszana przez prawo. W wielu państwach wszystkie strony rządowe muszą spełniać warunki związane z dostępnością. Staje się ona istotna także w przypadku witryn komercyjnych. Obecnie toczy się sprawa przeciw korporacji Target (duża sieć sklepów działająca w Stanach Zjednoczonych i innych państwach), która została pozwana za to, że jej sklep internetowy nie był dostępny dla osób niepełnosprawnych. Sieć przegrała sprawę, a obecnie rozpatrywana jest apelacja. Spodziewam się, że podobne sytuacje będą się powtarzać. Doskonała jednostronicowa lista najlepszych artykułów na temat dostępności i Ajaksa jest dostępna w witrynie programu Stanford Online Accessibility (http://soap.standford. edu/show.php?contentid=65).
W odróżnieniu od walidacji kodu CSS lub XHTML, sprawdzanie, czy strona spełnia wytyczne związane z dostępnością, jest w równym stopniu osobistą interpretacją, jak zautomatyzowanym testem. Wyniki sprawdzania dostępności w witrynie Cynthia Says mogą być dość obszerne, a programista powinien zapoznać się z wytycznymi, na podstawie których przeprowadzany jest ten test. Jednym z najważniejszych aspektów korzystania z tej witryny jest wybór standardów, na których opiera się test. Może to być jedna z trzech grup wytycznych W3C WAI 1.0 — priorytet 1, 2 lub 3 — albo wytyczne U.S. Section 508. Więcej informacji o wytycznych W3C WAI 1.0 można znaleźć na stronie http://www. w3.org/TR/WCAG10/, a o specyfikacji Section 508 — pod adresem http://www.access-board.gov/sec508/standards.htm (część Subpart B).
Total Validator także sprawdza dostępność i również pozwala na wybór wytycznych WAI lub Section 508. Jednak nawet po przeprowadzeniu tych testów trzeba ręcznie sprawdzić efekty 24
|
Rozdział 1. Przygotowania do wdrażania Ajaksa
pracy pod kątem zgodności z wymogami. Elementów takich jak atrybut alt z sensownym opisem elementu image (co pomaga czytelnikom, którzy nie widzą rysunków, dowiedzieć się, co tracą) nie można sprawdzić w czasie automatycznych testów.
Przekształcanie tabel na układ strony oparty na CSS Łatwo jest wykryć problemy z układem strony, które powodują komunikaty o błędzie. Jednak niektóre zmiany na stronie, które są konieczne, aby witryna była optymalnie przygotowana do dodawania Ajaksa (a także w innych celach), nie powodują błędów w czasie walidacji ani ostrzeżeń związanych z dostępnością. Jedną z najczęściej stosowanych sztuczek przed wprowadzeniem stylów CSS było używanie tabel HTML do zarządzania zawartością strony niezależnie od rodzaju treści. Jednak tabele HTML były zaprojektowane w celu wyświetlania danych tabelarycznych, na przykład rekordów z baz danych. Dlatego wiele efektów Ajaksa trudno jest zastosować, jeśli układ jest oparty na tabelach. Jak przedstawiam to w dalszej części tego rozdziału oraz w rozdziale 6., aktualizowanie tabel dynamicznych jest trudniejsze niż modyfikowanie pojedynczych elementów div. Ponadto nie zawsze można przenieść wiersz tabeli lub przetworzyć jej komórkę jako odrębny obiekt. Tabele HTML doprowadzają do szaleństwa osoby wrażliwe na punkcie semantycznego podejścia do WWW. Jeśli programista zastosuje tabele HTML do wyświetlania wszystkich informacji na stronie internetowej, dane tabelaryczne nie będą się wyróżniać. Poniżej wymieniam kilka problemów związanych z umieszczaniem całej zawartości strony w tabelach: • Elementy tabel HTML zostały zaprojektowane z myślą o danych tabelarycznych. • Tabele HTML zwiększają złożoność użycia kodu JavaScript. • Elementy tabel HTML, takie jak wiersze i komórki, istnieją w obrębie pewnego schematu,
co sprawia, że niewygodnie jest ich używać pojedynczo.
• Elementów tabel HTML nie można łatwo zwijać, przenosić i usuwać bez wpływu na inne
elementy.
• Niektóre efekty oparte na języku JavaScript, na przykład stosowanie warstw, przezroczy-
stości i innych subtelnych mechanizmów, mogą negatywnie wpływać na stronę, jeśli programista użyje ich wraz z tabelami.
Tak więc jednym z podstawowych zadań związanych z przygotowaniem strony do zastosowania Ajaksa jest przekształcenie tabel HTML na prawidłowe elementy XHTML i zastosowanie stylów CSS. Modyfikacja strony przez użycie CSS ułatwia tworzenie efektów Ajaksa, a także zarządzanie stroną. Wbrew oczekiwaniom takie przekształcanie stron nie jest zbyt trudne. Kilka następnych punktów zawiera opis tego procesu, a także kilku innych zadań związanych z wprowadzaniem niezbędnych zmian. Tabele HTML zostały zaprojektowane do wyświetlania powiązanych danych, takich jak listy sklepów i ich lokalizacje czy wyniki badań. Używanie tabel do zarządzania ogólnym układem strony nie jest semantycznie poprawne. W książce czasem wspominam o podejściu semantycznym. Oznacza to, że elementy strony są używane w sposób zgodny z ich przeznaczeniem, a nie do formatowania. Takie postępowanie pomaga programom odczytującym zawartość ekranu i innym narzędziom dokładniej przetwarzać strony. Może to także pomóc w bardziej wydajnym przetwarzaniu danych botom internetowym, czyli zautomatyzowanym robotom internetowym.
Przekształcanie tabel na układ strony oparty na CSS
|
25
Typowy projekt strony internetowej wygląda następująco. Nagłówek i stopka mają szerokość całej strony lub jej zawartości, a ciało strony jest rozbite na dwa pionowe komponenty. Jeden z nich to pasek boczny, a drugi zawiera główną treść. Pasek może znajdować się po lewej lub po prawej stronie, a rozmiar strony można ograniczyć lub zwiększyć do szerokości okna przeglądarki. Używanie tabel HTML w takiej sytuacji jest proste. Należy utworzyć jedną tabelę z dwoma kolumnami i trzema wierszami oraz użyć atrybutu colspan do rozciągnięcia pierwszego i trzeciego wiersza na szerokość całej tabeli. Jest kilka różnych metod, których można użyć do uzyskania tego samego efektu za pomocą XHTML i CSS. Jedna z nich polega na utworzeniu trzech bloków przy użyciu elementów div, przy czym każdy z nich ma szerokość 100 procent zawierającego je kontenera, a bloki te należy umieścić jeden nad drugim, tak jak ilustruje to rysunek 1.4.
Rysunek 1.4. Strona przekształcona na układ oparty na XHTML i CSS, utworzona przy użyciu elementów div i właściwości float języka CSS
Aby utworzyć dwie kolumny w środkowym bloku, należy dodać dwa elementy div obok siebie, zamiast jeden nad drugim, oraz określić ich rozmiar tak, aby kolumna z treścią była szersza. Tę kolumnę należy wymienić jako pierwszą w znacznikach strony, aby programy odczytujące zawartość strony najpierw przetwarzały właśnie ją. Zwykle elementy div działają na poziomie bloku, co oznacza, że są rozmieszczane jeden nad drugim. Aby umieścić kolumny obok siebie, należy zastosować właściwość float języka CSS i swobodnie umieścić blok po lewej lub prawej stronie, co powoduje usunięcie danego elementu z pionowego układu strony. Jeśli pasek boczny ma się znajdować po lewej stronie, należy
26
|
Rozdział 1. Przygotowania do wdrażania Ajaksa
ustawić wartość jego atrybutu float na left, a wartość tego atrybutu dla kolumny z treścią — na right. Listing 1.1 zawiera kod całej strony. Listing 1.1. Przekształcona strona XHTML z dwoma kolumnami CSS zamiast tabel
Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi.
Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat.
Opcja 1
Opcja 2
Aby usunąć właściwość float ze stopki, aby była ona widoczna pod paskiem bocznym i blokiem z treścią, a nie obok nich, należy ustawić właściwość clear języka CSS na both. Patrząc na stronę widoczną na rysunku 1.4, warto zwrócić uwagę na to, że lewa kolumna (pasek boczny) nie rozciąga się na całą długość, jeśli jej zawartość nie jest wystarczająco długa. Jest to wada używania kodu XHTML z określaniem pozycji za pomocą języka CSS — tło kolumny rozszerza się jedynie wraz z jej treścią.
Przekształcanie tabel na układ strony oparty na CSS
|
27
Jednym z rozwiązań tego problemu jest ustawienie tego samego koloru tła dla wszystkich kontenerów. Powoduje to ukrycie różnic w długości kolumn. Inne podejście, które zresztą preferuję, polega na utworzeniu rysunku o tej samej szerokości i kolorze, co pasek boczny, a następnie użyciu go jako obrazu tła kontenera zawierającego dany pasek. Aby użyć tej metody, należy ustawić atrybuty repeat tła tak, aby rysunek nie powtarzał się w poziomie lub pionie, oraz umieścić go w miejscu, w którym rozpoczyna się pasek boczny. Można także ustawić kolor tła kontenera z paskiem na kolor kolumny z treścią. Spowoduje to wypełnienie obszaru, którego nie obejmuje rysunek tła. Po tej modyfikacji obie kolumny będą wyglądały tak, jakby miały tę samą długość. Zmodyfikowany styl wygląda tak: #wrapper { background-image: url(ajaxbackground.jpg); background-postion: top left; background-repeat: repeat-y; background-color: #f00; width: 800px; margin: 0 auto; }
Rysunek 1.5 przedstawia wygląd strony po zastosowaniu zmodyfikowanego stylu kontenera.
Rysunek 1.5. Zmiana ustawień stylu kontenera powoduje, że długość obu kolumn wydaje się taka sama
To tylko jedno podejście. Możliwych jest wiele rozwiązań opartych na CSS. Technika opisana wcześniej to układ stały (ang. fixed). W przypadku układu płynnego (ang. fluid) rozmiar kolumn jest zmienny i dopasowuje się do wielkości strony, a w układzie elastycznym (ang. elastic) rozmiar kolumn dostosowuje się do strony, nie przekracza jednak określonej maksymal-
28
|
Rozdział 1. Przygotowania do wdrażania Ajaksa
nej szerokości. Aby znaleźć więcej informacji o układach opartych na CSS, wystarczy wpisać w przeglądarce wyrażenie „układ CSS” (ang. CSS layout). Przekształcenie układu witryny z opartego na tabelach HTML na oparty na stylach CSS może być trudne, ponieważ tabele są bardzo wygodne i łatwe w użyciu, a język CSS może być bardziej skomplikowany. Ponadto strona przejdzie walidację jako dokument XHTML, nawet jeśli programista użyje tabel HTML. Jednak niektóre elementy strony zostaną uznane za nieprawidłowe i będą wymagały wprowadzenia zmian. Następny punkt przedstawia najczęściej spotykane elementy tego typu.
Ciąg dalszy zmian — element po elemencie Przekształcanie stron tak, aby były oparte na XHTML i CSS, to jedna duża zmiana, jednak jest też wiele mniejszych, łatwiejszych poprawek, które należy wprowadzić, zanim strona przejdzie walidację jako dokument XHTML. Te zmiany powodują, że elementy będą dostępne dla skryptów, które są przedstawione w tej książce. Jest wiele narzędzi, które mogą pomóc w porządkowaniu stron internetowych i przekształcaniu ich z HTML na XHTML. Popularnym programem tego typu jest HTML Tidy, dostępny na stronie http://tidy.sourceforge.net/.
W czasach pierwszych przeglądarek jednym z pierwszych elementów, które projektanci chcieli kontrolować, była czcionka. Gdyby cała strona była czarno-biała, a rodzina i rozmiar czcionki zależały jedynie od typu elementu, witryny byłyby zbyt jednorodne i bardzo nieciekawe (choć praca projektanta byłaby łatwa, a sama witryna — w pełni dostępna). Aby umożliwić dostosowanie stron, wraz z rozwojem języka HTML wprowadzono nowe elementy, włączając w to jeden z cieszących się najgorszą sławą — blink. Ten element powodował, że obiekty (tekst, rysunki itd.) migotały. Stał się powszechnie znienawidzony i jest sztandarowym argumentem za rozdzieleniem znaczników strony od warstwy prezentacji. Na szczęście projektanci tylko przez krótki czas interesowali się elementem blink, a następnym powszechnie stosowanym elementem (i tak jest do dziś) stał się font. Element font umożliwia projektantom określenie rodziny, rozmiaru i koloru czcionki: Jakiś tekst
Element font pozwala wyróżnić tekst, ma jednak pewną wadę: jeśli programista chce zmienić czcionkę zawartości strony, musi wyszukać przypadki użycia danego elementu na wszystkich stronach i ręcznie zmienić jego ustawienia. Może to spowodować, że zarządzanie stroną stanie się niezwykle żmudne. Wraz z nadejściem CSS pojawił się nacisk na rezygnację z elementu font — i w mniejszym lub większym stopniu zniknął on z większości stron. Jeśli zaprojektowałeś witryny lub aplikacje zawierające ten element, warto, abyś go usunął i zamiast niego użył arkuszy stylów. Poniższy fragment obejmuje ustawienia CSS dla czcionki określonego elementu: #test { font-family: arial; font-size: 4em; color: #0000ff }
Ciąg dalszy zmian — element po elemencie
|
29
Są też inne elementy określające pozycję lub styl, które zostały uznane za przestarzałe lub powodują konflikt z Ajaksem. Należy je usunąć. Są to między innymi: center
Ten element pozwala wyśrodkować obiekt. Aby uzyskać ten efekt, należy użyć stylów CSS. menu
Ten element służy do tworzenia list elementów menu. Ten efekt można uzyskać za pomocą list nieuporządkowanych (ul).
strike
Ten element tworzy przekreślony tekst (należy użyć stylów CSS lub elementu del). dir
Ten element służy do tworzenia list katalogów i należy go zastąpić elementem ul. applet
Ten element był używany do dołączania apletów języka Java, jednak obecnie należy stosować w zamian element object. Najlepszy sposób na sprawdzenie, czy strona zawiera przestarzałe elementy, to podanie w atrybucie DOCTYPE specyfikacji, z którą dokument ma być zgodny, a następnie wysłanie strony do walidatora. Jeśli kod zawiera nieobsługiwane elementy, walidator poinformuje o tym. Następny aspekt języka HTML niezgodny z XHTML to brak domykania znaczników w przypadku elementów, które tego nie wymagają. Na przykład element img nie ma znacznika zamykającego, ponieważ wszystkie informacje o obrazie znajdują się w obrębie znacznika. W XHTML ten znacznik należy zamknąć, umieszczając ukośnik bezpośrednio przed zamykającym nawiasem ostrym:
Przy dodawaniu Ajaksa niezwykle ważne jest, aby elementy strony internetowej były prawidłowo zamknięte. W przeciwnym razie mogą pojawić się nieoczekiwane skutki uboczne przy dodawaniu dynamicznych efektów. Do często niezamykanych elementów należą akapity (p) i elementy listy (li). Zamykanie ich może wydawać się kłopotliwe, jednak pozwala zapobiec większym problemom w przyszłości. Co się stanie, kiedy programista wyeliminuje oczywiste problemy? Czy strona we wszystkich przeglądarkach będzie wyglądać i działać tak samo? O tym możemy tylko pomarzyć.
Radzenie sobie ze specyfiką przeglądarek Może się wydawać, że każdy element HTML ma wbudowane ustawienia stylu. Nagłówki to elementy blokowe o czcionce większej niż tekst akapitu, zwiększające się od nagłówków typu h6 do h1. Każdy element listy HTML ma domyślną wartość dopełnienia. Akapity to elementy blokowe o określonym marginesie, dopełnieniu, czcionce i odstępach między wierszami. Na pierwszy rzut oka te wartości domyślne są dość podobne w poszczególnych przeglądarkach, jednak nawet małe odstępstwo może mieć znaczący wpływ na projekt strony i efekty Ajaksa. Przeglądarki udostępniają własne wewnętrzne arkusze stylów, które określają przedstawione wartości, a choć obowiązują pewne ograniczenia dotyczące stylu (na przykład aka-
30
|
Rozdział 1. Przygotowania do wdrażania Ajaksa
pity muszą być elementami blokowymi), inne ustawienia zależą od interpretacji wytycznych W3C przez producentów przeglądarek. Z powodu tych różnic wielkość odstępów między literami, ich rozmiar i inne właściwości są odmienne w poszczególnych przeglądarkach. Różne mogą być także kolory odnośników, ikony list i względne rozmiary nagłówków — i to czasem znacząco. Może to prowadzić do wielu problemów z projektem strony i nieoczekiwanych efektów ubocznych przy dodawaniu Ajaksa.
Kontrolowanie strony i dodawanie Ajaksa Wiele efektów Ajaksa, na przykład pomoc JIT (ang. just-in-time, czyli „na czas”; są to informacje na stronie ukryte dopóty, dopóki nie są potrzebne), rozmieszczanie, zanikanie kolorów czy zwijanie elementów, może działać poprawnie w różnych przeglądarkach tylko wtedy, jeśli programista wyeliminuje specyficzne działanie poszczególnych przeglądarek. Na przykład Safari używa innej wysokości linii niż Firefox. Nie będzie to miało wpływu na strony zawierające duże ilości tekstu, jednak może prowadzić do poważnych problemów w przypadku aplikacji ajaksowych, które wyświetlają informacje systemu pomocy w małych polach. Jeśli tekst w niektórych przeglądarkach okaże się zbyt duży, a w innych zbyt mały, system pomocy może wyglądać amatorsko lub będzie mało czytelny. Jedną z najgorszych przeglądarek, jeśli chodzi o nieprawidłową obsługę CSS, był Internet Explorer, a niektóre sztuczki pozwalające omijać błędy otrzymały nawet własne nazwy, na przykład: Peekaboo Bug, Border Chaos, Guillotine Bug. Te nazwy mogą bawić, jednak obsługa związanych z nimi błędów jest naprawdę kłopotliwa. Microsoft starał się usunąć większość problemów ze stylami CSS w przeglądarce Internet Explorer 7. Więcej informacji na ten temat zawiera strona http://blogs.msdn.com/ ie/archive/2006/08/22/712830.aspx. Niestety, ta przeglądarka też nie jest doskonała i czasem działa specyficznie.
Internet Explorer to niejedyna przeglądarka, która ma pewne „specyficzne” działania — żadna z nich nie jest doskonała. Każda przeglądarka ma własną „osobowość”. Niestety, na rynku przeglądarek niekoniecznie jest to korzystne. Ale może gdyby zapewnienie zgodności było proste, nie byłoby tak ciekawe? Jeśli w poszczególnych przeglądarkach rozmiary czcionek, marginesów i dopełnienia są różne, nawet w małym stopniu, elementy służące jako kontenery na dynamiczny tekst, na przykład elementy listy czy pola, mogą być zbyt małe, aby pomieścić informacje, co doprowadzi do zawijania lub obcinania tekstu. Można w dużym stopniu wyeliminować te problemy, używając XHTML i kompletnych arkuszy stylów CSS.
Przejmowanie kontroli Jedną z technik do obsługi różnic między arkuszami stylów różnych przeglądarek jest utworzenie niestandardowego arkusza, który będzie zawierał optymalne ustawienia dla wybranej przeglądarki. Następnie należy wprowadzić niezbędne poprawki, aby strona wyglądała tak samo w drugiej przeglądarce (starając się nie zepsuć wyglądu dokumentu w pierwszej), potem zająć się trzecią przeglądarką i tak dalej. Może to być ciężkie zadanie, zwłaszcza w przypadku witryny, na której użyto złożonych stylów i stosunkowo skomplikowanych technik Ajaksa. Radzenie sobie ze specyfiką przeglądarek
|
31
Inne rozwiązanie polega na usunięciu wszystkich ustawień stylu z arkusza i dodanie jedynie tych, które są niezbędne. Może to być bardzo duża zmiana, jednak zapewnia całkowitą kontrolę nad układem strony, co gwarantuje, że efekty Ajaksa będą działały zgodnie z oczekiwaniami. Jeśli programista chce zmienić styl wszystkich elementów, może zastosować ustawienia globalne — wystarczy w tym celu użyć gwiazdki (*). Poniższy kod ustawia atrybuty margin i padding na 0, a także ustawia czcionkę (font-family) wszystkich elementów na Arial: * { padding: 0; margin: 0; font-family: Arial }
To proste ustawienia stylów, jednak zastosowanie ich może dać bardzo widoczne efekty. Oczywiście, usunięcie lub dostosowanie ustawień dla wszystkich elementów oznacza, że trzeba przyjrzeć się każdemu elementowi i określić jego styl, co może być żmudne. Lepsze rozwiązanie polega na dostosowaniu ustawień tylko tych elementów, których obsługa najbardziej różni się między przeglądarkami. W przypadku pozostałych można zastosować ustawienia domyślne. Następnie należy umieścić ustawienia domyślne w arkuszu stylów dołączanym do wszystkich stron przed dodaniem drugiego arkusza, zawierającego ustawienia specyficzne dla danej strony. Jedną z ogólnych zmian, którą warto rozważyć, jest usunięcie podkreślenia z wszystkich odnośników. To podkreślenie może zepsuć wygląd strony i rozpraszać uwagę, zwłaszcza jeśli na stronie znajduje się grupa podkreślonych elementów. W przypadku niektórych czcionek podkreślenia mogą także powodować obcinanie dolnych części liter, co utrudnia odczyt słów. Poniższe ustawienie usuwa podkreślenie z wszystkich odnośników: a { text-decoration: none }
Później można dodać podkreślenie (także za pomocą CSS) do odnośników w obrębie określonego elementu, na przykład takiego, który zawiera dużo tekstu i niewiele odnośników. Jednym z problemów związanych z takim podejściem jest to, że odnośniki hipertekstowe nie wyróżniają się na stronie. Na własnych stronach używam czcionki, która nie jest wrażliwa na podkreślenia i zachowuje je w blokach z treścią. W paskach bocznych i menu sam kontekst wystarcza, aby użytkownik wiedział, że tekst działa jako odnośnik. Ponieważ czcionka może być mniejsza, a wiele elementów można umieścić jeden pod drugim, usuwam podkreślenie, aby tworzyć bardziej przejrzyste i czytelne strony. Następne istotne zadanie porządkujące polega na ustawieniu takiego samego rozmiaru, dopełnienia i marginesu wszystkich nagłówków. Można w tym celu posłużyć się następującą składnią: h1, h2, h3, h4, h5, h6 { font-size: 1em; padding: 0; margin: 0 }
W rzeczywistości nagłówki to elementy semantyczne, które udostępniają dla botów silników wyszukiwarek i programów odczytujących tekst z ekranu informacje o schemacie strony: h1 nagłówek poziomu pierwszego h2 nagłówek poziomu drugiego h3 nagłówek poziomu trzeciego h3 nagłówek poziomu trzeciego h2 nagłówek poziomu drugiego
Wielu programistów przyzwyczaiło się do używania nagłówków ze względu na pogrubienie i wielkość czcionki, co jest zupełnie niezgodne z ich przeznaczeniem. Usuwając te cechy, programista zmusza się do prawidłowego stosowania nagłówków i do używania stylów czcio-
32
|
Rozdział 1. Przygotowania do wdrażania Ajaksa
nek w odpowiednich miejscach w celu uzyskania efektów wizualnych. Jeśli na stronie ma się znaleźć nagłówek graficzny, jego treść należy umieścić w elemencie h1. Jeśli nagłówek ma być niewidoczny, należy go ukryć, jednak nie usuwając go, ponieważ będzie potrzebny do zoptymalizowania działania silnika wyszukiwarki. Dobrym kompromisem jest użycie rysunku w elemencie h1 i podanie jego tekstu jako wartości atrybutu alt. Po usunięciu najbardziej oczywistych błędów należy kontynuować tworzenie własnego domyślnego arkusza stylów, usuwając lub normalizując wszelkie elementy, których wyświetlanie w różnych przeglądarkach sprawia problemy. Na stronie http://tantek.com/log/2004/09.html#d06t2354 znajduje się ciekawy artykuł Tanteka Çelika, dotyczący tworzenia domyślnych szablonowych arkuszy stylów. Rozszerzenie autorstwa Erica Meyera jest dostępne pod adresem http://meyerweb.com/ ´eric/thoughts/2004/09/15/emreallyem-undoing-htmlcss.
Następny etap w procesie dodawania Ajaksa, po dokonaniu walidacji i uporządkowaniu istniejących witryn i aplikacji, polega na ustaleniu planu wprowadzania zmian. Powinien on obejmować używane platformy oraz obsługiwane docelowe przeglądarki i inne agenty klienckie. W tym celu trzeba dobrze zrozumieć użytkowników — czytelników strony internetowej i osoby korzystające z aplikacji sieciowej.
Zrozumienie potrzeb użytkowników Liczba funkcji Ajaksa umieszczanych w witrynie zależy nie tylko od tego, ile programista chce ich dodać i iloma chce zarządzać. Nawet najlepsza technologia nie będzie przydatna, jeśli nie daje konkretnych korzyści lub, co gorsza, uniemożliwia użytkownikom korzystanie z witryny. Jednak uwzględniając opinie użytkowników, można dodać nieco skryptów i kilka usług sieciowych działających bezpośrednio na stronie, aby poprawić użyteczność witryny.
Poznawanie klientów Pierwszy etap w dodawaniu Ajaksa polega na zdobyciu wszystkich informacji o osobach odwiedzających witrynę, aby można było zaplanować zmiany pod ich kątem. Najlepszym towarzyszem w tych poszukiwaniach są logi witryny. Poniższy wpis pochodzi z jednej z moich witryn i jest dość typowy: 5x.x2.x8.xx0 - - [31/Aug/2006:03:09:27 +0000] "GET /wp-content/themes/bbgun/bbgung. png HTTP/1.1" 200 90338 "http://burningbird.net/wp-content/themes/bbgun/style.css" "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.0.6) Gecko/20060728 Firefox/ 1.5.0.6"
Ten wpis zawiera datę, pobierane zasoby oraz łańcuch znaków zawierający informacje o systemie operacyjnym i przeglądarce klienta. W tym przypadku przeglądarka to Firefox 1.5.0.6, a system operacyjny to Windows. Inny wpis to: x0.xx3.1xx.xx4 - - [31/Aug/2006:03:14:48 +0000] "GET /wp-content/themes/words/ eyesafe.png HTTP/1.1" 404 9058 "http://burningbird.net" "Mozilla/5.0 (Macintosh; U; PPC Mac OS X Mach-0; en-US; rv:1.8.1b1) Gecko/20060707 Firefox/2.0b1"
Ten klient używa pierwszej wersji beta przeglądarki Firefox 2.0 i systemu operacyjnego Mac OS X.
Zrozumienie potrzeb użytkowników
|
33
Uzyskane informacje pomagają określić, jakie agenty użytkowników (przeglądarki i urządzenia przenośne) warto obsługiwać, a także umożliwia zniwelowanie różnic w zakresie obrazu, rozdzielczości, obsługi czcionek i narzędzi występujących między poszczególnymi systemami operacyjnymi. Logi można przeglądać bezpośrednio lub za pomocą jednego z licznych narzędzi, takich jak AWStats czy Webalizer, które pomagają w ich analizie. Te i inne narzędzia można znaleźć, wpisując w ulubionej wyszukiwarce wyrażenie „logfile analyzer”. Inne zasoby, których można użyć do analizy narzędzi służących do odwiedzania stron, to witryny ze statystykami dotyczącymi przeglądarek, na przykład W3 Schools (http://www.w3schools. com/browsers/browsers_stats.asp) czy The Counter (http://www.thecounter.com/stats). Aby określić, ilu użytkowników ma włączoną obsługę języka JavaScript, można dodać sekcję NOSCRIPT wczytującą niepowtarzalny rysunek, który nie ma wpływu na działanie strony, a na-
stępnie sprawdzić logi i zobaczyć, jak często rysunek ten był pobierany. W wyniku niedawnych zaawansowanych, opartych na wykorzystaniu skryptów ataków na Google i inne witryny coraz więcej osób wyłącza skrypty w ramach zwiększania bezpieczeństwa. Inni użytkownicy mogą używać narzędzi takich jak NoScript (http://noscript.net). Jest to rozszerzenie przeglądarki Firefox, które umożliwia wyłączenie obsługi języka JavaScript dla poszczególnych witryn. Projektując zawartość strony, należy oczekiwać, że użytkownicy wyłączą skrypty, a jeśli aplikacja nie jest całkowicie oparta na skryptach, nie należy wyświetlać zaleceń dotyczących włączenia obsługi skryptów w celu uzyskania większej funkcjonalności. Takie komunikaty w świecie Ajaksa stały się odpowiednikiem elementu blink.
Polityka otwartych drzwi Kiedyś zapytałam użytkowników jednej z moich witryn, jakie konkretnie zastosowania Ajaksa chcieliby na niej zobaczyć. Jedną z opcji było menu rozwijane, wyświetlające się albo po umieszczeniu kursora myszy na elemencie menu najwyższego poziomu, albo po kliknięciu tego elementu. Jest to dość powszechnie spotykane zastosowanie DHTML, często rozszerzane w Ajaksie. Co ciekawe, menu rozwijane nie okazało się szczególnie ciekawe dla użytkowników. Bardziej interesowała ich kontrola nad interakcją z witryną niż sterowanie poruszaniem się między stronami. Gdyby struktura witryny była bardziej złożona, menu mogłoby się okazać bardziej pożądane. Jednak różnica między tym, co uważałam za istotne, a rzeczywistymi oczekiwaniami użytkowników demonstruje, że nie można samodzielnie stwierdzić, co jest ważne dla klientów. Przed rozpoczęciem dodawania Ajaksa do witryny warto włączyć w ten proces użytkowników. Zapytać ich, czego chcą, lub przetestować nową technologię w określonych miejscach i poprosić o opinie. Trzeba liczyć się z koniecznością zmiany zarówno założeń, jak i planu wdrażania. Co powinnam zrobić, gdyby użytkownicy oczekiwali dodania rozwijanego menu DHTML? Gdyby struktura witryny miała więcej niż dwa poziomy, zgodziłabym się na to, jednak nie wystarczy dodać menu i na tym zakończyć pracy. Dynamicznie generowane menu powoduje, że witryna jest niedostępna dla osób z wyłączoną obsługą języka JavaScript, mających problemy z używaniem myszy czy korzystających z przeglądarki odczytującej tekst.
34
|
Rozdział 1. Przygotowania do wdrażania Ajaksa
Nawet jeśli okaże się, że 95 procent użytkowników ma włączoną obsługę języka JavaScript, używa myszy oraz przeglądarek typu Firefox, brak dostępności strony stanowi dodatkowe utrudnienie dla osób, którym życie stawia już wystarczająco wiele wyzwań. Warto wziąć pod uwagę także to, że najbardziej popularne rozszerzenie przeglądarki Firefox domyślnie wyłącza obsługę skryptów na wszystkich witrynach (NoScript). Nie należy także czynić założeń co do liczby osób używających języka JavaScript. Wreszcie boty silników wyszukiwarek internetowych, takich jak Google czy Yahoo!, nie potrafią śledzić odnośników generowanych przez skrypty, dlatego nie dotrą do materiałów dostępnych wyłącznie poprzez takie menu. Niektóre funkcje wymagają języka JavaScript lub użycia myszy. Mogę one dotyczyć internetowych edytorów tekstu, arkuszy kalkulacyjnych czy gier, jednak nigdy nie powinny obejmować kluczowej funkcjonalności, na przykład możliwości poruszania się po głównej zawartości stron czy dokumentacji aplikacji. Przy określaniu najlepszego sposobu spełnienia wymagań użytkowników warto stosować się do następujących zaleceń: • Należy zrezygnować z niespodzianek. Warto zapytać się użytkowników, czego oczekują,
przed rozpoczęciem wprowadzania zmian. • Warto zastanowić się nad testami beta obejmującymi fragment witryny, które pozwolą
uzyskać informacje zwrotne przed rozciągnięciem zmian na pozostałe strony. • Spełniając wymagania, należy uwzględnić niezbędne nakłady pracy. Im więcej elementów
zmienia się, tym więcej może się zepsuć, a żaden użytkownik nie lubi niedziałających stron.
• Należy uwzględniać oczekiwania użytkowników. Jeśli wprowadzone zmiany nie działa-
ją, należy przygotować się na rezygnację z nich i wypróbowanie czegoś nowego.
Plan Teraz, kiedy programista zna już środowisko i oczekiwania użytkowników, pora przygotować plan dodawania Ajaksa do witryny. Różnica między dodawaniem Ajaksa do małych i dużych witryn dotyczy liczby godzin, które trzeba na to poświęcić, a nie podejmowanych operacji. To, który obiekt zostanie zmieniony jako pierwszy, zależy od przyczyn dodawania Ajaksa. Na przykład rozbudowana aplikacja do obsługi klientów, która zawiera wiele stron z formularzami, doskonale nadaje się do zastosowania Ajaksa. Jest to odrębna aplikacja, w której dodanie Ajaksa może dać duże korzyści, jeśli chodzi o zadowolenie klienta, jakość odpowiedzi i wykorzystanie zasobów. Ajax pozwala także poprawić ogólną wydajność aplikacji, co w jeszcze większym stopniu przyczynia się do powstania sytuacji wygrana-wygrana. Inne obszary docelowe to strony, na których użytkownicy mogą aktualizować lub modyfikować informacje. Dodanie Ajaksa w celu umożliwienia wprowadzania zmian w miejscu może w bardzo dużym stopniu poprawić komfort pracy użytkownika przy jednoczesnym zachowaniu pełnej dostępności do strony. Udostępnienie systemu pomocy w trybie JIT oraz informacji zwrotnych to także doskonałe przykłady zastosowania Ajaksa. Trzeba tylko upewnić się, że takie same informacje będą dostępne w przypadku braku obsługi skryptów lub jeśli klient używa przeglądarki odczytującej zawartość stron. Dodawanie elementów graficznych nie powinno zajmować wysokiej pozycji na liście priorytetów, jednak jest to dozwolone zastosowanie Ajaksa. Takie zmiany mogą wzbudzić zachwyt użytkowników przy minimalnych zakłóceniach podstawowej funkcjonalności witryny. Zrozumienie potrzeb użytkowników
|
35
Ajaksa należy dodawać stopniowo, archiwizując witrynę przed dodaniem choćby jednego wiersza kodu. Jeśli konieczne jest wprowadzenie zmian globalnych, na przykład nowego menu ajaksowego, które ma działać na wszystkich stronach witryny, warto umieścić nowy kod w pliku i dołączyć go do wszystkich stron. Może to być plik nagłówkowy dołączany do każdej strony przy użyciu mechanizmów działających po stronie serwera. Należy minimalizować ilość kodu dodawaną do każdej strony, umieszczając go w bibliotekach i dołączając do poszczególnych stron jedynie niezbędne z nich. Można też zastanowić się nad użyciem jednej z bezpłatnych bibliotek Ajaksa. Niektóre z nich są opisane w rozdziale 3. i używane w przykładach w tej książce. Tworząc plan, należy zastanowić się nad następującymi zagadnieniami: • Powtarzam się, ale jest to bardzo istotne — należy utworzyć kopię witryny przed rozpo-
częciem pracy i po udostępnieniu każdego dodatku. • Należy określić punkt wyjścia. Dobrym kandydatem są izolowane aplikacje. • Trzeba przeanalizować obszary, w których witryna lub aplikacja jest już interaktywna,
oraz określić, jak można zastosować Ajaksa w celu usprawnienia działania. • Należy udostępniać rozbudowaną pomoc JIT oraz dużo informacji zwrotnych, zapewnia-
jąc alternatywne rozwiązania dla klientów z wyłączoną obsługą skryptów. • Dzięki efektom graficznym zwiększającym istniejącą funkcjonalność lub towarzyszącym
jej strony są ciekawsze i bardziej efektowne, zwykle minimalnym kosztem. • Należy przyjrzeć się okazjom do wyodrębnienia fragmentów kodu, które można wielo-
krotnie wykorzystać, na przykład funkcji menu czy pomocy elektronicznej. • Warto używać istniejących bibliotek.
Projektowanie szkieletu witryny Najważniejszą decyzją stojącą przed programistą jest określenie, czy dodawać Ajaksa do istniejących stron czy rozpocząć pracę od początku. Ta książka opiera się na założeniu, że projektant chce dodać Ajaksa do istniejących stron i aplikacji — nie usuwa witryny i nie zaczyna wszystkiego od nowa. Mówiąc inaczej, programista modyfikuje funkcjonalność używaną po stronie klienta, podczas gdy komponent witryny działający po stronie serwera pozostaje w mniejszym lub większym stopniu niezmieniony. Następny etap w procesie decyzyjnym to określenie zakresu zmian. Jeśli programista modyfikuje formularz statycznej strony internetowej tak, aby za pomocą skryptów i obiektu XMLHttpRequest umożliwić edycję w miejscu, zastosowanie nowych technologii jest specyficzne i ma mały wpływ na całą aplikację. Jednak dodanie systemu pomocy obejmującego całą witrynę, nowego dynamicznego menu czy funkcji wyszukiwania w miejscu ma wpływ na wszystkie strony, dlatego warto rozważyć pewne dodatkowe zmiany w architekturze, na przykład umieszczenie nagłówka i wszystkich skryptów w odrębnych plikach, które będzie można dołączyć do każdej strony. To podejście pozwala upewnić się, że dołączone zostaną wszystkie potrzebne biblioteki języka JavaScript, a także umożliwia wyizolowanie elementów takich jak metody obsługi zdarzeń.
36
|
Rozdział 1. Przygotowania do wdrażania Ajaksa
Wiele nowych systemów zarządzania treścią jest opartych na podejściu modułowym, które umożliwia maksymalne wielokrotne wykorzystanie pasków bocznych, nagłówków i stopek. Jeśli witryna nie działa według systemu modularnego, a zarządzanie stronami odbywa się ręcznie, warto dodać wdrożenie modułów do listy zadań związanych z przyszłymi projektami. Niezależnie od tego, czy strony działają w systemie modułowym, czy nie, cały niestandardowy kod JavaScript należy umieścić w odrębnych plikach dołączanych do odpowiednich stron. Zmiana kodu w skrypcie zostanie automatycznie uwzględniona na wszystkich używających go stronach.
Wychodzenie naprzeciw użytkownikom Wspomniałam o znaczeniu wykrywania środowisk użytkowników, włączając w to przeglądarki i systemy operacyjne, a także sprawdzanie, czy obsługa języka JavaScript jest włączona, czy nie. Trzeba także przeanalizować plan pod kątem dostępności, uwzględniając kryteria służące do oceny każdej strony po użyciu na niej Ajaksa. Pomoże to zagwarantować spełnienie wymagań. Przeczytałam kiedyś, że trzeba spełniać potrzeby jedynie od 90 do 95 procent użytkowników danej strony internetowej. Może to być prawdą w przypadku osób używających starszych przeglądarek, takich jak Internet Explorer 5.5 dla komputerów Mac, jednak nie dotyczy użytkowników, którzy muszą pokonywać w życiu wystarczająco wiele przeszkód, aby nie musieć stawać przed jeszcze jedną, w postaci niedostępności danej witryny. Są to osoby: • niewidome, które mogą używać przeglądarek odczytujących tekst; • fizycznie niezdolne do używania myszy; • nierozróżniające kolorów; • z upośledzeniem słuchu, które nie mogą słuchać audycji podcast ani instrukcji dźwiękowych; • z zaburzeniami uwagi lub upośledzeniem zdolności uczenia się i rozumienia, które nie
potrafią zrozumieć szybko wyświetlanego tekstu ani błyskawicznych komunikatów; • zmęczone, pracujące pod presją czasu, rozproszone lub zestresowane.
Inaczej mówiąc — wszyscy. Użytkownicy mogą mieć problemy z korzystaniem z czcionki tak małej, że jej przeczytanie wymaga szkła powiększającego. Dodanie edycji w miejscu lub funkcji dynamicznego wyszukiwania nie przyda się na wiele, jeśli trzeba zmniejszyć nowe elementy tak, że będą nieczytelne. Ponadto mało rzeczy irytuje bardziej niż konieczność przesuwania kursora myszy jak magicznej różdżki po całej stronie, aby znaleźć elementy służące do poruszania się po stronie lub umożliwiające dostęp do istotnych informacji.
Bezpieczeństwo Użytkownicy witryny są zależni od projektantów także w zakresie możliwości korzystania z użytecznego i bezpiecznego środowiska. Część architektury witryny obejmuje dołączenie procedur testowych, które po wprowadzeniu każdej zmiany pozwalają sprawdzić, czy strona jest wciąż dostępna we wszystkich docelowych przeglądarkach i środowiskach. Plan usprawniania witryny powinien również obejmować procedury, które na różne sposoby sprawdzają funkcjonalność zmian.
Projektowanie szkieletu witryny
|
37
Należy także przeprowadzić testy, aby upewnić się, czy aplikacja nie zajmuje na komputerze użytkownika więcej miejsca, niż powinna. Większość aplikacji opartych na języku JavaScript z pewnych przyczyn używa pamięci po stronie klienta, a zwykle wystarczają do tego pliki cookie. Jednak niektóre biblioteki Ajaksa udostępniają mechanizmy do przechowywania na komputerze klienta dodatkowych danych za pomocą technologii Flash lub innego zewnętrznego magazynu. Tworząc plan, należy określić, ile danych aplikacja ma przechowywać po stronie klienta w porównaniu z danymi po stronie serwera. Przechowywanie informacji po stronie klienta nieodłącznie prowadzi do zagrożenia bezpieczeństwa w każdej aplikacji sieciowej, niezależnie od zastosowania w niej Ajaksa. W planie trzeba także uwzględnić zagadnienia związane z bezpieczeństwem. W książce wskazuję wrażliwe obszary przy okazji opisu różnych efektów Ajaksa. Im więcej dynamicznych elementów zawierają strony, tym ważniejsze jest śledzenie witryn z informacjami o bezpieczeństwie oraz testowanie stron po udostępnieniu nowych wersji przeglądarek. Ponieważ jest to ogólna książka o dodawaniu Ajaksa, nie mogę szczegółowo opisać zagadnień związanych z bezpieczeństwem. W zamian zachęcam do lektury książki Christophera Wellsa, Securing Ajax Applications1 (O’Reilly), która szczegółowo przedstawia Ajaksa i kwestie bezpieczeństwa.
Ścisłe i luźne powiązanie W ramach tworzenia szkieletu do dodawania Ajaksa projektanci stron muszą często decydować, jak ściśle powiązane mają być elementy aplikacji działające po stronie klienta i serwera. W aplikacjach ściśle powiązanych duża część kodu działającego po stronie klienta jest generowana przez serwer lub zależna od niego. Mówiąc inaczej: nie ma sposobu na rozdzielenie tych elementów, ponieważ cała aplikacja przestałaby działać. Z kolei w aplikacjach luźno powiązanych usługa sieciowa może zostać wywołana przez aplikację ajaksową, ale także przez inną usługę. Ponadto kod po stronie klienta komunikuje się jedynie z interfejsem API (ang. Application Programming Interface), dlatego nie jest istotne, w jaki sposób programista utworzył daną usługę sieciową ani jakiego języka do tego użył. Wybór luźnego powiązania oznacza, że programista tworzy aplikację serwerową, która ma udostępniać usługi sieciowe wywoływane przez inne aplikacje działające po stronie serwera, a także aplikacje ajaksowe. To rozwiązanie jest najbardziej uporządkowane, ponieważ nie opiera się na założeniach dotyczących możliwości klienta. Zmiana po stronie klienta nie wpływa na kod po stronie serwera, a modyfikacje po stronie serwera nie wpływają na działanie klienta. Jeśli jednak programista zastosuje ścisłe powiązanie (na przykład użyje biblioteki języka Java, takiej jak Google Web Toolkit — GTW), wtedy samo narzędzie określa, co można zrobić ze stroną, a nawet jakie informacje można wygenerować. Zwykle dodając Ajaksa do istniejących stron, należy użyć luźnego powiązania. Rozpoczynając pracę od początku, można zastosować ścisłe powiązanie, choć należy zachować ostrożność przed zbyt ścisłym powiązaniem kodu działającego po stronie serwera i klienta.
1
Wydanie polskie: Ajax. Bezpieczne aplikacje internetowe, Helion, w przygotowaniu — przyp. tłum.
38
|
Rozdział 1. Przygotowania do wdrażania Ajaksa
Stopniowe usprawnianie a remont generalny Jak już wspomniałam, zakładam, że jesteś jednym ze szczęśliwych użytkowników Ajaksa, ponieważ masz już witrynę lub aplikację z całą podstawową funkcjonalnością i chcesz ją rozbudować oraz usprawnić, dodając Ajaksa. Może to wymagać pewnych wcześniejszych przygotowań, jednak operacje sprawiające, że strony lepiej nadają się do wdrożenia Ajaksa, powodują także ulepszenie samej witryny.
Obsługiwane przeglądarki Pisząc książkę, zawsze trudno jest określić, jakie przeglądarki i inne agenty użytkownika uwzględnić. Sprawdziłam logi na licznych własnych witrynach, aby sprawdzić, z jakich narzędzi korzystają użytkownicy. Okazało się, że jest ich całe mnóstwo: od Firefoksa 2.x po przeglądarkę Internet Explorer 3.x. Użytkownicy odwiedzają strony za pomocą palmtopów, telefonów komórkowych i innych urządzeń przenośnych, a nawet odbiorników WebTV. Większość przykładów z tej książki nie wygląda dobrze na małym ekranie iPhone’a i podobnych urządzeń niezależnie od tego, czy na danej stronie zastosowano Ajaksa, czy nie. W przypadku urządzeń przenośnych udostępniam strony za pomocą odrębnego arkusza stylów, który generuje zawartość tak, aby była czytelna w małych urządzeniach. Można także użyć tej techniki do określenia, czy całkowicie wyłączyć efekty ajaksowe w takich urządzeniach. Sama zdecydowałam się z nich zrezygnować. W przypadku przeglądarek do testowania przykładów posłużyły wszystkie poniższe programy: • Internet Explorer 6.x w systemie Windows 2000, • Internet Explorer 7 w systemie Windows XP, • Firefox 2.x w systemach Windows i Mac, • Camino 1.0 w systemie Mac, • Safari 2.0.4, • WebKit 420+, • Opera 9.x i nowsze w systemach Windows oraz Mac, • OmniWeb 5.x, • Konqueror.
Po długim namyśle zrezygnowałam z przeglądarki Internet Explorer 5.5 i jej starszych wersji. Microsoft zaprzestał ich obsługi i mają one luki w systemie zabezpieczeń, nie wspominając o brakach w implementacji standardowych technologii internetowych. We wszystkich środowiskach, w których działała przeglądarka Internet Explorer 5.5 i jej starsze wersje, można użyć alternatywnych programów. Dotyczy to systemu Mac, a także starszych wersji systemu Windows. Nie trzeba wspominać, że kod źródłowy nie zapewnia działania Ajaksa także w przeglądarkach takich jak Netscape 4.x. Zastosowane podejście polega na emulacji efektu, jeśli obsługa skryptów została wyłączona, a klient przyjmuje strony internetowe bez Ajaksa. Zapewnia to dostęp do stron bez konieczności kłopotliwego zajmowania się niezliczonymi drobiazgami. Nie utożsamiam dostępności z masochizmem — w którymś miejscu trzeba ustalić granicę.
Stopniowe usprawnianie a remont generalny
|
39
Podsumowując zagadnienia opisane w tym rozdziale, można wyróżnić następujące etapy upraszczania strony i przygotowywania jej do dodania Ajaksa:
1. Walidacja istniejących stron pod kątem docelowych specyfikacji XHTML i CSS oraz wprowadzenie niezbędnych poprawek.
2. Przekształcenie starego układu opartego na tabelach HTML na układ oparty na CSS
i XHTML, usunięcie niezalecanych i przestarzałych elementów HTML oraz przekształcenie kodu HTML na XHTML.
3. Zdefiniowanie planu dodawania efektów Ajaksa. Należy w nim uwzględnić, które części
witryny wymagają napisania od nowa, gdzie potrzebne są zmiany oraz jakie efekty zostaną dodane.
4. Analiza użytkowników. Należy określić, które strony odwiedzają i z jakich narzędzi korzystają.
5. Zaangażowanie użytkowników, włączając w to testowanie prototypów. 6. Utworzenie szkieletu umożliwiającego wprowadzenie zmian. Należy zminimalizować
ilość kodu, maksymalizując możliwość jego wielokrotnego wykorzystania. Jest to dobry moment na zastanowienie się nad zastosowaniem systemu zarządzania treścią, jeśli jeszcze się go nie używa.
Po utworzeniu planu, zdefiniowaniu szkieletu, na podstawie którego można dodać elementy Ajaksa do witryny, przekształceniu stron na prawidłowe dokumenty XHTML i CSS oraz ograniczeniu różnic związanych z działaniem CSS w różnych przeglądarkach można przystąpić do dodawania Ajaksa.
40
|
Rozdział 1. Przygotowania do wdrażania Ajaksa
ROZDZIAŁ 2.
Elementy Ajaksa
Tym, co odróżnia Ajaksa od języka JavaScript, jest możliwość wysyłania żądań danych zewnętrznych z poziomu strony oraz możliwość zwracania wyników bez odświeżania stron. Żądanie może dotyczyć tak prostej operacji, jak wczytanie nowego rysunku lub arkusza stylów, lub tak złożonej, jak pobranie całego dokumentu XML ze zmianami wielu elementów strony. Dane zwracane przez usługi sieciowe można zintegrować ze stroną za pomocą dynamicznych skryptów, tworząc blok skryptowy składający się z wywoływanej zwrotnie funkcji oraz zwracanych danych. Jednak zwykle żądania kieruje się do usług sieciowych za pomocą kluczowego obiektu XMLHttpRequest. Pomysł obiektu, który mógłby służyć do zarządzania żądaniami kierowanymi do usług sieciowych za pomocą skryptów z poziomu strony, powstał kilka lat temu wraz z przeglądarką Internet Explorer 5.0. Na początku był to obiekt ActiveX, a Microsoft kontynuował jego rozwój w przeglądarkach Internet Explorer 6.x. Jednak obecnie Microsoft, podobnie jak inni producenci przeglądarek, zapewnia obsługę obiektu XMLHttpRequest. XMLHttpRequest to jeden z prostszych w obsłudze obiektów dostępnych w przeglądarkach.
Należy utworzyć żądanie do usługi sieciowej, przesłać je, a w celu przetworzenia zwróconych wyników uruchamiana jest wywoływana zwrotnie funkcja. Zamiast rozpraszać całą aktywność na wiele stron, co ma miejsce w przypadku tradycyjnych aplikacji sieciowych, można zrobić wszystko w obrębie jednej strony, bez odświeżania jej. W biznesie informatycznym stosowanych jest wiele akronimów. W większości miejsc w tej książce używam nazwy „XMLHttpRequest”, choć czasem, podobnie jak inni programiści, stosuję nazwę „XHR”, aby oszczędzać czas i klawiaturę.
Aplikacje sieciowe Na potrzeby tego rozdziału utworzyłam prostą aplikację, Drinki. Użytkownik może w niej wybrać nazwę drinka, a aplikacja wyświetli jego skład. Choć jest to bardzo prosty przykład, pozwala przedstawić komunikację między serwerem a klientem w aplikacjach sieciowych bez pogrążania się w analizie dodatkowej funkcjonalności. Za funkcjonalność po stronie serwera odpowiada kod w języku PHP, a w przypadku tego uproszczonego przykładu przepisy są przechowywane na stronie PHP. Po stronie klienta
41
znajduje się formularz z listą nazw drinków i przyciskiem do ich przesyłania. Kiedy użytkownik kliknie ten przycisk, formularz zostanie przesłany na serwer, a ten zwróci odpowiedni przepis i wyświetli go na stronie. Przed analizą funkcjonalności Ajaksa warto zobaczyć, jak można utworzyć tę aplikację bez stosowania języka JavaScript. W przypadku prostych aplikacji nierzadko kod klienta i serwera znajduje się w jednym pliku. Funkcje działające po stronie serwera generują wtedy odpowiednie elementy używane po stronie klienta. Jedyny żmudny element tworzenia omawianej aplikacji polega na upewnieniu się, że wybrany drink pojawi się na liście jako zaznaczony. W przeciwnym razie wyświetlona zostanie nazwa pierwszego drinka, co spowoduje niedopasowanie z wyświetlonym przepisem. Listing 2.1 przedstawia kompletną aplikację, w której jedna strona zawiera komponenty zarówno klienta, jak i serwera. Listing 2.1. Kompletna jednostronicowa aplikacja Drinki Gorąca herbata
Zagotuj wodę. " . "Zalej liście herbaty. Parz pięć minut. " . "Po odcedzeniu można podawać.
"; break; case "APPLETINI" : $result = "
Appletini
Wymieszaj w szklance z lodem 30 ml wódki " . "i 15 ml Sour Apple Schnapps lub innego sznapsa jabłkowego. " . "Przelej do kieliszka do martini. " . "Udekoruj plasterkiem jabłka lub rodzynkami.
"; break; case "NONCHAMP" : $result = "
Szampan bezalkoholowy
Wymieszaj 960 ml wody sodowej " . "i 360 ml zmrożonego koncentratu " . "z białych winogron.
"; break; case "SWMPMARGARITA" : $result = "
Swamp Margarita
" . "
Wymieszaj 45 ml dobrej tequili, 22 ml Cointreau, " . "22 ml Grand Marnier, 15 ml soku cytrynowego i 60 ml sour mix. " . "Schładzaj przez godzinę. Wrzuć na dno wysokiej szklanki kilka " . "zielonych oliwek i wlej kilka kropel oliwy z oliwek. " . "Wlej margeritę na oliwki i odczekaj dziesięć minut. " . "Odcedź i podawaj z oliwkami na wykałaczkach nadziewanymi " . "pieprzem angielskim.
"; break; case "LEMON" : $result = "
Kropla cytryny
Wymieszaj 30 ml wódki cytrynowej " . "z 30 ml soku cytrynowego i jedną łyżką cukru. Wymieszaj z lodem, " . "odcedź i podawaj.
"; break; default : $result = "Brak przepisu";
42
|
Rozdział 2. Elementy Ajaksa
break; } } ?> Drinki
Drinki
Ponieważ w atrybucie action formularza nie ma określonego elementu docelowego, po stronie serwera ta sama strona zostanie użyta do przetworzenia przesłanego formularza, a wybrany drink zostanie przesłany jako część żądania GET. Nazwa drinka (name) służy do znalezienia odpowiedniego przepisu, który zostaje przypisany do zmiennej. W dalszej części strony, w drugim bloku PHP, przepis jest wyświetlany na stronie tuż pod formularzem. Aplikacje sieciowe
|
43
Wartość drinka (value) jest porównywana z wartością każdej opcji select, a po dopasowaniu tych elementów drink zostaje zaznaczony. Ważne jest, aby odświeżyć zarówno formularz, jak i wyniki, bo w przeciwnym razie mogą nie pasować do siebie. Jedyny plik zewnętrzny tej aplikacji zawiera kod CSS przedstawiony na listingu 2.2. Ten sam plik CSS jest używany we wszystkich przykładach z drinkami, a jego dodatkowe elementy są opisane w dalszej części rozdziału. Listing 2.2. Kod CSS używany we wszystkich wersjach aplikacji Drinki przedstawionych w tym rozdziale html { background: #fff url(drink.jpg) no-repeat fixed top left; color: #000; } body { font-family: Verdana, Arial, sans-serif; } form { color: #000; padding: 20px; text-align: left; } h1 { margin-left: 380px; } label { display: block; margin-bottom: 10px; } #drinkblock { margin-left: 380px; width: 500px; }
Kod, znaczniki i ustawienia stylów przedstawione na listingach 2.1 i 2.2 składają się na całkowicie poprawną aplikację, jednak jeśli funkcjonalność ma być dostępna także w innych aplikacjach, trzeba ją powielić. Lepszym rozwiązaniem jest umieszczenie funkcji działających po stronie serwera w odrębnej aplikacji sieciowej i użycie instrukcji include języka PHP w innym kodzie uruchamianym po stronie serwera w celu dołączania odpowiednich funkcji wtedy, kiedy są potrzebne: require_once("./recipe.php");
Upraszcza to także klienta, ponieważ funkcje używane po stronie serwera można ograniczyć do minimum. Listing 2.3 demonstruje stosowanie języka PHP do przetwarzania żądań formularza. Listing 2.3. Kod PHP przetwarzający żądania aplikacji Drinki
44
|
Rozdział 2. Elementy Ajaksa
if (empty($drink)) { $drink = 'TEA'; } else { // Usuwanie odstępów z początku i końca łańcucha $search = trim($drink); switch($search) { case "TEA" : $result = "
Gorąca herbata
Zagotuj wodę. " . "Zalej liście herbaty. Parz pięć minut. " . "Po odcedzeniu można podawać.
"; break; case "APPLETINI" : $result = "
Appletini
Wymieszaj w szklance z lodem 30 ml wódki " . "i 15 ml Sour Apple Schnapps lub innego sznapsa jabłkowego. " . "Przelej do kieliszka do martini. " . "Udekoruj plasterkiem jabłka lub rodzynkami.
"; break; case "NONCHAMP" : $result = "
Szampan bezalkoholowy
Wymieszaj 900 ml wody sodowej " . "i 300 ml zmrożonego koncentratu z " . "białych winogron.
"; break; case "SWMPMARGARITA" : $result = "
Swamp Margarita
" . "
Wymieszaj 45 ml dobrej tequili, 20 ml Cointreau, " . "20 ml Grand Marnier, 15 ml soku cytrynowego i 60 ml sour mix. " . "Schładzaj przez godzinę. Wrzuć na dno wysokiej szklanki kilka " . "zielonych oliwek i wlej kilka kropel oliwy z oliwek. " . "Wlej margeritę na oliwki i odczekaj dziesięć minut. " . "Odcedź i podawaj z oliwkami na wykałaczkach nadziewanymi " . "pieprzem angielskim.
"; break; case "LEMON" : $result = "
Kropla cytryny
Wymieszaj 30 ml wódki cytrynowej " . "z 30 ml soku cytrynowego i jedną łyżką cukru. Wymieszaj z lodem, " . "odcedź i podawaj.
Także to rozwiązanie jest proste i funkcjonalne. Rysunek 2.1 przedstawia aplikację po przetworzeniu żądania i zwrócenia przepisu danego drinka. To rozwiązanie powinno działać w prawie każdej przeglądarce i każdym środowisku. Nie należy zapominać o tej właściwości aplikacji, dodając funkcjonalność Ajaksa. Pewną wadą tej aplikacji jest to, że elementy formularzy muszą mieć takie same nazwy. Należy zauważyć, że elementy formularzy trzeba aktualizować tak, aby pasowały do przepisów, dzięki czemu zawartość formularza pasuje do wyświetlanych informacji. W przypadku niektórych formularzy może to wymagać dużo pracy. Obejście tej niedogodności polega na wyświetlaniu przepisów drinków na odrębnej stronie wynikowej. Nazwę pliku tej strony należy podać w atrybucie action formularza. Formularz utworzony w ten sposób jest dużo prostszy, ponieważ nie trzeba przejmować się synchronizacją wybranej nazwy:
Aplikacje sieciowe
|
45
Rysunek 2.1. Prosta odpowiedź aplikacji potwierdzająca, że skrypt PHP działa zgodnie z oczekiwaniami
Strona, która przetwarza dane formularza, jest sformatowana tak, aby wyglądała tak samo jak wyjściowa strona kliencka, co pozwala zachować ciągłość między nimi. Jedyna funkcjonalność strony wynikowej to dołączanie aplikacji działającej po stronie serwera (recipe.php) i wyświetlanie pobranego przepisu: 46
|
Rozdział 2. Elementy Ajaksa
Drinki
Drinki
Zastosowanie techniki opartej na użyciu odrębnej strony wynikowej powoduje, że jeśli użytkownik aplikacji chce znaleźć nowy przepis, musi wrócić do wyjściowej strony z formularzem i zacząć cały proces od początku. Zamiast konieczności przesyłania formularza — albo do tej samej, ponownie wczytywanej strony, albo do odrębnej — podejście ajaksowe umożliwia wywołanie aplikacji sieciowej w celu pobrania danych i przetworzenia tych wyników bezpośrednio na stronie początkowej. Nie wymaga to ponownego wczytywania strony ani aktualizowania strony z formularzem w celu zsynchronizowania danych. Właśnie to podejście będzie tematem pozostałej części tego rozdziału.
Struktura obiektów XMLHttpRequest W3C, organizacja odpowiedzialna za większość specyfikacji obowiązujących w aplikacjach sieciowych, pracuje obecnie nad definicją standardu obiektu XMLHttpRequest. Najnowsza wersja robocza (w czasie pisania tej książki) pochodzi z września 2006 roku i można się z nią zapoznać na stronie http://www.w3.org/TR/XMLHttpRequest. Jednak większość wersji obiektu XMLHttpRequest wywodzi się z jego popularnych wersji — tej z przeglądarki Internet Explorer i tych późniejszych, czyli z przeglądarek opartych na Gecko, między innymi Firefoksa. Mogą występować różnice w stosowaniu tego obiektu w różnych aplikacjach, jednak w tej książce skoncentruję się na najważniejszych i najczęściej używanych funkcjach. Obiekt XMLHttpRequest jest dość prosty. We wszystkich docelowych przeglądarkach opisywanych w tej książce dostępne są następujące metody: open
Składnia to open(metoda,url[,async,nazwaużytkownika,hasło]). Ta metoda otwiera połączenie z danym adresem URL, używając określonej metody (na przykład GET lub POST). Opcjonalne parametry to async — wartość logiczna określająca żądanie jako asynchroniczne (true) lub synchroniczne (false), a także nazwaużytkownika i hasło, jeśli wymaga ich przetwarzanie danych po stronie serwera. Jeśli programista nie poda parametru async, domyślnie żądania są asynchroniczne. setRequestHeader Składnia to setRequestHeader(klucz,wartość). Ta metoda dodaje parę klucz-wartość
do nagłówka żądania.
Aplikacje sieciowe
|
47
send
Składnia to send(treść). Ta metoda służy do wysyłania żądań zawierających dane. Jeśli żądanie odbywa się za pomocą metod HTTP, takich jak POST, w których parametry nie są dołączane do adresu URL, za pomocą tej metody przekazywane są wszystkie potrzebne parametry. W przeciwnym razie treść ma wartość null. getAllResponseHeaders Składnia to getAllResponseHeaders(). Ta metoda zwraca wszystkie nagłówki odpowie-
dzi HTTP jako łańcuch znaków. Te informacje obejmują między innymi limit czasu podtrzymywania połączenia, typ zawartości odpowiedzi, dane o serwerze i datę. getResponseHeader Składnia to getResponseHeader(klucz). Ta metoda zwraca wybrany nagłówek odpowiedzi HTTP określony przez klucz. abort
Składnia to abort(). Ta metoda zatrzymuje wykonywanie bieżącego żądania. Obiekty XMLHttpRequest mają także kilka właściwości: onreadystatechange
Ta właściwość przechowuje funkcję wywoływaną w wyniku zmiany stanu żądania.
readyState
Ta właściwość przyjmuje pięć wartości: 0 oznacza brak inicjacji, 1 to otwarte żądanie, 2 — przesłane, 3 oznacza otrzymanie odpowiedzi, a 4 — zakończenie wczytywania odpowiedzi. Zwykle ważny jest stan 4. responseText
Ta właściwość określa, że odpowiedź ma być przesłana jako tekstowy łańcuch znaków. responseXML
Tej właściwości należy używać do nadania odpowiedzi formatu XML. status
Ta właściwość dotyczy stanu serwera, na przykład 404, 500 czy 200. Ta ostatnia wartość oznacza udane przesłanie żądania.
statusText
Ta właściwość określa tekst komunikatu powiązany ze stanem. Pozostała część rozdziału to dokładny opis i szczegółowa demonstracja zastosowania powyższych właściwości oraz metod. Najpierw jednak trzeba dowiedzieć się, jak utworzyć obiekt XMLHttpRequest.
Przygotowywanie obiektu do użytku Jedną z pierwszych decyzji, które trzeba podjąć, tworząc obiekt XMLHttpRequest, jest określenie, czy aplikacja ma obsługiwać przeglądarki Internet Explorer 6.x. W Internet Explorer 7 i nowszych wersjach innych przeglądarek (takich, jak: Opera, Firefox, Safari, Netscape) obiekt do obsługi żądań kierowanych do serwera to po prostu XMLHttpRequest, a we wszystkich tych przeglądarkach działa on podobnie. Jednak przeglądarki Internet Explorer 6.x wciąż obsługują starszy obiekt ActiveX Microsoftu. Ponieważ starsze systemy operacyjne Windows, na przykład Windows 2000, nie współpracują z nowszą przeglądarką Internet Explorer 7, trzeba zdecydować, 48
|
Rozdział 2. Elementy Ajaksa
czy aplikacja ma być dostępna także dla osób używających systemu Windows 2000 (oraz tych użytkowników systemu Windows XP, którzy nie zaktualizowali używanej przeglądarki). W tym momencie warto wrócić do logów i sprawdzić, ile osób odwiedza witrynę za pomocą przeglądarek Internet Explorer 6.x. Jeśli jest ich chociaż 5 procent, zwykle warto dodać obsługę tych starszych przeglądarek. Na szczęście dodatkowy kod nie zajmuje zbyt wiele miejsca. Następne zagadnienie związane z korzystaniem z obiektów ActiveX to określenie, jakiego identyfikatora obiektu użyć w wywołaniu funkcji tworzącej potrzebny obiekt. Niestety, Microsoft nie zachował spójności w nazwach tego obiektu w różnych systemach operacyjnych, a ponadto występują różne odmiany tych obiektów, na przykład MSXML2.XMLHttp, MSXML2.XMLHttp.3.0 i MSXML2.XMLHttp.4.0. Większość bibliotek Ajaksa obejmuje dwa identyfikatory: starszy — Microsoft.XMLHttp, oraz bardziej popularny z nowszych — MSXML2.XMLHttp. Ponieważ większość przeglądarek służących do odwiedzania witryny ma dostęp do będącego obecnie standardem obiektu XMLHttpRequest, najbardziej wydajne rozwiązanie to sprawdzanie jego dostępności w pierwszej kolejności. Jeśli ten test nie powiedzie się, należy sprawdzić dwa obiekty specyficzne dla Microsoftu. Ponieważ w książce obiekty XHR są używane bardzo często, umieszczenie ich obsługi w funkcji nadającej się do wielokrotnego wykorzystania upraszcza dołączanie go do biblioteki. Listing 2.4 przedstawia kod typowej funkcji tworzącej obiekt XMLHttpRequest. Listing 2.4. Typowa funkcja tworząca obiekt XMLHttpRequest function getXmlHttpRequest() { var xmlHttpObj; if (window.XMLHttpRequest) { xmlHttpObj = new XMLHttpRequest(); } else { try { xmlHttpObj = new ActiveXObject("Msxml2.XMLHTTP"); } catch (e) { try { xmlHttpObj = new ActiveXObject("Microsoft.XMLHTTP"); } catch (e) { xmlHttpObj = false; } } } return xmlHttpObj; }
Zaczynając od góry — jeśli obiekt window zawiera metodę XMLHttpRequest, należy jej użyć do utworzenia obiektu XMLHttpRequest. W przeciwnym razie trzeba utworzyć obiekt ActiveX w bloku try-catch, co umożliwia elegancką obsługę błędów (w tym przypadku jest to przypisanie do obiektu wartości false). Przedstawiona funkcja zwraca utworzony obiekt. Ten obiekt jest używany w wielu miejscach tej książki, dlatego warto rozpocząć tworzenie globalnej biblioteki języka JavaScript stosowanej we wszystkich przykładach i dodać do niej tę funkcję jako pierwszy element. W ten sposób można napisać kod funkcji raz i zapomnieć o niej. Przygotowywanie obiektu do użytku
|
49
Dla uproszczenia biblioteka nosi nazwę addingajax.js (na podstawie oryginalnego tytułu tej książki). Ponieważ przykłady w tej książce używają także innych bibliotek, funkcje i obiekty z tej biblioteki mają przedrostek „aa” (na przykład aaGetXmlHttpRequest zamiast samego getXmlHttpRequest), co pozwala odróżnić je od funkcji bibliotecznych mających taką samą nazwę. Na początku będzie to mała biblioteka, jednak z czasem zacznie się rozrastać. W ramach krótkiego przypomnienia: cały przykładowy kod, włączając w to ostateczną wersję biblioteki addingajax.js, można pobrać ze strony internetowej niniejszej książki (http://helion.pl/AJAXIM.htm).
Przypomnij sobie kod JavaScript z listingu 2.4. Sposób obsługi zwróconego obiektu zależy od tego, jak program ma go używać. Zwykle obiekt XMLHttpRequest jest przypisywany do zmiennej globalnej, dzięki czemu poszczególne funkcje mogą z niego korzystać w różnych miejscach. Jak wkrótce zobaczysz, obiekt XMLHttpRequest powinien być dostępny przynajmniej w funkcji callback. Jest to jeden z niewielu przypadków, kiedy stosowanie zmiennych globalnych w języku JavaScript jest przyjętą (choć z oporami) praktyką. W wielu bibliotekach ten obiekt jest używany w ramach innych, bardziej złożonych obiektów. Inny aspekt braku zgodności między przeglądarkami polega na tym, że niektóre starsze przeglądarki oparte na Mozilli mogą niepoprawnie obsługiwać żądania, jeśli wynik nie zostanie zwrócony z prawidłowym nagłówkiem XML MIME text/xml. Przeglądarki zgodne z Mozillą mają dodatkową metodę, overrideMimeType (wywoływaną z parametrem text/xml), do rozwiązywania takich problemów. Jednak ta metoda jest niedostępna w obiektach XMLHttpRequest niektórych przeglądarek, na przykład w Internet Explorer 7.
Obiekt XMLHttpRequest został utworzony. Co można z nim teraz zrobić?
Przygotowywanie i wysyłanie żądania Przygotowywanie i wysyłanie żądań do usług sieciowych to prawdopodobnie jeden z najłatwiejszych aspektów Ajaksa. Wystarczy podjąć kilka decyzji co do parametrów, wywołać kilka funkcji — i gotowe. W czasie przygotowywania żądania pierwszą decyzją jest wybór jednej z metod HTTP używanych wraz z żądaniem. Przy dodawaniu Ajaksa do witryny często trzeba używać istniejących usług sieciowych i utworzyć zgodny z nimi interfejs klienta w języku JavaScript. Jednak jeśli programista ponadto modyfikuje interfejs usług sieciowych, warto poświęcić nieco czasu na przegląd metod HTTP używanych do wymiany danych między klientem a usługami. Pozwoli to zmaksymalizować wydajność i bezpieczeństwo aplikacji.
Żądania GET i POST HTTP i inne żądania zgodne z REST Zakładam, że tworzyłeś już aplikacje sieciowe i znasz żądania GET oraz POST HTTP. Obiekt XMLHttpRequest obsługuje obie te metody oraz wiele innych. Stosowanie wielu z nich nie ma większego sensu w aplikacjach ajaksowych, jednak cztery metody nadają się tu szczególnie dobrze:
50
|
Rozdział 2. Elementy Ajaksa
GET
Służy do pobierania informacji z serwera internetowego. Parametry są dołączane do adresu URL, a sam adres jest udostępniany w żądaniu.
POST
Służy do przesyłania informacji na serwer internetowy. Dane są przekazywane na serwer za pomocą funkcji send.
DELETE
Służy do przesyłania żądania usunięcia określonego obiektu. Dane są przekazywane na serwer za pomocą funkcji send.
PUT
Służy do przesyłania aktualizacji na serwer internetowy. Dane są przekazywane na serwer za pomocą funkcji send.
Dwie pierwsze metody, GET i POST, są powszechnie używane w aplikacjach sieciowych, jednak dwie następne mogą być dla programisty czymś nowym. Wszystkie cztery wymienione metody HTTP obsługiwane przez usługi sieciowe są określane jako zgodne z aplikacjami REST. Aplikacje tego typu są oparte na podejściu REST (ang. Representational State Transfer), którego stosowanie jest zalecane przy udostępnianiu usług sieciowych. Potrzeba stosowania czterech odrębnych metod nie wynika tylko z semantyki. Żądania GET nie powinny prowadzić do skutków ubocznych — mają służyć jedynie do pobierania danych. Firma Google udostępniła kiedyś funkcję Google Web Accelerator w wersji beta. To narzędzie miało poprawiać wydajność aplikacji, automatycznie przetwarzając wszystkie żądania GET na stronie i zapisując dane w pamięci podręcznej. Niestety, niektóre żądania GET na odwiedzanych stronach służyły do usuwania lub aktualizowania danych. Dostęp do takich stron za pomocą przeglądarki z tym narzędziem powodował spustoszenie. Choć wykorzystanie żądań GET przez Google było, delikatnie mówiąc, pochopne, można wykorzystać tę historię jako wartościową lekcję: metody GET należy używać wyłącznie do pobierania danych, a do tworzenia, aktualizowania i usuwania ich — metod: POST, PUT i DELETE. • Metoda GET służy do pobierania danych. • Metoda POST tworzy nowe obiekty. • Metody PUT należy używać do aktualizowania danych. • Metoda DELETE pozwala usuwać obiekty.
Jednak nie wszyscy stosują się do tych reguł. Żądań POST można używać także do pobierania informacji, aby lepiej zabezpieczyć parametry, a także do zarządzania tymi żądaniami do usług sieciowych, w których występuje wiele parametrów lub rozmiar poszczególnych parametrów jest dość duży. GET i POST są powszechnie obsługiwane przez przeglądarki. Niestety, nie jest to prawdą w przypadku metod PUT i DELETE. Ten brak obsługi oznacza, że choć metody te mogą być bardzo
użyteczne, nie nadają się do stosowania w aplikacjach ajaksowych (przynajmniej w czasie pisania tej książki), dlatego nie są używane w przykładach w tej książce. Po wyborze metody GET lub POST kolejnym zadaniem (opisuje je następny punkt) jest określenie, jak przekazywać parametry w każdym żądaniu HTTP. W zależności od rodzaju zastosowanej metody parametry można przekazać jako część adresu URL lub poprzez jedną z metod obiektu XMLHttpRequest. Przygotowywanie i wysyłanie żądania
|
51
Parametry Do żądanych usług sieciowych można przekazać więcej niż jeden parametr. Każdy parametr ma format klucz=wartość, a poszczególne pary są oddzielone od innych symbolem ampersanda (&). Jeśli używaną metodą HTTP jest POST, parametry należy przekazać za pomocą metody send obiektu: xmlhttp.send(parametry);
W przypadku żądań POST przed ich przesłaniem trzeba także określić rodzaj zawartości w nagłówku żądania. Poniższy kod ustawia tę wartość na kodowanie formularza: xmlhttp.setRequestHeader('Content-Type','application/x-www-form-urlencoded');
Jeśli programista użyje metody GET HTTP, musi dołączyć parametry na końcu adresu URL z żądaniem do usługi sieciowej, oddzielając je od samego adresu znakiem zapytania (?): http://serwerinternetowy.pl/pewnaap.php?param=wartość¶m2=wartość2
Jeśli parametry zawierają znaki specjalne protokołu HTTP, na przykład odstępy lub ampersandy, albo znaki języka HTML, takie jak nawiasy ostre, trzeba je przekształcić za pomocą sekwencji ucieczki. W tym celu można użyć wbudowanej funkcji encodeURIComponent języka JavaScript: var paramvalue = encodeURIComponent(wartość);
Ponieważ aplikacja Drinki wykonuje proste zapytanie i nie trzeba ukrywać parametru, można użyć metody GET do przesłania nazwy drinka dołączonej do adresu URL usługi sieciowej. Kod JavaScript tworzący obiekt XHR i adres URL żądania wygląda tak: if (!xmlhttp) xmlhttp = aaGetXmlHttpRequest(); if (!xmlhttp) return; var drink = encodeURIComponent(document.getElementById('drink').value); var qry = 'drink=' + drink; var url = 'drink.php?' + qry;
Wysyłanie żądania Aby wysłać żądanie HTTP, trzeba najpierw otworzyć obiekt XHR. Pierwszy parametr metody open określa metodę żądania HTTP. Po niej następuje adres URL, a następnie wartość true, która oznacza żądanie asynchroniczne: if (!xmlhttp) xml = aaGetXmlHttpRequest(); if (!xmlhttp) return; var drink = encodeURIComponent(document.getElementById('drink').value); var qry = 'drink=' + drink; var url = 'drink.php?' + qry; xmlhttp.open('GET', url, true);
Niezależnie od natury żądania zawsze przychodzi odpowiedź, nawet jeśli jest to tylko komunikat o błędzie. Jednak ponieważ żądanie jest asynchroniczne, przeglądarka nie oczekuje na odpowiedź. Aby obsługiwać odpowiedzi w środowisku asynchronicznym, trzeba określić wywoływaną zwrotnie funkcję, wywoływaną po zakończeniu obsługi żądania XHR. Wywoływana zwrotnie funkcja to dowolna metoda lub funkcja wywoływana w przyszłości w wyniku wystąpienia pewnego zdarzenia, na przykład po zakończeniu obsługi żądania skierowanego za pomocą Ajaksa do usługi sieciowej.
52
|
Rozdział 2. Elementy Ajaksa
W następnym wierszu wywoływana zwrotnie funkcja printRecipe jest przypisywana do metody obsługi zdarzenia onreadystatechange. Oznacza to, że funkcja ta zostanie wywołana przy każdej zmianie stanu obiektu żądania. Odpowiedzi są opisane bardziej szczegółowo w następnym punkcie. W dalszej części kodu wywoływana jest metoda send obiektu żądania z parametrem null (ponieważ jest to żądanie GET), a parametry są dołączane do adresu URL. Całe żądanie znajduje się w jednej funkcji — getRecipe: function getRecipe() { if (!xmlhttp) xml = aaGetXmlHttpRequest(); if (!xmlhttp) return; var drink = encodeURIComponent(document.getElementById('drink').value); var qry = 'drink=' + drink; var url = 'drink.php?' + qry; xmlhttp.open('GET', url, true); xmlhttp.onreadystatechange = printRecipe; xmlhttp.send(null); }
Gdyby żądanie było zgłaszane za pomocą metody POST, a nie GET, zmian w aplikacji klienckiej wymagałaby jedynie funkcja getRecipe. Zamiast dołączać parametry do adresu URL, należy je wtedy przekazać w wywołaniu metody send. Ponadto konieczne byłoby zakodowanie żądania przed jego przesłaniem, co ilustruje pogrubiony kod w poniższym fragmencie: function getRecipe() { if (!xmlhttp) xml = aaGetXmlHttpRequest(); if (!xmlhttp) return; var drink = encodeURIComponent(document.getElementById('drink').value); var qry = 'drink=' + drink; var url = 'drink.php'; xmlhttp.open('POST', url, true); xmlhttp.onreadystatechange = printRecipe; xmlhttp.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); xmlhttp.send(qry); }
Aplikacja potrzebuje zdarzenia, które uruchomi funkcję getRecipe. W nowej wersji aplikacji Drinki można dodać takie zdarzenie na kilka sposobów. Pierwszy z nich polega na połączeniu funkcji z metodą obsługi zdarzenia onchange elementu select, które jest aktywowane w momencie wybrania elementu z listy. Problem z takim rozwiązaniem polega na tym, że w przypadku pierwszej pozycji na liście użytkownik musi najpierw wybrać inny element, dzięki czemu możliwe będzie późniejsze wybranie pierwszego elementu i wywołanie zmiany. Następne podejście polega na przechwyceniu zdarzenia kliknięcia przycisku submit i użyciu żądań Ajaksa, zamiast zezwolenia na dalsze przesyłanie formularza. Może to jednak być nieintuicyjne dla użytkowników aplikacji, ponieważ przycisk submit służy do przesyłania formularza, a zgodnie z jedną z zasad tworzenia dobrych projektów ajaksowych należy minimalizować odstępstwa od standardowego działania stron. W artykule internetowym „Ajax Accessibility Overview” Becky Gibson z firmy IBM napisała: „(…) kiedy użytkownicy jawnie żądają aktualizacji, wybierając odnośnik lub przycisk, mogą być zaskoczeni, jeśli cała strona nie zostanie odświeżona”. Oczywiście, na tej zmianie opiera się Ajax. Jednak naruszając przyjęte działanie, należy robić to bez zaskakiwania użytkowników (więcej na ten temat w rozdziale 7.).
Przygotowywanie i wysyłanie żądania
|
53
Nawet uwzględniając „naruszanie” działania stron internetowych, lepszym z dwóch opisanych rozwiązań jest przechwycenie operacji przesyłania formularza, ponieważ gwarantuje, że użytkownicy będą mieli dostęp do wszystkich opcji z listy wyboru. Jednak nie można po prostu ingerować w przesyłanie formularza — trzeba anulować zdarzenie submission, a ponadto zagwarantować, że domyślna obsługa formularza nie będzie działać. W aplikacji znajduje się bezpośrednie przypisanie funkcji getRecipe do metody obsługi zdarzenia onsubmission. Następnie program anuluje zdarzenie, używając funkcji preventDefault w takich przeglądarkach, jak: Firefox, Opera, Safari, i zwracając false w przypadku przeglądarki Internet Explorer. W rozdziale 4. biblioteka addingajax.js jest wzbogacana o kilka funkcji pomocnych przy zarządzaniu zdarzeniami. Na razie Internet Explorer wymaga odmiennej obsługi zdarzeń, ponieważ nie udostępnia funkcji preventDefault.
Listing 2.5 przedstawia kod JavaScript specyficzny dla omawianej aplikacji. Listing 2.5. Kod JavaScript używający Ajaksa do pobrania przepisu drinka var xmlhttp; function getRecipe(evnt) { if (!xmlhttp) xmlhttp = aaGetXmlHttpRequest(); if (!xmlhttp) return; var drink = encodeURIComponent(document.getElementById('drink').value); var qry = "drink=" + drink; var url = 'drink.php?' + qry; xmlhttp.open('GET', url, true); xmlhttp.onreadystatechange = printRecipe; xmlhttp.send(null); if (evnt && evnt.preventDefault()) evnt.preventDefault(); return false; } function printRecipe() { if(xmlhttp.readyState == 4 && xmlhttp.status == 200) { alert(xmlhttp.getAllResponseHeaders()); alert(xmlhttp.responseText); } } window.onload=function() { document.getElementById('myform').onsubmit=getRecipe; };
W wywoływanej zwrotnie funkcji printRecipe, która zarządza żądaniem Ajaksa, sprawdzane są dwie właściwości obiektu XMLHttpRequest. Pierwszy test dotyczy tego, czy właściwość readyState ma wartość 4. Jak już wspomniałam, żądanie przyjmuje różny stan do czasu zwrócenia odpowiedzi, o czym informuje wartość 4. Ponadto w przypadku poprawnej odpowiedzi właściwość status ma wartość 200. Po udanym obsłużeniu żądania Ajaksa obie te wartości powinny być ustawione. W prawidłowo obsłużonym żądaniu Ajaksa właściwość readyState obiektu XMLHttpRequest ma wartość 4, a właściwość status — 200.
54
|
Rozdział 2. Elementy Ajaksa
W obecnej wersji wywoływana zwrotnie funkcja wyświetla tekst odpowiedzi (responseText) obiektu XMLHttpRequest, a także nagłówki odpowiedzi (getAllResponseHeaders). Następny punkt pokazuje, jak wykonać operacje na pobranym tekście. Nowa kliencka strona aplikacji przypomina poprzednią, ale zawiera dwa dodatkowe bloki dołączające skrypty języka JavaScript:
Nawet jeśli obsługa języka JavaScript jest wyłączona, wyjściowa aplikacja wciąż udostępnia oryginalną funkcjonalność opartą na formularzu. Przed bliższym przyjrzeniem się odpowiedziom warto zastanowić się nad jeszcze jednym zagadnieniem. W rozdziale 1. wspomniałam o obsłudze starszych wersji przeglądarki Internet Explorer przez domyślne zastosowanie funkcjonalności dla przeglądarek z wyłączoną obsługą skryptów. Wiele lat temu, zarządzając funkcjonalnością w różnych przeglądarkach, programiści przetwarzali właściwości appName i appVersion obiektu navigator, aby wykryć rodzaj przeglądarki i jej wersję. Zrezygnowano z tego rozwiązania na rzecz wykrywania obiektów, jak ilustruje to listing 2.4 w przypadku obiektu XMLHttpRequest. Jednak przy sprawdzaniu specyficznej przeglądarki, w tym przypadku — starszych wersji przeglądarki Internet Explorer, sprawdzanie właściwości obiektu navigator to wydajna technika. W bibliotece addingajax.js znajduje się funkcja aaScreenIE, która zwraca wartość false, jeśli przeglądarka to nie Internet Explorer lub jeśli jest to wersja 6.x albo nowsza tej przeglądarki. W przeciwnym razie zwracana wartość to true: // Ochrona przed starszymi wersjami przeglądarki Internet Explorer function aaScreenIE() { if (navigator.appName == 'Microsoft Internet Explorer') { msie=navigator.appVersion.split("MSIE") version=parseFloat(msie[1]); if (version >= 6) return false; } else return false; return true; }
Następnie należy zmodyfikować metodę obsługi zdarzenia window.onload, aby w przypadku użycia przeglądarki Internet Explorer w wersji starszej niż 6 aplikacja nie dodawała metod obsługi zdarzenia: // Interwencja w przesyłanie formularza window.onload=function() { if (aaScreenIE()) return; document.getElementById('myform').onsubmit=getRecipe; alert(navigator.appName + " " + navigator.appVersion); };
Dzięki temu filtrowi nie trzeba martwić się o to, że użytkownicy korzystający ze starszych, niezgodnych wersji przeglądarki Internet Explorer będą otwierać aplikacje ajaksowe, które prawdopodobnie nie będą w nich działać. W zamian osoby używające starszych przeglądarek będą mogły korzystać z tradycyjnej funkcjonalności opartej na formularzach. Pora zająć się odpowiedziami w Ajaksie.
Przygotowywanie i wysyłanie żądania
|
55
Przetwarzanie ajaksowych odpowiedzi Aby przetworzyć ajaksową odpowiedź i wyświetlić przepis, trzeba wprowadzić dwie zmiany w istniejącej aplikacji. Pierwsza z nich polega na umieszczeniu odpowiedzi na żądanie bezpośrednio na stronie, a druga — na modyfikacji układu strony, aby zapewnić miejsce na wyniki. W ostatnim przykładzie w poprzednim punkcie funkcja getRecipe zgłasza żądanie do tej samej aplikacji sieciowej drink.php, która była używana we wcześniejszej, tradycyjnej wersji, a istniejąca wersja aplikacji działającej po stronie serwera ma raczej tworzyć stronę internetową, niż zwracać dane. To działanie stanie się widoczne przy przeglądaniu zwróconych danych, zapisanych we właściwości responseText obiektu XHR. Zwracane jest zarówno formatowanie strony, jak i dane, podczas gdy w aplikacji ajaksowej potrzebne są tylko dane. Dlatego trzeba zmodyfikować program działający po stronie serwera, przekształcając go z aplikacji sieciowej na usługę sieciową, która będzie obsługiwać interakcję między komputerami przez internet. Mówiąc dokładniej, usługa sieciowa ma zwracać dane przy użyciu określonego protokołu (w tym przypadku będzie to REST), zamiast stosować prostsze rozwiązanie w postaci ustawiania zmiennych aplikacji. Ponieważ ta sama funkcjonalność ma być dostępna także w przypadku aplikacji bez obsługi skryptów, niezależnie od wybranego podejścia program musi działać zarówno wtedy, kiedy zostanie użyty jako tradycyjna aplikacja sieciowa, jak i wtedy, gdy zostanie użyty jako usługa. Na potrzeby omawianego przykładu wystarczą proste zmiany; wystarczy dodać instrukcję echo $result na końcu aplikacji recipe.php, aby wyświetlić wyniki. Po tej drobnej zmianie
aplikacja będzie działać niezależnie od tego, czy zostanie zagnieżdżona w bloku kodu PHP na stronie internetowej, czy zostanie wywołana jako usługa REST. Żądanie Ajaksa w funkcji getRecipe należy zmodyfikować tak, aby wywoływało usługę recipe.php: // Pobieranie przepisu za pomocą Ajaksa function getRecipe(evnt) { if (!xmlhttp) xmlhttp = aaGetXmlHttpRequest(); if (!xmlhttp) return; var drink = encodeURIComponent(document.getElementById('drink').value); var qry = "drink=" + drink; var url = 'recipe.php?' + qry; xmlhttp.open('GET', url, true); xmlhttp.onreadystatechange = printRecipe; xmlhttp.send(null); if (evnt && evnt.preventDefault()) evnt.preventDefault(); return false; }
Po uruchomieniu aplikacji ze zmodyfikowaną wersją funkcji getRecipe przetworzony tekst odpowiedzi będzie wyświetlał fragment kodu HTML, a nie całą stronę internetową. Jest to minimalna odpowiedź potrzebna w aplikacjach ajaksowych — dane sformatowane w taki sposób, że aplikacja potrafi je obsłużyć. Formatowanie zwracanych danych zależy od tego, co programista chce z nimi zrobić za pomocą kodu JavaScript. Ważne jest także, jakie inne aplikacje mają korzystać z danej usługi. W przypadku istniejących aplikacji sama usługa określa używany format. Jeśli ta usługa zwraca dane w określonym formacie, program kliencki musi się do tego dostosować. Przykładowy program zwraca już fragmenty kodu HTML. Pora dołączyć ten kod do dokumentu. 56
|
Rozdział 2. Elementy Ajaksa
Odpowiedzi szybko i łatwo — fragmenty kodu HTML i właściwość innerHTML Jednym z najprostszych sposobów na dołączenie tekstu odpowiedzi ajaksowych do strony jest dodanie fragmentu kodu HTML przy użyciu niestandardowej, ale powszechnie obsługiwanej właściwości innerHTML. Jeśli dane zwrócone przez usługę sieciową mają oparty na tekście format kodu HTML, można je dołączyć bezpośrednio do dokumentu, wprowadzając drobne zmiany lub w ogóle z nich rezygnując. Jak ilustrują to przykłady z poprzedniego punktu, odpowiedź ma już format HTML. Aby zakończyć zadanie, wystarczy zintegrować odpowiedź ze stroną. Prowadzi to do drugiego kluczowego zagadnienia w aplikacjach ajaksowych: jak dodawać nowe dane do stron. Przy zagnieżdżaniu odpowiedzi na stronie jednym z rozwiązań jest ręczne dodanie do strony pustego elementu, który służy jako miejsce na zwrócone dane. Jednak lepszym sposobem jest utworzenie takiego elementu dynamicznie. Dzięki temu, jeśli obsługa skryptów jest wyłączona, taki element nie ma wpływu na stronę. Co ważniejsze, jeśli element jest tworzony dynamicznie, nie trzeba modyfikować wszystkich poszczególnych stron, które używają danego elementu. Po dodaniu obiektu przeznaczonego na nowy przepis można albo uzyskać do niego dostęp i ponownie go użyć, albo uzyskać dostęp do niego, usunąć go, a następnie utworzyć od nowa przy każdym uruchomieniu aplikacji. To drugie rozwiązanie jest atrakcyjne, ponieważ nie trzeba przejmować się opróżnianiem elementu przed dodaniem nowych danych. W obecnej wersji aplikacji dane wyjściowe są dodawane do elementu div o identyfikatorze recipe. Ten nowy element jest dodawany do strony internetowej pod blokiem zawierającym formularz z listą wyboru. Aby prawidłowo wyświetlać dane, można rozbudować dynamiczny aspekt aplikacji, nie tworząc odrębnych ustawień stylu dla nowego elementu. Wymaga to po prostu dostosowania za każdym razem stylu elementu za pomocą właściwości style: elem.style.backgroundColor="#fff";
Jednak jeśli ustawienia stylu nie zmieniają się wielokrotnie (wtedy próba utworzenia nowych klas CSS dla każdej zmiany może się okazać kosztowna), lepszą techniką jest przypisanie zdefiniowanej reguły arkusza stylów do właściwości className elementu: obj.className = 'pewnaklasa';
Jeśli właściwość className nie istnieje, można utworzyć nową regułę arkusza stylów i zastosować ją dla nowego elementu. Niestety, występują znaczące różnice między przeglądarkami związane ze stosowaniem tej techniki, a jej obsługa jest w nich bardzo różna. W zamian można utworzyć klasę ręcznie i dodać ją do ogólnego arkusza stylów aplikacji. Warto zapoznać się z doskonałą serią How to create autorstwa Marka „Tarquina” Wiltona-Jonesa, a zwłaszcza z samouczkiem dotyczącym dynamicznych arkuszy stylów. Znajdują się tam szczegółowe informacje dotyczące dynamicznego tworzenia i dodawania reguł arkuszy stylów. Materiały te są dostępne na stronie http://www.howtocreate.co.uk/tutorials/javascript/domstylesheets. Ponieważ arkusz stylów jest używany przez wiele stron, można dodać odpowiednią regułę bezpośrednio do pliku drink.css:
Kliencka strona internetowa zawiera odnośnik do nowego pliku JavaScript — getdrink2.js. Ten plik ze skryptami zawiera kopię kodu JavaScript przedstawionego na listingu 2.5, jednak w tym przypadku funkcja getRecipe wywołuje nowo utworzoną usługę sieciową — recipe.php. Funkcja printRecipe obecnie przetwarza tekst odpowiedzi, zagnieżdżając zwrócony fragment HTML na stronie, co przedstawia listing 2.6. Listing 2.6. Dołączanie na stronie fragmentu kodu HTML z przepisem var xmlhttp; // Pobieranie przepisu za pomocą Ajaksa function getRecipe(evnt) { if (!xmlhttp) xmlhttp = aaGetXmlHttpRequest(); if (!xmlhttp) return; var drink = encodeURIComponent(document.getElementById('drink').value); var qry = "drink=" + drink; var url = 'recipe.php?' + qry; xmlhttp.open('GET', url, true); xmlhttp.onreadystatechange = printRecipe; xmlhttp.send(null); if (evnt && evnt.preventDefault()) evnt.preventDefault(); return false; } // Dodawanie przepisu do strony function printRecipe() { if(xmlhttp.readyState == 4 && xmlhttp.status == 200) { var body = document.getElementsByTagName('body'); // Usuwanie, jeśli istnieje if (document.getElementById('recipe')) { body[0].removeChild(document.getElementById('recipe')); } var recipe = document.createElement('div'); recipe.id = 'recipe'; recipe.className = 'recipe'; recipe.innerHTML = xmlhttp.responseText; body[0].appendChild(recipe); } } // Ingerencja w przesyłanie formularza window.onload=function() { if (aaScreenIE()) return; document.getElementById('myform').onsubmit=getRecipe; };
Funkcja uzyskuje dostęp do elementu div z przepisem, dołącza do niego pobrany tekst przepisu za pomocą właściwości innerHTML i formatuje przy użyciu nazwy klasy — recipe. Rysunek 2.2 przedstawia stronę po wybraniu jednego z przepisów.
58
|
Rozdział 2. Elementy Ajaksa
Rysunek 2.2. Aplikacja Drinki wyświetlająca przepis pobrany za pomocą żądania Ajaksa i dołączony przy użyciu właściwości innerHTML
Przy użyciu kodu HTML zwróconego jako fragment tekstu przez usługę sieciową można zmodyfikować dowolny element HTML, do którego istnieje dostęp przy użyciu skryptów. Dlatego żądania Ajaksa z wynikami w postaci fragmentów kodu HTML oraz właściwość innerHTML są tak popularne w przypadku aplikacji, które kierują zapytania w celu pobrania danych i używają ich bezpośrednio na stronie. Jest to szybkie i łatwe podejście do Ajaksa, jednak z przedstawioną techniką wiąże się pewne ryzyko: trzeba mieć bardzo duże zaufanie do usługi sieciowej, aby bezpośrednio dodawać kod HTML bez wiedzy o tym, co zawiera. Nawet jeśli programista używa własnych usług, może popełnić błąd, który doprowadzi do tego, że strona będzie nieczytelna. Jeśli aplikacja zostanie odpowiednio zaatakowana, napastnik będzie mógł umieścić na stronie skrypt, który spowoduje problemy po stronie użytkowników. Stosując omówione podejście, należy pamiętać o następujących zagadnieniach: • Zagnieżdżanie fragmentów kodu XHTML za pomocą właściwości innerHTML wymaga
dużego zaufania do używanych usług sieciowych. • Zagnieżdżane fragmenty mogą być nieprawidłowo sformatowane i powodować, że stro-
na będzie nieczytelna. • Napastnicy mogą przechwycić żądanie i umieścić na stronie znaczniki niebezpieczne dla
użytkowników aplikacji. Jest też inne podejście do korzystania z XHTML, jednak najpierw dowiesz się, jak przetwarzać dokumenty XML zamiast fragmentów kodu HTML. Przetwarzanie ajaksowych odpowiedzi
|
59
Używanie bardziej tradycyjnego kodu XML W czasie tworzenia Ajaksa uważano, że wszystkie odpowiedzi powinny być sformatowane jako dobrze zdefiniowany kod XML, a do przekształcania danych należy używać kodu XSLT. Jeśli potrzebny jest dostęp do poszczególnych komponentów odpowiedzi, można przetworzyć kod XML za pomocą funkcji wbudowanych w silnik języka JavaScript. Oczywiście, nic nie jest tak proste, jak wygląda na pierwszy rzut oka, a używanie formatu XML wiąże się z pewnymi wyzwaniami, z których wcale nie najmniejszym jest duża ilość kodu potrzebna do jego wydajnej obsługi. Aby zademonstrować używanie odpowiedzi sformatowanych jako XML, zmieniłam program PHP z listingu 2.3, wysyłając nazwę drinka, jego składniki i instrukcję przygotowania jako odrębne elementy kodu XML. Ponadto nagłówek XML jest ustawiony na typ MIME text/xml. Sformatowanie danych w taki sposób wpływa na dwie rzeczy: po pierwsze, kod XML jest wczytywany i przetwarzany jako dokument XML i można go pobrać za pomocą właściwości responseXML, a nie responseText; po drugie, dostęp do każdego elementu danych można uzyskać za pomocą metod do obsługi modelu DOM. Listing 2.7 przedstawia kod nowej usługi sieciowej. Jest on zapisany w pliku recipe2.php. Listing 2.7. Usługa sieciowa zwracająca przepisy na drinki. Odpowiedź jest sformatowana jako dobrze zdefiniowane dane XML Brak nazwy drinka"; } else { // Usuwanie odstępów z początku i końca szukanej nazwy $search = trim($drink); switch($search) { case "TEA" : $result = "Gorąca herbata" . "liście herbaty" . "Zagotuj wodę. Zalej nią liście herbaty. " . "Parz pięć minut. Po odcedzeniu można podawać."; break; case "APPLETINI" : $result = "Appletini" . "30 ml wódki" . "15 ml Sour Apple Pucker lub innego sznapsa " . "jabłkowego" . "Wymieszaj sznapsa z wódką w szklance z lodem. " . "Przelej do kieliszka do martini. " . "Udekoruj plasterkiem jabłka lub rodzynkami."; break; case "NONCHAMP" : $result = "Szampan bezalkoholowy" . "960 ml wody sodowej" . "360 zmrożonego koncentratu z białych " . "winogron" . "Wymieszaj wodę sodową" . " z sokiem z białych winogron."; break; case "SWMPMARGARITA" :
60
|
Rozdział 2. Elementy Ajaksa
$result = "Swamp Margarita" . "45 ml dobrej tequili" . "22 ml Cointreau" . "22 ml Grand Marnier" . "15 ml soku cytrynowego" . "60 ml sour mix" . "kilka zielonych oliwek" . "Wymieszaj wszystkie składniki. Schładzaj przez " . "godzinę. Wrzuć na dno wysokiej szklanki kilka zielonych oliwek. " . "Wlej margeritę na oliwki. Po dziesięciu minutach można podawać." . ""; break; case "LEMON" : $result = "Kropla cytryny" . "30 ml wódki cytrynowej" . "30 ml soku cytrynowego" . "łyżeczka cukru" . "Wymieszaj z lodem, " . "odcedź i podawaj."; break; default : $result = "Nie znaleziono przepisu"; break; } $result = '' . "" . $result . ""; header("Content-Type: text/xml; charset=utf-8"); echo $result; } ?>
Ponieważ XML to kapryśny format, warto najpierw przetestować samą usługę sieciową, uruchamiając aplikację i ręcznie podając nazwę drinka: http://pewnaap.pl/recipe2.php?drink=LEMON
Jeśli dane XML zwrócone przez serwer są dobrze zdefiniowane i aplikacja działa bez problemów, obiekt responseXML powinien zawierać wynik. Na listingu 2.7 kod XML jest zapisany na stałe w aplikacji PHP. Jednak w aplikacjach produkcyjnych do udostępniania danych w formacie XML należy używać bibliotek XML. Z tego samego powodu po stronie klienta lepiej jest generować treść za pomocą modelu DOM, a nie metody document.write, i używać funkcji do obsługi danych XML w celu sprawdzania, czy kod XML jest prawidłowo zbudowany, wszystkie elementy są poprawnie zakończone i nie występują nieoczekiwane efekty uboczne z powodu „złego” kodu XML. PHP5 udostępnia funkcjonalność do obsługi DOM XML podobną do tej dostępnej w języku JavaScript. Poniższy fragment to przykład utworzenia przepisu na gorącą herbatę za pomocą modelu DOM: // Tworzenie nowego dokumentu XML $dom = new DOMDocument('1.0', 'utf-8'); // Główny element $recipe = $dom->createElement('recipe'); $dom->appendChild($recipe);
Przetwarzanie ajaksowych odpowiedzi
|
61
// Tworzenie nagłówka z nazwą $title = $dom->createElement('title'); $title->appendChild($dom->createTextNode("Gorąca herbata"); $recipe->appendChild($title);
Po stronie usługi sieciowej sformatowanie odpowiedzi jako kodu XML jest łatwe. Przetwarzanie takich danych po stronie ajaksowego klienta jest nieco bardziej skomplikowane. Funkcję printRecipe trzeba napisać od nowa i jak wkrótce zobaczysz, wymaga to dużej ilości kodu. Ponieważ istnieje dostęp do poszczególnych obiektów, składniki są wymieniane jako elementy ul, nazwa jest wyświetlana jako nagłówek, a instrukcje są wyświetlane jako akapit (listing 2.8). Wszystkie elementy są zagnieżdżone w jednym bloku recipe. Listing 2.8. Klient Ajaksa używający danych w formacie XML // Dodaje przepis do strony function printRecipe() { if(xmlhttp.readyState == 4 && xmlhttp.status == 200) { var body = document.getElementsByTagName('body'); // Usuwa przepis, jeśli istnieje if (document.getElementById('recipe')) { body[0].removeChild(document.getElementById('recipe')); } var recipe = document.createElement('div'); recipe.id = 'recipe'; recipe.className='recipe'; // Dodaje nagłówek var title = xmlhttp.responseXML.getElementsByTagName('title')[0].firstChild. nodeValue; var titleNode = document.createElement('h3'); titleNode.appendChild(document.createTextNode(title)); recipe.appendChild(titleNode); // Dodaje składniki var ul = document.createElement("ul"); var ingredients = xmlhttp.responseXML.getElementsByTagName('ingredient'); for (var i = 0; i < ingredients.length; i++) { var x = document.createElement('li'); x.appendChild(document.createTextNode(ingredients[i].firstChild.nodeValue)); ul.appendChild(x); } recipe.appendChild(ul); // Dodaje instrukcję var instr = xmlhttp.responseXML.getElementsByTagName('instruction')[0].firstChild. nodeValue; var instrNode = document.createElement('p'); instrNode.appendChild(document.createTextNode(instr)); recipe.appendChild(instrNode); // Dołącza do ciała body[0].appendChild(recipe); } }
Warto dokładniej przyjrzeć się kodowi funkcji printRecipe. Funkcja getElementsByTagName modelu DOM służy do uzyskania dostępu do elementów powiązanych z każdym obiektem — nazwy, składników i instrukcji. Składników może być kilka, dlatego kod używa ich jako tablicy i przetwarza w odpowiedni sposób. 62
|
Rozdział 2. Elementy Ajaksa
W przypadku każdego obiektu kod uzyskuje dostęp do tekstu węzła podrzędnego powiązanego z danym elementem i używa tego tekstu do zapełnienia węzła textNode modelu DOM. Następnie funkcja dołącza ten węzeł do nowego elementu niezależnie od tego, czy jest to nazwa, element listy czy akapit z instrukcjami. Element jest następnie dołączany do obiektu recipe w głównym dokumencie. A oto kolejne etapy:
1. Pobieranie elementów na podstawie nazwy znacznika (nazwy elementu): titleObj = xmlhttp.responseXML.getElementsByTagName('title');
2. Po uzyskaniu dostępu do kolekcji (funkcja getElementsByTagName zwraca listę nodeList,
która jest przetwarzana jako tablica) należy uzyskać dostęp do każdego węzła (lub pierwszego w przypadku pojedynczego elementu), znaleźć pierwszy węzeł podrzędny danego węzła i pobrać jego wartość: title = titleObj[0].firstChild.nodeValue;
3. Tworzenie nowego elementu h3 do przechowywania zawartości: titleText = document.createElement('h3');
4. Tworzenie węzła textNode do przechowywania wartości tekstowej z danych XML: tileText = document.createTextNode(title);
5. Dołączanie węzła textNode do nowo utworzonego węzła: titleNode.appendChild(titleText);
6. Dołączanie tego węzła do elementu, który ma go zawierać: recipe.appendChild(titleNode);
Po przetworzeniu danych obejmujących nazwę, składniki i instrukcje strona powinna wyglądać tak jak na rysunku 2.3.
Rysunek 2.3. Aplikacja „Pobierz drinki” oparta na danych w formacie XML Przetwarzanie ajaksowych odpowiedzi
|
63
Listing 2.8 zawiera znacznie więcej kodu niż rozwiązanie używające właściwości innerHTML, ale wyniki są bardziej eleganckie, ponieważ można sformatować poszczególne elementy w przeglądarce, a nie po stronie serwera. Można jednak użyć jeszcze innej techniki. Pozwala ona uprościć dostęp do danych, umożliwiając przy tym formatowanie poszczególnych elementów. To podejście używa notacji skryptowej zwanej JSON.
Upraszczanie przetwarzania za pomocą formatu JSON JSON (ang. JavaScript Object Notation) to sposób przekazywania odpowiedzi z danymi za pomocą opartej na tekście notacji obiektowej języka JavaScript. Dostęp do takich danych w tradycyjnych aplikacjach ajaksowych odbywa się przez użycie funkcji eval do „utworzenia” obiektu zdefiniowanego w odpowiedzi. Format JSON ma postać par klucz-wartość: object { klucz : wartość }
Może też przyjmować postać tablicy: array [wartość, wartość, ..., wartość]
Można też zastosować obie formy jednocześnie: object { klucz: [{ klucz : wartość}, {klucz : wartość}] }
Tworzenie obiektów odbywa się podobnie jak w poniższym kodzie. W instrukcji eval używana jest tu właściwość responseText: eval("theObj = (" + xmlhttp.responseText ")");
Jeśli zwrócone dane obejmują nazwę obiektu, można użyć następującej składni: eval(xmlhttp.responseText);
Funkcja eval tworzy obiekt, do którego można uzyskać dostęp w taki sam sposób jak do zwykłych obiektów i tablic języka JavaScript. Nie trzeba używać metod modelu DOM do obsługi danych XML, co miało miejsce w poprzednim punkcie. Krótkie przypomnienie: funkcja eval interpretuje tekst jako kod języka JavaScript i wykonuje instrukcje zawarte w tekście tak, jakby był to zwykły kod JavaScript.
Oczywiście, jeśli bezpośrednie zagnieżdżanie na stronie kodu HTML w takiej postaci, w jakiej się go otrzymało, wydaje się ryzykowne, tym bardziej dotyczy to używania funkcji eval na nieznanych danych. Jeśli programista zna lub kontroluje źródło danych, takie rozwiązanie powinno być dość bezpieczne. W przeciwnym razie (lub jeśli programista chce zapewnić większe bezpieczeństwo) można użyć parsera danych JSON, na przykład parseJSON dostępnego w witrynie poświęconej temu formatowi (http://www.json.org/js.html): var obj = xmlhttp.responseText.parseJSON();
Aby skrypty w przykładach w tym rozdziale były prostsze, funkcja eval jest wywoływana bezpośrednio na danych. Zastosowanie formatu JSON wymaga wprowadzenia zmian zarówno w usłudze sieciowej, jak i w kliencie ajaksowym. Skoncentrujmy się najpierw na usłudze. Listing 2.9 zawiera usługę sieciową w języku PHP, recipe3.php, zmodyfikowaną w porównaniu do listingów 2.3 i 2.7. Ta wersja zwraca oparte na tekście dane w formacie JSON. 64
|
Rozdział 2. Elementy Ajaksa
Listing 2.9. Usługa sieciowa zwracająca dane w formacie JSON Brak nazwy drinka"; } else { // Usuwanie odstępów z początku i końca szukanej nazwy $search = trim($drink); switch($search) { case "TEA" : $result = "{ 'title' : 'Gorąca herbata'," . " 'ingredients' : [ { 'ingredient' : 'liście herbaty' }," . " {'ingredient' : 'woda'}]," . " 'instruction' : 'Zagotuj wodę. Zalej nią liście herbaty." . " Parz pięć minut. Po odcedzeniu można podawać.'}"; break; case "APPLETINI" : $result = "{ 'title' : 'Appletini', " . " 'ingredients' : [ { 'ingredient' : '30 ml wódki' }, " . " { 'ingredient' : '15 ml Sour Apple Pucker lub innego sznapsa'}]," . " 'instruction' : 'Wymieszaj sznapsa z wódką w szklance z lodem. " . "Przelej do kieliszka do martini. Udekoruj plasterkiem " . "jabłka lub rodzynkami.'}"; break; case "NONCHAMP" : $result = "{ 'title' : 'Szampan bezalkoholowy', " . " 'ingredients' : [ { 'ingredient' : '960 ml wody sodowej' }, " . " { 'ingredient' : '360 ml zmrożonego koncentratu z białych" . " winogron'}]," . " 'instruction' : 'Wymieszaj wodę sodową z koncentratem z białych" . " winogron.'}"; break; case "SWMPMARGARITA" : $result = "{ 'title' : 'Swamp Margaria', " . " 'ingredients' : [ { 'ingredient' : '45 ml dobrej tequili'}, " . " { 'ingredient' : '22 ml Cointreau'}, " . " { 'ingredient' : '22 ml Grand Marnier'}, " . " { 'ingredient' : '15 ml soku cytrynowego'}, " . " { 'ingredient' : '60 ml sour mix<'}, " . " { 'ingredient' : 'kilka zielonych oliwek'}]," . " 'instruction' : 'Wymieszaj wszystkie składniki. Schładzaj przez " . "godzinę. Wrzuć na dno wysokiej szklanki kilka zielonych oliwek. " . "Wlej margeritę na oliwki. Po dziesięciu minutach należy odcedzić " . "i można podawać.'}"; break; case "LEMON" : $result = "{ 'title' : 'Kropla cytryny', " . " 'ingredients' : [ { 'ingredient' : '30 ml wódki cytrynowej'}, " . " { 'ingredient' : '30 ml soku cytrynowego'}, " . " { 'ingredient' : 'łyżeczka cukru'}]," . " 'instruction' : 'Wymieszaj z lodem, " . "odcedź i podawaj.'}"; break; default : $result = "{ 'title','Brak przepisu'}"; break; } echo $result; } ?>
Przetwarzanie ajaksowych odpowiedzi
|
65
Struktura każdego przepisu na listingu 2.9 to obiekt ograniczony przez nawiasy klamrowe. Taki obiekt zawiera nazwę i instrukcje jako pary właściwość-wartość, a składniki są zapisane jako tablica obiektów. Podobnie jak w przypadku XML większość języków używanych do tworzenia usług sieciowych ma biblioteki także do obsługi formatu JSON. Na przykład w języku PHP jest to php-json, która obecnie wchodzi w skład języka PHP 5.2.0.
Listing 2.10 przedstawia zmodyfikowaną wersję funkcji printRecipe. Warto zauważyć, że jedyna różnica między kodem tego przykładu a listingiem 2.8 polega na sposobie pobierania danych. Tworzenie nowych elementów odbywa się identycznie. Listing 2.10. Klient aplikacji Drinki używający danych w formacie JSON // Dodaje przepis do strony function printRecipe() { if(xmlhttp.readyState == 4 && xmlhttp.status == 200) { var body = document.getElementsByTagName('body'); // Usuwa przepis, jeśli istnieje if (document.getElementById('recipe')) { body[0].removeChild(document.getElementById('recipe')); } var recipe = document.createElement('div'); recipe.id = 'recipe'; recipe.className='recipe'; var recipeObj = eval("(" + xmlhttp.responseText + ")"); // Dodaje nagłówek var title = recipeObj['title']; var titleNode = document.createElement('h3'); titleNode.appendChild(document.createTextNode(title)); recipe.appendChild(titleNode); // Dodaje składniki var ul = document.createElement('ul'); var ingredients = recipeObj.ingredients; for (var i = 0; i < ingredients.length; i++) { var x = document.createElement('li'); x.appendChild(document.createTextNode(ingredients[i].ingredient)); recipe.appendChild(x); } recipe.appendChild(ul); // Dodaje instrukcję var instr = recipeObj.instruction; var instrNode = document.createElement('p'); instrNode.className='instruction'; instrNode.appendChild(document.createTextNode(instr)); recipe.appendChild(instrNode); body[0].appendChild(recipe); } }
Efekt zastosowania w aplikacji danych w formacie JSON jest taki sam jak w przypadku formatu XML, jednak dostęp do takich danych jest zwykle łatwiejszy, a ich obsługa w języku
66
|
Rozdział 2. Elementy Ajaksa
JavaScript — wygodniejsza. Jest to jeden z powodów, dla których w wielu platformach zarządzanie wymianą danych między serwerem a klientem odbywa się przy użyciu formatu JSON. Przetwarzanie obiektów JSON jest także bardziej wydajne niż stosowanie wielu parserów XML wbudowanych w przeglądarki, choć jeśli dane nie są szczególnie długie lub złożone, różnice nie będą zauważalne. JSON to także popularny format używany do obchodzenia zabezpieczeń języka JavaScript. To zagadnienie omówione jest w dalszej części rozdziału. Choć format JSON został stworzony doraźnie, następują próby standaryzacji jego składni. Na przykład wszystkie łańcuchy znaków w danych JSON muszą używać cudzysłowów, a nie apostrofów. Ponadto wszystkie słowa kluczowe muszą znajdować się w cudzysłowach. Więcej informacji na ten temat zawiera artykuł Keep your JSON valid Simona Willisona (http://simonwillison.net/2006/Oct/11/json) i JSON is not just Object Notation Jesse Skinnera (http://www.thefutureoftheweb.com/blog/2006/8/json-is-not-just-object-notation). Jesse był także recenzentem technicznym tej książki.
JSON i XML są dużo łatwiejsze w użytku, jeśli programista chce w odmienny sposób obsługiwać zwracane elementy, choć korzystanie z właściwości innerHTML i fragmentów kodu HTML może być szybsze. Jednak istotna różnica między trzema omówionymi wcześniej formatami polega na tym, że usługa zwracająca dane dla aplikacji udostępnia je także aplikacjom bez obsługi skryptów. Strona przetwarzająca formularz przesłany w przypadku braku obsługi skryptów może używać zarówno danych XML, jak i JSON — dostępne są biblioteki języka PHP, które wykonują większość operacji za programistę. Lepszym rozwiązaniem jest albo udostępnianie danych w różnych formatach (HTML, XML lub JSON) i umożliwienie klientowi wyboru (na przykład za pomocą parametru typu format=JSON), albo zastosowanie danych XHTML, po upewnieniu się, że mają format XML umożliwiający bezpośrednie umieszczenie ich na stronie lub przekazanie do parsera kodu XML.
Fragmenty kodu (X)HTML Nie ma powodu, dla którego aplikacje ajaksowe nie mogą przetwarzać kodu XHTML za pomocą technik do obsługi danych XML. Wystarczy nieco uporządkować i przygotować dane. Jedna zmiana w stosunku do poprzedniego przykładu, używającego fragmentów kodu HTML, polega na zastosowaniu bardziej sformalizowanego formatu, włączając w to utworzenie składników jako listy nieuporządkowanej, nazwy jako nagłówka i instrukcji jako akapitów. Mówiąc inaczej, format będzie pasował do danych wyjściowych. Następna zmiana polega na umieszczeniu danych w obrębie pojedynczego elementu. Posłuży do tego element div o nazwie recipe. Listing 2.11 przedstawia zmodyfikowaną wersję programu — recipe4.php. Listing 2.11. Dane w formacie XHTML Brak nazwy drinka"; } else { // Usuwanie odstępów z początku i końca szukanej nazwy
Zagotuj wodę. Zalej nią liście herbaty. " . "Parz pięć minut. Po odcedzeniu można podawać.
"; break; case "APPLETINI" : $result = "
Appletini
" . "
30 ml wódki
" . "
15 ml Sour Apple Pucker lub innego sznapsa
" . "
Wymieszaj sznapsa z wódką w szklance z lodem. Przelej do ". "kieliszka do martini. " . "Udekoruj plasterkiem jabłka lub rodzynkami.
"; break; case "NONCHAMP" : $result = "
Szampan bezalkoholowy
" . "
960 ml wody sodowej
" . "
360 zmrożonego koncentratu z białych winogron
" . "
Wymieszaj wodę sodową" . " z koncentratem z białych winogron.
"; break; case "SWMPMARGARITA" : $result = "
Swamp Margarita
" . "
45 ml dobrej tequili
" . "
22 ml Cointreau
" . "
22 ml Grand Marnier
" . "
15 ml soku cytrynowego
" . "
60 ml sour mix
" . "
kilka zielonych oliwek
" . "
Wymieszaj wszystkie składniki. Schładzaj przez godzinę. " . "Wrzuć na dno wysokiej szklanki kilka zielonych oliwek. " . "Wlej margeritę na oliwki. Po dziesięciu minutach należy odcedzić " . "i można podawać.
"; break; case "LEMON" : $result = "
Kropla cytryny
" . "
30 ml wódki cytrynowej
" . "
30 ml soku cytrynowego
" . "
łyżeczka cukru
" . "
Wymieszaj z lodem, " . "odcedź i podawaj.
"; break; default : $result = "
Brak przepisu
"; break; } $result = "
" . $result . "
"; echo $result; } ?>
Stronę używaną w przypadku braku obsługi skryptów trzeba nieco zmodyfikować, aby móc umieścić wyniki działania aplikacji PHP bezpośrednio w ciele strony, jak ilustruje to listing 2.12. Listing 2.12. Strona do przesyłania formularza w przypadku braku obsługi skryptów
68
|
Rozdział 2. Elementy Ajaksa
Drinki
Drinki
Kliencka strona internetowa pozostaje prawie taka sama. Jedyne zmiany to przesyłanie formularza do strony drink2.php i używanie zagnieżdżonego skryptu o nazwie getdrink5.js. Nowy kod JavaScript jest bardzo podobny do tego z listingu 2.8, jednak zamiast korzystać z uniwersalnego dokumentu XML, używa standardowych danych XHTML. Aby obiekt XMLHttpRequest przetwarzał dokumenty w formacie XML, w usłudze sieciowej trzeba użyć nagłówka XML. Jednak ta usługa jest zagnieżdżona w innym dokumencie, dlatego nagłówek jest już wysyłany wcześniej i nie można zwrócić jego drugiej wersji. Potrzebny jest sposób na przetwarzanie kodu XML bez zależności od obiektu XMLHttpRequest. Aby obejść problem związany z nagłówkiem, w klienckim kodzie JavaScript można utworzyć egzemplarz parsera XML i wczytać łańcuch znaków w formacie XHTML zwrócony do tego nowego parsera jako zwykły tekst. Używam do tego techniki działającej w różnych przeglądarkach, utworzonej na podstawie artykułu z witryny W3 Schools (http://www.w3schools.com/dom/ dom_parser.asp). To rozwiązanie używa obiektu DOMParser w przypadku przeglądarek zgodnych z W3C (Safari, przeglądarki oparte na Gecko, Opera itd.), a obiektu ActiveXObject — w przeglądarkach Internet Explorer: // Kod dla przeglądarki Internet Explorer if (window.ActiveXObject) { var doc=new ActiveXObject("Microsoft.XMLDOM"); doc.async="false"; doc.loadXML(recipeObj); } // Kod dla Mozilli, Firefoksa, Opery itd. else { var parser=new DOMParser(); var doc=parser.parseFromString(recipeObj,"text/xml"); } xDoc = doc.documentElement;
Do przetwarzania danych w nowo utworzonym dokumencie XML można użyć metod modelu DOM w taki sam sposób jak do przetwarzania kodu XML dostępnego w obiekcie responseXML. Listing 2.13 zawiera nową wersję funkcji printRecipe języka JavaScript, która używa właśnie tej techniki. Listing 2.13. Używanie parsera danych XML do obsługi fragmentów kodu XHTML // Dodaje przepis do strony function printRecipe() { if(xmlhttp.readyState == 4 && xmlhttp.status == 200) {
Przetwarzanie ajaksowych odpowiedzi
|
69
if (window.ActiveXObject) { var doc=new ActiveXObject("Microsoft.XMLDOM"); doc.async="false"; doc.loadXML(xmlhttp.responseText); } else { var parser=new DOMParser(); var doc=parser.parseFromString(xmlhttp.responseText,"text/xml"); } var xDoc = doc.documentElement; var body = document.getElementsByTagName('body'); // Usuwa przepis, jeśli istnieje if (document.getElementById('recipe')) { body[0].removeChild(document.getElementById('recipe')); } var recipe = document.createElement('div'); recipe.id = 'recipe'; recipe.className='recipe'; // Dodaje nagłówek var title = xDoc.getElementsByTagName('h3')[0].firstChild.nodeValue; var titleNode = document.createElement('h3'); titleNode.appendChild(document.createTextNode(title)); recipe.appendChild(titleNode); // Dodaje składniki var ul = document.createElement("ul"); var ingredients = xDoc.getElementsByTagName('li'); for (var i = 0; i < ingredients.length; i++) { var x = document.createElement('li'); x.appendChild(document.createTextNode(ingredients[i].firstChild.nodeValue)); ul.appendChild(x); } recipe.appendChild(ul); // Dodaje instrukcję var instr = xDoc.getElementsByTagName('p')[0].firstChild.nodeValue; var instrNode = document.createElement('p'); instrNode.appendChild(document.createTextNode(instr)); recipe.appendChild(instrNode); // Dołącza do ciała body[0].appendChild(recipe); } } // Interwencja w przesyłanie formularza window.onload=function() { if (aaScreenIE()) return; document.getElementById('myform').onsubmit=getRecipe; };
Strona wynikowa będzie wyglądała tak samo niezależnie od tego, czy do pobrania przepisu na drinki posłużyła technika oparta na Ajaksie czy rozwiązanie bez obsługi skryptów. Jak widać, w zakresie przetwarzania żądań Ajaksa możliwych jest wiele rozwiązań. Ich wspólnym elementem jest to, że są ograniczone przez zabezpieczenia języka JavaScript. Wszystkie usługi sieciowe muszą działać w tej samej domenie, która zwraca strony klienckie. Następny punkt opisuje, jak można „obejść” to zabezpieczenie, co jest niezbędne w przypadku tworzenia lub używania widgetów w aplikacji.
70
|
Rozdział 2. Elementy Ajaksa
Punkty końcowe, zabezpieczenia języka JavaScript i widgety Stosowanie obiektów XMLHttpRequest ma jeden istotny limit: ograniczenie do jednej domeny. Żądania zgłaszane za pomocą tego obiektu można kierować tylko do tej samej domeny, z której pochodzi dana strona. Jest to kluczowy element strefy bezpieczeństwa języka JavaScript (można pracować jedynie w obrębie własnej „strefy bezpieczeństwa”, unikając w ten sposób zamieszania, które mogłoby powstać, gdyby kod miał dostęp do dowolnej lokalizacji). Jedną z przyczyn stosowania tego ograniczenia jest chęć zagwarantowania, że strona internetowa zgłaszająca żądanie XHR nie zostanie użyta przez szkodliwą witrynę w celu uzyskania dostępu do zasobów firmowego intranetu znajdujących się za zaporą sieciową. Pewne techniki umożliwiają obejście tego ograniczenia, jednak wraz z pojawianiem się nowych wersji przeglądarek jest ich coraz mniej. W przypadku aplikacji sieciowych ograniczenia związane z dostępem pomiędzy domenami zwykle nie stanowią problemu. W końcu większość usług sieciowych jest używana z tej samej domeny. Ponadto jeśli dane pochodzą z innej domeny, można utworzyć do pomocy pośrednika — aplikację działającą po stronie serwera, która uzyskuje dostęp do danych i przekazuje je klientowi. Jednak w pewnym przypadku nie można polegać na tym, że serwer będzie zarządzał dostępem do danych zewnętrznych za programistę — dzieje się tak przy korzystaniu z widgetów. Widgety są używane od dawna w aplikacjach stacjonarnych i innych środowiskach, są jednak stosunkowo nowe w programowaniu internetowym. Widgety to małe obiekty, zwykle umieszczane na pasku bocznym, które działają jak uproszczone aplikacje lub zapewniają dostęp do danych, na przykład pobierają informacje o pogodzie z witryn meteorologicznych lub dane o znacznikach z witryny del.icio.us. W rozdziale 3. przedstawiony jest widget z witryny weather.com. Do utworzenia go posłużyła jedna z bibliotek Ajaksa — Rico. Czasem pliki z kodem JavaScript udostępniającym funkcjonalność są w całości przechowywane na serwerze zdalnym, co oznacza, że nie będą występować problemy z dostępem między domenami, ponieważ zarówno kod JavaScript, jak i dane znajdują się na tym samym serwerze. W ten sposób działa Google Maps. Jednak czasem widgety wymagają, aby kod JavaScript był lokalny, ale dane i usługi sieciowe — zdalne. Co programista powinien wtedy zrobić?
Punkty końcowe JSON i dynamiczne tworzenie skryptów W opisanej sytuacji objawia się wartość JSON. Jest to coś więcej niż format czy struktura — to rodzaj bramy. Można utworzyć obiekt skryptowy w jednym dokumencie i domenie, przypisać do jego atrybutu src dane zwrócone z wywołania JSON skierowanego do punktu końcowego w innej domenie, a dane zostaną dołączone do strony klienckiej jako obiekty. Wszystkie usługi sieciowe to „punkty końcowe”, udostępniające usługi, których mogą używać aplikacje ajaksowe i inne. W tej książce nazwa „punkt końcowy” oznacza usługi, które umożliwiają stosowanie wywołań zwrotnych, co demonstruje niniejszy punkt.
Punkty końcowe, zabezpieczenia języka JavaScript i widgety
|
71
Aby opisane rozwiązanie działało, usługa zdalna musi udostępniać funkcjonalność umożliwiającą przekazanie w wywołaniu usługi nazwy wywoływanej zwrotnie funkcji. W żądaniach GET służy do tego parametr callback. Poniższy skrypt demonstruje to podejście i wywołuje punkt końcowy z danymi JSON z witryny del.icio.us, przekazując nazwę wywoływanej zwrotnie funkcji: var script = document.createElement('script'); script.type = 'text/javascript'; script.src = 'http://badges.del.icio.us/feeds/json/url/blogbadge?hash=b543788c882e78ef73bc012fbe4c4 eb1?callback=myFunction'; document.getElementsByTagName('head')[0].appendChild(script); function myFunction(data){ var obj = eval(data); alert(obj[0].hash); }
To żądanie spowoduje zwrócenie tekstu w formacie JSON i przekazanie tej struktury danych do funkcji wywoływanej zwrotnie podanej jako parametr callback: myFunction([...dane])
Ponieważ wywołanie jest używane jako atrybut src nowego elementu script, technika ta działa niemal tak samo jak wczytanie pliku z kodem JavaScript za pomocą istniejącego obiektu. Utworzenie elementu script, dodanie go do dokumentu i ustawienie właściwości src na zwrócone dane przekazywane do funkcji to odpowiednik uruchomienia skryptu na stronie. Jedyna różnica polega na tym, że dane używane jako argumenty w wywołaniu funkcji pochodzą ze zdalnego serwera, a nie z kodu. Poniższy fragment obejmuje kod z listingu 2.12, ale do utworzenia obiektu JavaScript używany jest blok z dynamicznym skryptem i wywoływana zwrotnie funkcja, a nie funkcja eval. Strona internetowa nie zmienia się, a jedynie wywołuje inny plik z kodem JavaScript. Po otrzymaniu żądania są z niego pobierane oba parametry — nazwa drinka i wywoływanej zwrotnie funkcji — a następnie do funkcji tej przekazywany jest obiekt JSON: echo $callback . '(' . $result . ')';
Po stronie klienta trzeba odpowiednio zmodyfikować funkcje getRecipe i printRecipe, co ilustruje listing 2.14. Funkcja printRecipe wygląda niemal tak samo jak wcześniej, jednak nie trzeba w niej tworzyć obiektu ani sprawdzać, czy żądanie zostało przetworzone, a jego status to 200. Wyniki zwrócone przez punkt końcowy są dodawane do dokumentu ze stroną internetową podobnie jak nowy plik ze skryptami. Listing 2.14. Skrypt używany przez kod z listingu 2.12, zmodyfikowany tak, aby używał wywoływanej zwrotnie funkcji JSON // Pobiera przepis za pomocą Ajaksa function getRecipe() { var drink = escape(document.forms[0].elements[0].value); var qry = "drink=" + drink; var url = 'http://burningbird.net/addingajax/examples/recipe3json.php?' + qry; var script = document.createElement('script'); script.type = 'text/javascript'; script.src = url + '&callback=printRecipe'; document.getElementsByTagName('head')[0].appendChild(script); }
72
|
Rozdział 2. Elementy Ajaksa
// Dodaje przepis do strony function printRecipe() { var body = document.getElementsByTagName('body'); // Usuwa przepis, jeśli istnieje if (document.getElementById('recipe')) { body[0].removeChild(document.getElementById('recipe')); } var recipe = document.createElement('div'); recipe.id = 'recipe'; recipe.className='recipe'; // Dodaje nagłówek var title = recipeObj['title']; var titleNode = document.createElement('h3'); titleNode.appendChild(document.createTextNode(title)); recipe.appendChild(titleNode); // Dodaje składniki var ul = document.createElement('ul'); var ingredients = recipeObj.ingredients; for (var i = 0; i < ingredients.length; i++) { var x = document.createElement('li'); x.appendChild(document.createTextNode(ingredients[i].ingredient)); ul.appendChild(x); } recipe.appendChild(ul);
}
// Dodaje instrukcję var instr = recipeObj.instruction; var instrNode = document.createElement('p'); instrNode.appendChild(document.createTextNode(instr)); recipe.appendChild(instrNode); body[0].appendChild(recipe);
}
Domena używana tu dla dynamicznego skryptu i jako punkt końcowy JSON to burningbird.net, podczas gdy oryginalna strona została pobrana z witryny Addingajax.com. Większość widgetów używających skryptów lokalnych do wywoływania zdalnych usług sieciowych działa w taki sposób i używa danych w formacie JSON. W dalszej części rozdziału poznasz wpływ dostępu do usług sieciowych z innych domen na bezpieczeństwo.
Czy trzeba stosować dane w formacie JSON? Nie. Jedyny kluczowy element tej techniki polega na tym, że usługa sieciowa dołącza wywoływaną zwrotnie funkcję do odpowiedzi, a także przekazuje tę odpowiedź jako argument do funkcji. Oznacza to, że dane mogą mieć inny format, na przykład XML.
Dynamiczne skrypty i XML Często utożsamia się przedstawione wcześniej podejście ze stosowaniem danych w formacie JSON. Nie jest to jednak konieczne. Można użyć danych w dowolnym formacie, który umożliwia przekazanie ich jako argumentu funkcji. Jedyne wymaganie polega na tym, że serwer musi przetworzyć parametr callback, a aplikacja ajaksowa musi zarządzać zwróconymi danymi. Punkty końcowe, zabezpieczenia języka JavaScript i widgety
|
73
Jeśli występuje zgodność między aplikacją a usługą sieciową, można nawet użyć większej liczby parametrów, choć należy tego unikać choćby w celu zachowania prostoty. Aby zademonstrować, że omówiona technika nie wymusza stosowania formatu JSON, zmodyfikowałam program PHP tworzący dane w formacie XML tak, aby przetwarzał także żądanie wywoływanej zwrotnie funkcji. Jedyna zmiana polega na tym, że program pobiera parametr callback ze zmiennej $_GET, a następnie przetwarza wywołanie zwrotne (jeśli jest dostępne) przed zwróceniem wyniku: $callback = $_GET['callback']; ... $result = "" . $result . "" if ($callback){ $result = $callback . '("' . $result . '")'; echo $result; } else { header("Content-Type: text/xml; charset=utf-8"); echo $result; }
Aplikacja udostępnia nagłówek odpowiedzi tylko przy braku wywołania zwrotnego. Nagłówek XML nie jest potrzebny, jeśli odpowiedź aplikacji służy do utworzenia wywołania funkcji z parametrem. Taką odpowiedź należy traktować jako zwykły tekst, a dane inne niż elementy XML są zbędne. Wysłanie nagłówka XML spowoduje wtedy, że aplikacja nie będzie działać. W odróżnieniu od danych JSON, które są już obiektem języka JavaScript, jeśli zostaną wczytane jako parametr, dokument XML trzeba zaimportować do aplikacji, aby można było uzyskać dostęp do niego za pomocą wbudowanego parsera XML przeglądarki: function printRecipe(recipeObj){ // Wczytywanie danych XML. // Kod dla przeglądarki Internet Explorer if (window.ActiveXObject) { var doc=new ActiveXObject("Microsoft.XMLDOM"); doc.async="false"; doc.loadXML(recipeObj); } // Kod dla Mozilli, Firefoksa, Opery itd. else { var parser=new DOMParser(); var doc=parser.parseFromString(recipeObj,"text/xml"); } xDoc = doc.documentElement; ...
Po wczytaniu za pomocą parsera DOM danych XML do dokumentu można manipulować nimi w taki sam sposób jak na listingach 2.8 i 2.13. Można użyć dowolnego formatu, jeśli dane w nim można albo zwrócić do obiektu XMLHttpRequest, albo przekazać jako argument funkcji w przypadku dynamicznych skryptów.
Bezpieczeństwo Podejrzewam, że wielu Czytelników jest przerażonych liczbą przedstawionych możliwości narażenia stron internetowych na najgorsze zagrożenia. 74
|
Rozdział 2. Elementy Ajaksa
W szczególności obejście zabezpieczających ograniczeń przeglądarki przez zastosowanie dynamicznych skryptów wywołujących zewnętrzne usługi, których nie można kontrolować, oznacza, że aplikacji grozi naruszenie bezpieczeństwa. Takie naruszenie zostało wykryte w 2006 roku w popularnej aplikacji Gmail firmy Google. Jedna z usług powiązanych z tą aplikacją zwracała listę kontaktów danego użytkownika. Ta funkcjonalność była udostępniana poprzez usługę sieciową w formacie JSON, a dopóki użytkownik był zalogowany, wywołanie tej usługi zwracało listę kontaktów tej osoby. Jednak wywołania tego typu można było zgłaszać z dowolnej lokalizacji, a usługa sieciowa nie sprawdzała, czy pochodzą z „bezpiecznej” domeny. Dlatego witryna internetowa mogła łatwo wywołać tę usługę JSON, a następnie przesłać listę kontaktów za pomocą wywołania Ajaksa do innej usługi, a nawet do żądania XHR na danej witrynie, co narażało osoby z listy kontaktów na spam. W przypadku poufnych danych tworzenie punktów końcowych w postaci usług JSON, a nawet XML, nie ma sensu, podobnie jak nie warto wywoływać usług z punktów końcowych znajdujących się na niezaufanych witrynach. Należy jednak pamiętać o zagadnieniach związanych z bezpieczeństwem, ponieważ usługi tego typu są przydatne przy stosowaniu widgetów. Jeśli zapoznasz się z ograniczeniami związanymi z bezpieczeństwem w Ajaksie, przedstawionymi na wiki Open Web Security Project (OWASP) na stronie www.owasp.org/index.php/ OWASP_AJAX_Security_Guidelines, możesz dojść do wniosku, że żadne zastosowanie Ajaksa nie jest bezpieczne, i jest to prawda — nie ma możliwości bezpiecznego używania Ajaksa. Są tylko zabezpieczenia, które można zastosować. Jak już wspomniałam, Douglas Crockford udostępnia na stronie http://www.json.org/js.html parser JSON, który można zastosować w aplikacji zamiast funkcji eval. Ten parser gwarantuje, że nieprzetwarzany obiekt nie zawiera ukrytych skryptów, które mogłyby wyrządzić szkody. W przypadku stosowania dynamicznych skryptów przetwarzanie danych XML przy użyciu parsera DOMParser jest bezpieczniejszym rozwiązaniem. Żadna z tych technik nie gwarantuje pełnego bezpieczeństwa, chyba że programista kontroluje punkt końcowy lub usługę sieciową albo ma duże zaufanie do używanej usługi zewnętrznej. Teraz, kiedy wiesz już, jak naruszyć bezpieczeństwo przeglądarki, pora zająć się dwoma następnymi kluczowymi zagadnieniami związanymi z tworzeniem aplikacji sieciowych: wydajnością i dostępnością. Rozdział 10. dotyczy skalowania i tworzenia witryn od podstaw, a także opisuje ogólnie problemy z bezpieczeństwem w Ajaksie.
Pierwszy rzut oka na wydajność Na kartach tej książki, szczególnie w dalszych rozdziałach, omawiane są pewne zagadnienia związane z wydajnością i usprawnienia dotyczące aplikacji ajaksowych. W tym miejscu chcę jednak po raz pierwszy poruszyć ten temat, omawiając ograniczenie do dwóch otwartych połączeń HTTP, które większość przeglądarek nakłada na strony pochodzące z określonej domeny. Kiedy użytkownik otwiera stronę internetową lub pobiera dane z internetu, w dowolnym momencie otwarte mogą być tylko dwa połączenia z jedną domeną. Oznacza to, że jeśli przeglądarka pobiera rysunki, pliki JavaScript i inne elementy zagnieżdżone na stronie, operacje Pierwszy rzut oka na wydajność
|
75
pobierania ich są umieszczane w kolejce na podstawie liczby połączeń. Ten problem staje się bardziej istotny, kiedy skrypt używa wywołań Ajaksa, które mogą zostać wstrzymane w oczekiwaniu na zwolnienie połączeń zajętych na czas przetwarzania innych wywołań lub w wyniku pobierania danych. Jeden ze sposobów na poprawę wydajności polega na umieszczeniu części mniej istotnych danych, na przykład rysunków, w innej domenie, co zwalnia połączenia z bieżącej domeny na potrzeby wywołań Ajaksa. Ta inna domena może znajdować się na tym samym serwerze, ponieważ ograniczenie związane z połączeniami wbudowane w przeglądarki opiera się na nazwach domen, a nie ich adresach IP. Można więc utworzyć poddomeny z nazwami CNAME, takie jak rysunki.nazwawitryny.pl czy js.nazwawitryny.pl, co umożliwia przeglądarce utworzenie więcej niż dwóch równoległych połączeń i zwiększa szybkość obsługi żądań strony oraz Ajaksa. Używanie kilku poddomen w celu zwiększenia liczby równoległych żądań HTTP może irytować użytkowników modemów telefonicznych, którzy korzystają z łączy o ograniczonej przepustowości. Jednak wraz z rosnącą powszechnością łączy szerokopasmowych staje się to mniej istotne. Trzeba wziąć pod uwagę możliwości klientów i na tej podstawie zaplanować rozwiązanie.
Następne usprawnienie związane z wydajnością polega na kompresji kodu JavaScript, dzięki czemu rozmiary plików są mniejsze. Jednak może to zaciemniać ten kod, przez co będzie on nieczytelny i trudny do powtórnego wykorzystania, jeśli dla programisty te zagadnienia mają znaczenie. Można łatwo znaleźć i wypróbować kilka narzędzi do kompresji kodu JavaScript, wpisując w wyszukiwarce wyrażenie „javascript compression”. Zamiast używać małych plików JavaScript, z których każdy trzeba wczytywać oddzielnie, można zastanowić się nad umieszczeniem w pojedynczym pliku kodu, który będzie najczęściej używany wspólnie. Jednak należy uwzględnić przy tym koszty pobierania kodu, który w danej aplikacji może być niepotrzebny. Lepiej jest używać mniejszych plików, niż pobierać zbędny kod.
Ostatnie słowo o asynchroniczności i synchroniczności Choć „A” w nazwie „Ajax” pochodzi od „asynchroniczności”, obiektów XMLHttpRequest nie trzeba używać asynchronicznie. Żądania synchroniczne to takie, w których przeglądarka jest zablokowana w czasie oczekiwania na odpowiedź. Tak działa poniższy kod: var state = document.forms[0].elements[0].value; var url = 'ajax.php?state=' + state; xmlhttp.open('GET', url, false); xmlhttp.send(null); alert(xmlhttp.getAllResponseHeaders());
W tym kodzie łańcuch znaków reprezentujący stan jest pobierany z pola formularza i służy do utworzenia żądania GET HTTP kierowanego do usługi sieciowej ajax.php. Trzeci parametr metody open to false, co oznacza, że żądanie będzie synchroniczne. To żądanie jest wysyłane bez parametrów (wartość null), a bezpośrednio po wywołaniu metody send kod otwiera okno komunikatu i wyświetla w nim nagłówki odpowiedzi. Jeśli usługa działa szybko, odpowiedź jest niemal natychmiastowa. Jednak jeśli usługa jest powolna, do czasu zakończenia obsługi żądania na stronie nie może zajść żadna inna opera-
76
|
Rozdział 2. Elementy Ajaksa
cja. Efekt jest nieco odmienny przy pierwszym otwarciu strony internetowej, kiedy to zwykle nie można podjąć żadnych działań do czasu zakończenia jej wczytywania. Większość aplikacji ajaksowych używa obiektów XMLHttpRequest asynchronicznie. Przesyłane jest wtedy żądanie asynchroniczne, a przeglądarka wciąż przetwarza kod po jego zgłoszeniu, zamiast czekać na odpowiedź serwera. Aby móc przetworzyć odpowiedź po jej otrzymaniu, należy przypisać odpowiednią funkcję do właściwości onreadystatechange obiektu XMLHttpRequest. Ta funkcja powinna sprawdzać stan gotowości i status żądania, a jeśli stan to 4, oznacza to, że żądanie zostało przetworzone. Asynchroniczna wersja wcześniejszego kodu wygląda tak: // Pobieranie przepisu za pomocą Ajaksa function getRecipe(evnt) { if (!xmlhttp) xmlhttp = aaGetXmlHttpRequest(); if (!xmlhttp) return; var drink = encodeURIComponent(document.getElementById('drink').value); var qry = "drink=" + drink; var url = 'recipe.php?' + qry; xmlhttp.open('GET', url, true); xmlhttp.onreadystatechange = printRecipe; xmlhttp.send(null); if (evnt && evnt.preventDefault) evnt.preventDefault(); return false; }
Zaletą żądań asynchronicznych jest to, że programy odczytujące tekst stron internetowych mogą kontynuować interakcję ze stroną i przeglądarką w czasie przetwarzania żądania. Nawet jeśli programista chce zapobiec podejmowaniu przez takie programy innych działań do czasu zakończenia obsługi żądania, warto, aby uruchamiał żądania asynchronicznie. Jeśli żądanie jest synchroniczne, a usługa zostaje opóźniona lub działa zbyt długo, strona wygląda na „zamrożoną”, co daje wrażenie, że aplikacja jest zepsuta. Aby lepiej zrozumieć, jakie wrażenia powoduje opóźnienie w obsłudze żądań asynchronicznych i synchronicznych, można dodać funkcję sleep do dowolnej wersji aplikacji PHP zwracającej przepisy, którą wywołują żądania Ajaksa (obiekt XMLHttpRequest): // Opóźnienie sleep(10); // Usuwanie odstępów z początku i końca nazwy szukanego drinka $search = trim($drink);
Obecne ustawienia powodują 10 sekund opóźnienia, jednak można ustawić je na dowolną wartość. Zalecam jednak wypróbowanie najpierw 10 sekund. Zaręczam, że efekt zostanie osiągnięty. Po otwarciu strony i wybraniu drinka można zauważyć, że strona zostaje „zamrożona”, a pole wyboru wciąż jest otwarte. Wszystkie operacje, włączając w to ponowne wyświetlenie strony, oczekują teraz na zakończenie obsługi żądania. Inaczej mówiąc, strona działa tak, jakby była „zepsuta”. Po przywróceniu asynchronicznego działania w funkcji odpowiedź nadejdzie z zauważalnym opóźnieniem, dlatego warto rozważyć dodanie pola komunikatu lub jakiegoś innego wskaźnika informującego, że aplikacja działa. W tym rozwiązaniu strona przynajmniej nie jest „zamrożona”, co jest najgorszym wrażeniem, jakie może sprawiać witryna.
Ostatnie słowo o asynchroniczności i synchroniczności
|
77
Niezależnie od zastosowanego podejścia, jeśli obsługa żądania do usługi trwa dłużej niż limit czasu (zależy to od ustawień — zwykle jest to ponad 30 sekund), wystąpi błąd. W różnych przykładach przedstawione są proste techniki dodawania do aplikacji wskaźnika „w toku…”.
78
|
Rozdział 2. Elementy Ajaksa
ROZDZIAŁ 3.
Narzędzia i pojęcia związane z Ajaksem
Od czasu powstania język JavaScript przeszedł metamorfozę. W nowszych wersjach języka JavaScript obok bardziej złożonych i bogatszych bibliotek dostępne są elementy, dzięki którym programistom może się wydawać, że używają zupełnie nowego języka — pełnego nieznanych i dziwnych operatorów oraz funkcji. Popularność Ajaksa spowodowała zainteresowanie bardziej formalnymi podejściami do programowania w języku JavaScript, co doprowadziło do powstania nowych pomysłów oraz powiązanej z nimi terminologii i składni. Pozytywny efekt tych zmian to bardziej stabilne i bogatsze aplikacje. Wady to konieczność nauki żargonu społeczności programistów stosujących Ajaksa, a także spędzenia znacznej ilości czasu na zapoznawaniu się z bibliotekami, zanim będzie można je dołączać do własnych aplikacji. Ten rozdział opisuje niektóre z popularnych bibliotek Ajaksa jako wprowadzenie do nich, a także w celu przedstawienia niektórych z często spotykanych zagadnień i pojęć dotyczących Ajaksa. Ponadto w rozdziale tym opisano standardowe pułapki związane z używaniem bibliotek, z których wiele jest zaprojektowanych bardziej w celu tworzenia nowych wersji aplikacji stacjonarnych, niż dodawania Ajaksa do istniejących witryn i aplikacji internetowych. Większość bibliotek Ajaksa udostępnia podstawową funkcjonalność, na przykład metody, które umożliwiają dostęp do dowolnego obiektu strony internetowej bez konieczności korzystania z metod modelu DOM. Większość bibliotek umożliwia także komunikowanie się z usługami sieciowymi. Największa różnica między różnymi rodzajami bibliotek polega na tym, że jedne zawierają funkcje do wykonywania konkretnych zadań, a inne stanowią platformy zapewniające infrastrukturę, której można użyć do tworzenia innych bibliotek. Biblioteki można tworzyć w warstwach, budując jedną na podstawie innej, lub łączyć ze sobą, co stawia przed programistą specyficzne wyzwania. Na przykład, jeśli dwie biblioteki mają odmienne funkcje do obsługi zdarzenia onload okna, trzeba je odpowiednio skonfigurować, bo w przeciwnym razie jedna biblioteka przesłoni drugą, a procedura inicjująca może nie zostać wykonana prawidłowo. Diagnozowanie takich konfliktów między bibliotekami może być niezwykle trudne, ponieważ jeśli programista nie zna kodu którejś z bibliotek, nie będzie wiedział, w jaki sposób przeszkadzają one sobie nawzajem. Społeczność programistów aplikacji działających po stronie serwera także ma wpływ na Ajaksa, albo projektując nowe technologie klienckie współdziałające z usługami, językiem lub infrastrukturą, albo dołączając istniejące technologie do używanego zestawu narzędzi. Wiele takich narzędzi jest opisanych w ostatnim rozdziale tej książki. Obejmuje to rzut oka na platformę Ruby on Rails (RoR), która jest ściśle zintegrowana z przodkiem wszystkich bibliotek ajaksowych — Prototype. 79
Prototype Prawdopodobnie najlepiej znaną z bibliotek Ajaksa i jedną z najczęściej używanych jest Prototype. Jest to dość mała i niezależna biblioteka. Można ją szybko wczytać i stanowi ona podstawę, na której zbudowane są inne biblioteki, włączając w to opisane dalej Rico i script.aculo.us. Prototype to mniej platforma, a bardziej korektor języków skryptowych, ponieważ udostępnia zestaw obiektów, które zmniejszają złożoność dostępu do elementów stron internetowych i ułatwiają używanie ich w różnych przeglądarkach. To narzędzie zostało utworzone i wciąż jest zarządzane przez Sama Stephensona, a najnowszą wersję (w czasie pisania tej książki była to Prototype 1.5) można pobrać ze strony http://prototypejs.org. W tej witrynie znajduje się także dokumentacja interfejsu API tej biblioteki oraz artykuły i samouczki dotyczące korzystania z niej. Dodawanie biblioteki Prototype do projektów jest proste. Można dołączyć ją do aplikacji, używając następującej składni:
Jedną z cech biblioteki Prototype jest obecność w niej kilku funkcji i operacji przypominających te z języka Ruby. Język ten jest łatwy w użyciu, ale nieco niezrozumiały, a zastosowanie opartych na nim funkcji biblioteki Prototype w kodzie JavaScript może prowadzić do pomyłek, szczególnie jeśli programista nie zna wyjątkowych komponentów biblioteki Prototype. Kluczowymi komponentami są funkcje znane jako dolar i dolar F. Funkcja dolar, $(), zwraca referencję do elementu, którego nazwa zostanie przekazana do tej funkcji: var elem = $('pewien_element');
Do identyfikacji elementów służy atrybut id:
...
Jest to odpowiednik użycia poniższego wywołania metody DOM: var elem = document.getElementById('pewien_element');
Oprócz zwracania elementów funkcja $() dołącza kilka właściwości specyficznych dla biblioteki Prototype. Funkcja $F() zwraca wartość pola formularza na podstawie jego identyfikatora: var value = $F('pole_formularza'); ...
Powyższy kod działa tak samo jak: var value = document.getElementById('pole_formularza').value;
Biblioteka Prototype nie zawiera nowej funkcjonalności, a raczej udostępnia potrzebne funkcje w wygodny sposób. Korzystanie z funkcji document.getElementById jest proste, ale jeszcze łatwiejsze jest wywoływanie $(), zwłaszcza biorąc pod uwagę dodatkową funkcjonalność. Następny kluczowy element biblioteki Prototype to obiekt Enumerable, który służy do obsługi iteracji w języku JavaScript. W tym języku funkcje do przechodzenia po kolekcjach danych,
80
|
Rozdział 3. Narzędzia i pojęcia związane z Ajaksem
takich jak tablice, zawsze były nieco prymitywne, przynajmniej w porównaniu z innymi językami. Jest to także obszar ciągłych zmian w nowych wersjach języka JavaScript. Na przykład w JavaScript 1.6 dodano funkcje forEach i filter, a w JavaScript 1.7 — możliwość iteracji. Jednak dopóki te zmiany nie będą powszechnie obsługiwane we wszystkich przeglądarkach, do emulowania większości tej funkcjonalności można używać obiektu Enumerable biblioteki Prototype. Prototype udostępnia także obiekty do zarządzania obiektami XMLHttpRequest. Używanie tych obiektów zarządzających nie jest skomplikowane, a pozwalają one uprościć kod odpowiedzialny za tworzenie żądań kierowanych do usług sieciowych. Podstawowy obiekt do obsługi Ajaksa nosi, całkiem słusznie, nazwę Ajax, jednak większość operacji dostępna jest poprzez obiekt Ajax.Request. Poniżej przedstawiony jest przykład zastosowania tego obiektu do utworzenia żądania: var myAjax = new Ajax.Request( url, { method: 'get', parameters: qry, onComplete: printRecipe });
Pierwszy parametr to adres URL, pod który kierowane jest żądanie do usługi sieciowej, a drugi przyjmuje obiekt zawierający nazwę metody żądania HTTP, parametry żądania oraz nazwę funkcji wywoływanej po zakończeniu przetwarzania żądania. Odpowiedź obiektu XMLHttpRequest jest opakowana przez bibliotekę Prototype — nie można przechwycić poszczególnych stanów żądania. W zamian programista powinien obsługiwać zdarzenia: onComplete, onLoading, onLoaded i onInteractive, jeśli chce przetworzyć dane po przejściu żądania w każdy kolejny stan. Jednak w przypadku większości aplikacji istotne będzie wyłącznie zdarzenie onComplete lub onSuccess, które zachodzą tylko po udanym przetworzeniu żądania. Biblioteka Prototype umożliwia także emulowanie metod DELETE i PUT HTTP przy użyciu alternatywnego podejścia opartego na metodzie POST opisanej w rozdziale 2. Ta biblioteka umożliwia również automatyczne przetwarzanie zwracanych danych. Jeśli dane mają format HTML, można użyć metody Ajax.Updater do przetworzenia odpowiedzi i wstawienia danych do odpowiedniego elementu. Jeśli dane są oznaczone jako JSON, Prototype potrafi przetworzyć je automatycznie i przekazać jako obiekt do wywoływanej zwrotnie funkcji. Ważnym warunkiem jest to, aby dane były „oznaczone jako JSON”, co wymaga użycia nagłówka X-JSON. Jest on obsługiwany przez platformę Ruby on Rails, która w praktyce jest środowiskiem serwerowym biblioteki Prototype. Niestety, użycie niestandardowego nagłówka może się zakończyć niepowodzeniem, zwłaszcza z powodu rozmiaru zwracanych danych. Zamiast używać niestandardowych nagłówków, w przykładach w tym rozdziale i całej książce dane JSON są zwracane w obiekcie odpowiedzi, a następnie albo używane bezpośrednio w funkcji eval, albo przekazywane do mechanizmu przetwarzającego dane JSON, który można pobrać z witryny json.org. Najlepszy sposób na podsumowanie analizy Ajaksa i obiektów wyliczeniowych, a także funkcji dolar, to przedstawienie przykładu. Listing 3.1 pokazuje, jak używać biblioteki Prototype w celu utworzenia usługi sieciowej za pomocą obiektu Ajax. Tu usługa sieciowa przypomina tę przedstawioną na listingu 2.9 w rozdziale 2., jednak zamiast przesyłać jeden drink na podstawie nazwy, zwraca wszystkie przepisy w formacie JSON.
Prototype
|
81
Listing 3.1. Aplikacja PHP zwracająca listę przepisów na drinki w formacie JSON
Kiedy usługa zwróci odpowiedź na żądanie, dane w formacie JSON zostaną przetworzone w funkcji eval, co spowoduje utworzenie obiektu języka JavaScript zawierającego grupę obiektów reprezentujących drinki. Każdy obiekt drink można zidentyfikować za pomocą nazwy drinka. Wszystkie te obiekty zawierają następny obiekt, makings, który obejmuje nazwę, składniki i instrukcje. W części klienckiej aplikacji ajaksowej, którą przedstawia listing 3.2, po wybraniu drinka z listy rozwijanej tworzony jest obiekt Ajax.Request biblioteki Prototype, który przyjmuje adres URL usługi sieciowej. Do żądania przekazywane są także inne informacje, włączając w to nazwę funkcji, którą należy wykonać po udanym przetworzeniu żądania. Jeśli potrzebne są parametry, należy je przypisać do właściwości parameters tego obiektu. Wywoływana zwrotnie funkcja używa tekstu odpowiedzi w funkcji eval, aby utworzyć obiekt recipeObj, zawierający przepisy na wszystkie drinki, a w celu przetworzenia wyników wywoływana jest funkcja printRecipe. W funkcji printRecipe używana jest funkcja $F() w celu pobrania nazwy drinka, a funkcja $() służy do pobrania elementu zawierającego wyświetlany przepis. 82
|
Rozdział 3. Narzędzia i pojęcia związane z Ajaksem
Dalszy kod to najciekawszy fragment aplikacji. Do pobrania drinka wybranego z listy służy metoda find obiektu Enumerable biblioteki Prototype, do której przekazywana jest funkcja anonimowa odpowiedzialna za porównywanie — w tym przypadku porównuje ona nazwy drinków. Jeśli funkcja dopasuje nazwy, zwróci konkretny obiekt drink zawierający wszystkie informacje o danym drinku. Obiekt drink zawiera właściwość makings, która obejmuje właściwości: title, ingredients i instruction drinka. Do właściwości title i instruction można uzyskać dostęp bezpośrednio, jednak w przypadku właściwości ingredients, która jest tablicą pojedynczych składników, trzeba użyć dwóch innych funkcji obiektu Enumerable: collect, a następnie join. Wszystkie dane są złączane w łańcuch znaków, który służy do zaktualizowania istniejącej zawartości obiektu recipe. Kod JavaScript omawianej aplikacji przedstawiony jest na listingu 3.2. Listing 3.2. Używanie obiektu Enumerable biblioteki Prototype do wyszukiwania danych w obiektach reprezentujących drinki var recipeObj; Event.observe(window,'load',function() { Event.observe('myform','submit',getRecipe); }); function getRecipe(evnt) { Event.stop(evnt); if (recipeObj) { printRecipe(); return; } var url = 'query1.php'; var myAjax = new Ajax.Request( url, { method:'get', onSuccess: function(transport){ eval("recipeObj = {" + transport.responseText + "}"); printRecipe(); }, onFailure: function(){ alert('Wystąpił błąd...') } }); } function printRecipe() { // Pobieranie nazwy drinka i obiektu na przepis var drinkName = $F('drink'); // Wyszukiwanie nazwy drinka var drinkObj = recipeObj.drinks.find(function(drink) { if (drink.drink == drinkName) { return drink; } }); if (!drinkObj) return; var recipe = document.createElement('div'); Element.extend(recipe); recipe.id = 'recipe';
" + drinkObj.makings.instruction; if ($('recipe')) $('recipe').remove('recipe'); var recipe = document.createElement('div'); Element.extend(recipe); recipe.id = 'recipe'; recipe.addClassName('recipe'); recipe = recipe.update(str); document.body.appendChild(recipe); }
W funkcji getRecipe, jeśli obiekt recipeObj został już utworzony, wywoływana jest funkcja printRecipe. W przeciwnym razie kod zgłasza żądanie ajaksowe i tworzy potrzebny obiekt w funkcji processRecipe. Zapobiega to wielokrotnym wywołaniom serwera internetowego. Jeśli przy pierwszym żądaniu nie wystąpi opóźnienie po stronie serwera, przepis zostanie wyświetlony niemal natychmiast po wybraniu nazwy drinka. Sama strona internetowa wygląda prawie identycznie jak ta przedstawiona na listingu 2.10 w rozdziale 2., jednak używa biblioteki Prototype. Kod JavaScript specyficzny dla aplikacji znajduje się w pliku getalldrinks.js. Używa go przykładowy plik ch03-02.xhtml. „Rubyfikacja języka JavaScript”, którą umożliwia biblioteka Prototype, nie musi być wystarczającym powodem jej używania, jednak obiekt Enumerable udostępnia ciekawą funkcjonalność. Jak działa ten obiekt? W końcu w kodzie nie powstaje żaden wyspecjalizowany obiekt. Z tego, co wiadomo, używane są proste obiekty języka JavaScript. Enumerable i wiele innych obiektów biblioteki Prototype działa poprzez właściwość prototype
języka JavaScript (stąd prawdopodobnie wzięła się nazwa omawianej biblioteki). Jeśli nie korzystałeś jeszcze z tej właściwości, w następnym punkcie znajdziesz przegląd jej podstawowych funkcji. W Ajaksie właściwość prototype jest stosunkowo często używana do rozszerzania obiektów — często, ale nie bez zastrzeżeń. Jeśli znasz już tę właściwość, możesz pominąć następny punkt.
Właściwość prototype języka JavaScript JavaScript nie udostępnia tradycyjnej struktury klas, w której można rozszerzać klasy, dziedzicząc po innych i dodając nową funkcjonalność. W zamian język udostępnia właściwość prototype do rozbudowy istniejących obiektów i ich egzemplarzy. prototype to kolekcja właściwości i metod zdefiniowanych w czasie wykonywania programu i dostępnych w każdym obiekcie niezależnie od tego, czy został on utworzony przed czy po wprowadzeniu zmian w tej kolekcji.
Jak działa prototype w języku JavaScript? Kiedy program uzyska dostęp do właściwości obiektu, silnik skryptowy przeglądarki najpierw sprawdza, czy dana właściwość jest dostępna wśród macierzystych właściwości, a następnie przeszukuje właściwości dostępne w prototype. Jeśli danej właściwości nie ma w prototype, sprawdzane są właściwości z poziomu obiektu.
84
|
Rozdział 3. Narzędzia i pojęcia związane z Ajaksem
W poniższym kodzie prototype służy do rozbudowy obiektu Number; w tym przypadku polega to na dodaniu nowej właściwości (percentage) oraz nowej metody (adjustValue): Number.prototype.percentage=0.15; // 15 procent Number.prototype.adjustValue = function() { return this * this.percentage; }
Używając nowych właściwości, można uzyskać dostęp do nich w taki sam sposób jak do właściwości macierzystych: var someValue = 3.0; alert(someValue.adjustValue());
W aplikacji komunikat wyświetli wartość obliczoną przez pomnożenie wyjściowej liczby 3,0 przez wartość odpowiadającą procentom — 0,15. Biblioteka Prototype korzysta z tej funkcji właściwości prototype do dodawania rozszerzeń do pewnych obiektów wbudowanych, takich jak: String, Array czy Function. Przykładem jest rozszerzona metoda stripTags, która usuwa z łańcuchów znaków wszystkie znaczniki: var str = "
To jest akapit
"; var newStr = str.stripTags(); // Wynik to "To jest akapit"
Właściwość prototype języka JavaScript daje wielkie możliwości i tworzy podstawę dużej części biblioteki Prototype, jednak korzystanie z niej jest ryzykowne.
Tablice asocjacyjne i zagrożenia związane z Prototype Wszystkie obiekty języka JavaScript można traktować jak tablice asocjacyjne (nazywane także skrótami, strukturami skrótów czy tablicami haszującymi). Oznacza to, że dostęp do właściwości obiektu można uzyskać za pomocą notacji z kropką: someObject.objProperty
Można także uzyskać dostęp do właściwości za pomocą notacji tablicowej, używając nazwy właściwości jako indeksu: someObject["objProperty"]
Odwzorowywanie kluczy i wartości w parach to nieodłączny element tablic asocjacyjnych. Ponieważ obiekty języka JavaScript można traktować jak takie tablice, można też użyć struktury for...in tego języka do przejścia po właściwościach danego obiektu: for (props in obj) {...}
Można też użyć dowolnego obiektu języka JavaScript do utworzenia nowej tablicy asocjacyjnej, włączając w to obiekty: Array, RegExp, Object, String i tak dalej. Poniższy kod używa obiektu Array, który wydaje się do tego najbardziej odpowiedni: var newArray = new Array(); newArray['1'] = 'jeden'; newArray['3'] = 'trzy';
Wydaje się to wystarczająco proste, jednak może spowodować zażarte dyskusje pomiędzy użytkownikami języka JavaScript, szczególnie tymi, którzy korzystają z biblioteki Prototype. Niektórzy z nich twierdzą, że w języku JavaScript nie ma tablic asocjacyjnych, a przedstawiona technika to sposób dodawania właściwości w czasie wykonywania programu. Niezależnie
Prototype
|
85
od semantyki te struktury działają tak jak tablice asocjacyjne (dostęp do elementów tablicy odbywa się za pomocą kluczy, a nie liczb), dlatego na potrzeby wszystkich zastosowań są to tablice asocjacyjne. Inni uważają, że tablice asocjacyjne w języku JavaScript są nieprawidłowe lub szkodliwe. Ich zdaniem stosowanie obiektu Array do tworzenia tablic asocjacyjnych jest nienaturalne, ponieważ jego tradycyjne metody, które zwykle zwracają informacje o tablicach (na przykład o ich długości), nie będą działać. Z tego powodu jedynie używanie obiektów Array na potrzeby tradycyjnych tablic liczbowych zapewnia oczekiwane działanie. Jest to szczególnie istotne w przypadku biblioteki Prototype, ponieważ rozbudowuje ona podstawowy obiekt Array, używając właściwości prototype. Jeśli programista utworzy tablicę asocjacyjną na podstawie obiektu Array, który sam został rozbudowany za pomocą biblioteki takiej jak Prototype, użycie pętli for...in może prowadzić do nieoczekiwanych wyników, a najbardziej prawdopodobnym skutkiem jest nieprawidłowe działanie aplikacji. Znawcy Ajaksa zalecają tworzyć tablice asocjacyjne za pomocą obiektu Object zamiast Array. Wersja 1.4 biblioteki Prototype rozbudowuje za pomocą właściwości prototype także obiekt Object języka JavaScript, ale z powodu powszechnego zapotrzebowania Stephenson zrezygnował z tego w wersji 1.5. Jednak biblioteka wciąż rozbudowuje obiekty String, Array i inne, o czym należy pamiętać, używając funkcjonalności tablic asocjacyjnych w celu uzyskania dostępu do właściwości obiektów w aplikacjach. Programista musi zwrócić szczególną uwagę na możliwość rozbudowy obiektów za pomocą właściwości prototype, jeśli używa więcej niż jednej biblioteki, ponieważ każda z nich może tworzyć własne rozszerzenia podstawowych obiektów języka JavaScript. Konflikty między przeglądarkami mogą powodować drobne problemy, które trudno jest zdiagnozować.
Biblioteki zewnętrzne — zagrożenia i korzyści Zastrzeżenia związane z biblioteką Prototype w rzeczywistości dotyczą wszystkich bibliotek zewnętrznych. Do najczęściej spotykanych wad należą: • duży rozmiar biblioteki; • powodowanie w aplikacji trudnych do zdiagnozowania problemów; • powodowanie ukrytych niestandardowych zmian, które mogą prowadzić do nieoczekiwa-
nego działania;
• konflikty z bibliotekami utworzonymi przez programistę, a także z innymi bibliotekami
zewnętrznymi. Można się przekonać, ile wywołań biblioteka dodaje do aplikacji, używając rozszerzenia Firebug do przejścia przez typowe żądanie Ajaksa. Kiedy użyłam tego podejścia w przypadku biblioteki Prototype, naliczyłam około 50 różnych instrukcji wykonanych oprócz wywołań standardowo używanych w żądaniach Ajaksa. Prototype to bardzo mała, zwięzła biblioteka. Jej rozmiar to poniżej 150 kilobajtów. Jednak inne biblioteki, na przykład Dojo (opisuje ją dalszy punkt — „Dojo”), są dużo większe i mogą spowalniać wczytywanie stron.
86
|
Rozdział 3. Narzędzia i pojęcia związane z Ajaksem
Dla mnie najbardziej frustrującym aspektem korzystania z bibliotek zewnętrznych jest sytuacja, w której jakaś operacja nie powiodła się, a dokładne określenie problemu jest bardzo trudne, nawet za pomocą krokowego wykonania kodu przy użyciu rozszerzenia Firebug. Należy jednak uwzględnić także korzyści stosowania danej biblioteki. Prototype udostępnia dużą liczbę operacji rozwiązujących brak zgodności między przeglądarkami, dzięki czemu nie trzeba zmagać się z wszystkimi specyficznymi podejściami i problemami, które odmienne działanie poszczególnych bibliotek dodaje do procesu projektowania stron. W szczególności bardzo wygodna jest obsługa różnic przy dostępie do stylów elementów stron, podobnie jak zarządzanie zdarzeniami i innymi rozszerzeniami modelu DOM. Podsumowując — nie zalecam używania Prototype ani innych bibliotek w przypadku małych aplikacji lub wyłącznie w celu obsługi żądań Ajaksa. Jednak przy tworzeniu bardzo rozbudowanych aplikacji w dużym stopniu opartych na Ajaksie, który używany jest w całej witrynie i musi działać stabilnie w przypadku wszystkich przeglądarek, zachęcam do korzystania z Prototype i prawdopodobnie jednej innej biblioteki, na przykład jQuery, Dojo czy MochiKit. W większości przykładów w tej książce kod JavaScript jest tworzony od podstaw, aby przedstawić pewną funkcjonalność, jednak w wielu punktach pokazane jest zastosowanie wybranych bibliotek lub znajduje się opis implementacji danej funkcji za pomocą jednej z dostępnych bibliotek. Pomoże Ci to poznać przynajmniej kilka popularnych i stabilnych bibliotek Ajaksa. Dowiesz się także, jak uzyskać tę samą funkcjonalność za pomocą standardowych funkcji języka JavaScript, dzięki czemu będziesz mógł samodzielnie określić, kiedy warto użyć danej biblioteki, a kiedy nie. Jeśli w tym rozdziale zbytnio krytykuję Prototype lub inną biblioteką, wynika to z tego, że chcę mieć pewność, iż rozumiesz potencjalne problemy związane ze stosowaniem bibliotek zewnętrznych. Mimo to uważam, że wszystkie biblioteki opisane w tym rozdziale to niezwykle przydatne narzędzia i jestem pełna uznania dla ich autorów nie tylko za udostępnienie ich, ale także za to, że zrobili to bezpłatnie.
script.aculo.us Biblioteka script.aculo.us jest oparta na Prototype; wzbogaca jej funkcjonalność o funkcje wyższego poziomu i udostępnia platformę, która umożliwia tworzenie bardziej zaawansowanych efektów Ajaksa. Jej nazwa to zmodyfikowana wersja nazwy witryny do zawierania kontaktów towarzyskich, del.icio.us, choć oba narzędzia nie mają ze sobą nic wspólnego. Tę bibliotekę można pobrać z witryny http://script.aculo.us. Przykłady przedstawione w tym rozdziale są oparte na wersji 1.7.0. Aby użyć biblioteki script.aculo.us, trzeba dołączyć pliki z kodem JavaScript zarówno biblioteki Prototype, jak i samej script.aculo.us. Nie trzeba jednak pobierać Prototype, ponieważ znajduje się ona w pakiecie z biblioteką script.aculo.us:
Biblioteka script.aculo.us zawiera różne efekty, z których każdy znajduje się w odrębnym pliku. Jeśli programista chce użyć tylko jednego efektu script.aculo.us, może określić to w ramach adresu URL pliku ze skryptami w atrybucie src elementu script:
script.aculo.us
|
87
Powyższy znacznik script wczytuje główną bibliotekę script.aculo.us, która jest przede wszystkim programem ładującym. Adres URL w tym znaczniku powoduje, że ten program wczytuje także plik effects.js. Program ładujący pobiera adres URL skryptu, przetwarza wczytywaną bibliotekę (lub biblioteki), a następnie wywołuje funkcję używającą instrukcji document.writeln w celu „dodania” potrzebnych bibliotek. Jeśli programista nie poda żadnej biblioteki, program ładujący script.aculo.us pobierze wszystkie biblioteki. Dodawanie kodu JavaScript za pomocą instrukcji document.write lub używanie modelu DOM do wstawiania nowych elementów script to znany wzorzec Ajaksa, JavaScript na żądanie, opisany przez Michaela Mahemoffa w książce Ajax Design Patterns1 (O’Reilly).
W czasie pisania tej książki można było dołączyć następujące biblioteki: • builder, • effects, • dragdrop, • controls, • slider.
Między tymi bibliotekami występują zależności, które szczegółowo opisuje dokumentacja script.aculo.us. Używanie instrukcji document.writeln i document.write do dodawania znaczników script w celu dołączenia bibliotek JavaScript to wygodny sposób na dynamiczne „ładowanie” bibliotek wtedy, kiedy są potrzebne. Niestety, to rozwiązanie nie działa, jeśli dokumenty są przesyłane jako XHTML, ponieważ dynamiczne dodawanie zbyt skomplikowanych znaczników do prawidłowych dokumentów XHTML jest niedozwolone. Biblioteka script.aculo.us nie działa w prawidłowo sformatowanych dokumentach XHTML.
Efekty script.aculo.us Po wczytaniu biblioteki script.aculo.us można używać zestawu ciekawych efektów: przeciągania, automatycznego uzupełniania, suwaków, kontrolek i sortowania list. Dostępnych jest także wiele efektów wizualnych, które w script.aculo.us nazywane są „efektami łączonymi”. Można ich używać poprzez obiekt o oczywistej nazwie — Effect. Tabela 3.1 zawiera listę efektów dostępnych poprzez ten obiekt. Listing 3.3 oparty jest na kodzie, który został przedstawiony na listingu 3.2. W tym przypadku użyto efektu Shake biblioteki script.aculo.us przy aktualizacji elementu recipe w celu podkreślenia, że jego zawartość zmieniła się. Odbywa się to za pomocą bardzo odpowiedniego, przynajmniej moim zdaniem, efektu. Ponieważ kod strony jest dość długi, na tym listingu przedstawiona jest jedynie zmodyfikowana funkcja. Należy jednak pamiętać, że stronę trzeba zwrócić jako dokument HTML, a nie XHTML, ponieważ najnowsza wersja biblioteki script.aculo.us nie współdziała z formatem XHTML. 1
Wydanie polskie: Ajax. Wzorce projektowe, Helion 2007 — przyp. tłum.
88
|
Rozdział 3. Narzędzia i pojęcia związane z Ajaksem
Tabela 3.1. Efekty wizualne biblioteki script.aculo.us Nazwa efektu
Działanie efektu
Effect.Appear
Wyświetla ukryty element.
Effect.Fade
Stopniowo ukrywa element, powodując zanikanie koloru.
Effect.BlindDown
Przewija element w dół, przenosząc go z góry na dół.
Effect.BlindUp
Przewija element w górę, przenosząc go z dołu do góry.
Effect.SwitchOff
Rozświetla rysunek przed ukryciem go.
Effect.SlideDown
Przenosi element z góry na dół. Zawartość elementu przesuwa się w dół wraz z kontenerem.
Effect.SlideUp
Przenosi element z dołu do góry. Zawartość elementu przesuwa się w górę wraz z kontenerem.
Effect.DropOut
Emuluje spadanie elementu przed ukryciem go.
Effect.Shake
Gwałtownie „wstrząsa” elementem.
Effect.Pulsate
Szybkie zanikanie i pojawianie się elementu, dające efekt pulsowania.
Effect.Squish
Ukrywanie elementu przez zmniejszanie elementu w górę i w lewo do czasu jego zniknięcia.
Effect.Fold
Przewijanie elementu w górę (BlindUp), a następnie poziomo w kierunku środka.
Effect.Grow
Powiększanie elementu, rozpoczynając od punktu.
Effect.Shrink
Zmniejszanie elementu w dół i prawo do czasu jego zniknięcia.
Effect.Highlight
Wyróżnia element, zwykle nadając mu na krótko żółty kolor. Wielu programistów stosujących Ajaksa nazywa ten efekt zanikaniem (ang. fade).
Listing 3.3. Testowanie biblioteki script.aculo.us function printRecipe() { // Pobieranie nazwy drinka i obiektu przeznaczonego na przepis var drinkName = $F('drink'); // Wyszukiwanie nazwy drinka var drinkObj = recipeObj.drinks.find(function(drink) { if (drink.drink == drinkName) { return drink; } }); if (!drinkObj) return; var str = "
" + drinkObj.makings.instruction; if ($('recipe')) $('recipe').remove('recipe'); var recipe = document.createElement('div'); Element.extend(recipe); recipe.id = 'recipe'; recipe.addClassName('recipe'); recipe = recipe.update(str); document.body.appendChild(recipe); new Effect.Shake(recipe); }
script.aculo.us
|
89
To rozwiązanie wczytuje wszystkie biblioteki script.aculo.us, co pozwala zobaczyć, jak duże opóźnienie biblioteka skryptów może spowodować przy wczytywaniu stron. Nawet w przypadku łączy szerokopasmowych jest to zauważalna różnica. W tym przykładzie blok recipe drży po wyświetlenie pobranego przepisu. Efekt nie zwiększa w znaczący sposób funkcjonalności aplikacji — jest to dodatek wizualny, podobnie jak wiele efektów używanych w Ajaksie. Jeśli dany efekt nie zmniejsza komfortu pracy użytkownika w wyniku dłuższego czasu wczytywania i podobnych zjawisk, może udostępniać ciekawe informacje zwrotne. W rozdziale 4., a następnie w rozdziale 6., pokazuję, jak użyć zanikania i podobnych efektów do udostępniania informacji zwrotnych użytkownikom strony. Takie techniki są szczególnie przydatne do wyróżniania udanych operacji aktualizacji i usunięcia danych za pomocą Ajaksa. Należy zwrócić uwagę na słowo new przed zastosowanym efektem biblioteki script. aculo.us. Tworzenie obiektu to część efektu, która jednak może nie być zupełnie oczywista i intuicyjna. Stosunkowo często programiści zapominają użyć słowa new.
Rico Rico to następna biblioteka Ajaksa zbudowana na podstawie Prototype. Rico, podobnie jak script.aculo.us, obejmuje zestaw efektów, włączając w to efekt zanikania, a także udostępnia funkcje upraszczające wywoływanie usług Ajaksa, podobnie jak wszystkie platformy do obsługi tej technologii. Rico można pobrać z witryny http://openrico.org. Rico ma także elegancki efekt zaokrąglania narożników, który można dodać do elementów strony, jednak zdaniem niektórych programistów związane z tym koszty są zbyt duże. Uważam, że zaokrąglone narożniki zaczną tracić popularność — przynajmniej dopóty, dopóki nie będą dostępne w większości przeglądarek poprzez style CSS. Rico udostępnia ciekawą kontrolkę LiveGrid, która łączy się ze źródłem danych za pomocą żądania Ajaksa i zwraca nowe dane wraz z przewijaniem tabeli z danymi przez użytkownika. Ten efekt jest nazywany stronicowaniem w Ajaksie lub przewijaniem na żywo.
Stronicowanie w Ajaksie Pojęcie „stronicowanie w Ajaksie” obejmuje różne techniki. Jeśli ten efekt zostanie zaimplementowany poprawnie, będzie doskonałym sposobem na pobieranie zewnętrznych danych bez wiedzy użytkownika. Google używa tego podejścia do pobierania pozornie nieskończonej mapy z usług sieciowych Google Maps. Jednak nieprawidłowe zastosowanie tego efektu może prowadzić do wielkich problemów. Ostatnio firma Yahoo! zmodyfikowała witrynę internetową Yahoo! TV, aby dołączyć kilka efektów Ajaksa, włączając w to stronicowanie. Wydajność i stopień „zablokowania” witryny, wynikające głównie ze stronicowania, spowodowały liczne protesty ze strony użytkowników. Stronicowanie wydłużyło łączny czas wczytywania danych, a przy wdrażaniu efektu nie zastosowano żadnej odmiany pamięci podręcznej, co mogłoby prowadzić do efektu „płynnego przewijania”.
90
|
Rozdział 3. Narzędzia i pojęcia związane z Ajaksem
Rico udostępnia opakowaną funkcjonalność poprzez kontrolkę LiveGrid, która obsługuje stronicowanie, współpracując z aktualną bazą danych w celu pobierania danych w razie potrzeby. Autorzy biblioteki Rico udostępniają na stronie http://openrico.org/rico/demos.page demonstrację wyszukiwania danych w Yahoo! za pomocą kontrolki LiveGrid.
Widget biblioteki Rico z prognozą pogody Rico, podobnie jak script.aculo.us, udostępnia programy demonstrujące jej funkcjonalność, jednak ma stosunkowo mało formalnej dokumentacji. Typowe podejście przy nauce korzystania z tych bibliotek polega na znalezieniu przykładu i analizie jego kodu źródłowego. Jednym z moich ulubionych przykładów jest widget Weather Watch (http://openrico.org/demos/ weather_demo), przedstawiony na rysunku 3.1.
Rysunek 3.1. Demonstracja widgeta Weather Watch biblioteki Rico to dobry przykład zastosowania ajaksowych kontrolek accordion
Ten program demonstracyjny używa trzech kontrolek typu accordion — zwijanych paneli — do ukrywania i wyświetlania różnych informacji o pogodzie. Każda z tych kontrolek używa efektu zaokrąglonych krawędzi biblioteki Rico, co nadaje rozwiązaniu ajaksowy wygląd. Accordion to w rzeczywistości połączenie efektów; działa albo wyświetlając element natychmiast i zwiększając jego rozmiar od zera do pełnej wysokości, albo zwiększając widoczny obszar za pomocą przycinania obszarów w CSS. Ponadto kontrolki te są wyświetlane w warstwach, dlatego jeśli widoczna jest większa część jednej z nich, pozostałe muszą zmienić pozycję, aby zrobić miejsce na element o nowej wielkości. W rozdziale 5. opisane jest tworzenie efektu accordion.
Rico
|
91
Aplikacja Rico Weather Watch wywołuje funkcję usługi sieciowej, która jest uruchamiana lokalnie względem biblioteki Rico, a następnie zgłasza wywołanie usługi sieciowej z witryny weather.com, aby pobrać informacje o pogodzie. Zwrócone dane są wyświetlane w różnych kategoriach. Żądania do usługi sieciowej z witryny weather.com nie można zgłosić bezpośrednio ze strony klienckiej z uwagi na strefę bezpieczeństwa i zabezpieczenia języka JavaScript.
Strefa bezpieczeństwa i zabezpieczenia języka JavaScript W rozdziale 2. opisałam, w jaki sposób model strefy bezpieczeństwa języka JavaScript ogranicza przeglądarki — mogą one używać jedynie zasobów znajdujących się w tej samej domenie, z której został pobrany sam skrypt. Inaczej mówiąc, wywołania Ajaksa można kierować jedynie do tej samej domeny serwera internetowego, która zwróciła wyjściową stronę. Dlatego jeśli dana strona internetowa pochodzi z domeny przykładowadomena.pl, żądanie usługi sieciowej także trzeba skierować do tej domeny. Opisałam także sposób obejścia tego ograniczenia poprzez zmianę ustawień zabezpieczeń, cyfrowe podpisanie skryptu lub użycie dynamicznie wczytywanych skryptów oraz funkcji wywoływanej zwrotnie; ten ostatni sposób jako jedyny działa we wszystkich przeglądarkach. Następnym rozwiązaniem, z którego korzysta Rico i widget Weather, nie łamie modelu zabezpieczeń języka JavaScript, ale umożliwia dostęp do usług sieciowych z różnych domen. Ta technika polega na zastosowaniu pośrednika. Pośrednik w Ajaksie to aplikacja działająca na serwerze internetowym (w domenie lokalnej), która wywołuje zdalne usługi na rzecz skryptu działającego po stronie klienta, a następnie przekazuje wyniki do klienta. Rozdział 9. przedstawia działanie pośrednika bardziej szczegółowo. Teraz wróćmy do bibliotek Ajaksa. Pośrednik ajaksowy to aplikacja sieciowa (działająca w domenie lokalnej), która udostępnia skrypt, wywołując zdalną usługę sieciową (ograniczenie związane z domenami nie obowiązuje w przypadku dostępu typu serwer-serwer). Następnie pośrednik przekazuje wyniki z powrotem do klienta. Dzięki aplikacji pośredniczącej kliencki program ajaksowy może wtedy zażądać usługi sieciowej z domeny lokalnej, zachowując dostęp do licznych usług sieciowych.
Dojo Dojo to projekt o otwartym dostępie do kodu źródłowego, zarządzany przez Dojo Foundation. W odróżnieniu od innych bibliotek, które są rozwijane przez pojedynczych programistów lub małe grupy, projekt Dojo aktywnie zachęca do dodawania elementów z różnych źródeł do udostępnianej biblioteki Ajaksa. Podstawowy zestaw Dojo Toolkit jest dość duży i udostępnia stosunkowo złożoną platformę z „wtyczkami”, co umożliwia tworzenie widgetów w stylu Dojo — zewnętrznych rozszerzeń, których stosowanie nie ma wpływu na kod. Ten zestaw można pobrać z witryny http://dojotoolkit.org. Dostępna wersja tej biblioteki udostępnia zarówno czytelny dla człowieka kod źródłowy, jak i kod źródłowy w języku JavaScript, z którego usunięto wszystkie zbędne odstępy (zmniejsza to rozmiar biblioteki i powoduje, że staje się mało czytelna).
92
|
Rozdział 3. Narzędzia i pojęcia związane z Ajaksem
Dostępnych jest kilka narzędzi i witryn obsługujących kompresowanie kodu języka JavaScript. Na początek można wpisać wyrażenie „JavaScript compression” w ulubionej wyszukiwarce.
Dojo ma rozbudowaną dokumentację dostępną pod adresem http://manual.dojotoolkit.org/index. html. Opis techniki tworzenia widgetów za pomocą Dojo wykracza poza zakres tej książki. Podobnie jak inne biblioteki Ajaksa Dojo początkowo miała skromną dokumentację. Biorąc pod uwagę złożoność tej biblioteki, powodowało to spore utrudnienia w jej używaniu. Na szczęście utworzono wiki (witrynę, której treść może modyfikować grupa osób) z dokumentacją poświęconą temu projektowi, choć podobnie jak w przypadku wszystkich witryn typu wiki niektóre zagadnienia są opisane pobieżnie. Innym miejscem zawierającym dokumentację jest strona http://dojotoolkit.org/docs. Dojo udostępnia własną funkcjonalność do obsługi różnych przeglądarek i języka JavaScript, a w odróżnieniu od Prototype obejmuje także platformę do tworzenia rozszerzeń i uruchamiania środowiska Dojo. Według dokumentacji dostępnej w witrynie Dojo biblioteka ta składa się z szeregu modułów. Poniżej przedstawione są wybrane z nich. dojo
Narzędzie do ładowania bibliotek Dojo, które wczytuje używane moduły.
dojo.lang
Narzędzia związane z językiem JavaScript.
dojo.string
Narzędzia do obsługi łańcuchów znaków w języku JavaScript.
dojo.dom
Procedury do manipulowania modelem DOM.
dojo.style
Procedury do manipulowania stylami CSS.
dojo.html
Specyfikacje HTML.
Dictionary
Obiekt reprezentujący słownik (tablicę asocjacyjną).
dojo.animation.Animation
Obsługa animacji.
dojo.crypto
Procedury szyfrujące.
Ta lista ciągnie się i ciągnie. Jak widać, Dojo nie jest małą biblioteką. Na szczęście nie trzeba wczytywać wszystkich elementów Dojo, aby uzyskać dostęp do podzbioru jej funkcji. Wypróbowałam Dojo na jednej z moich witryn, ponieważ chciałam przetestować jeden z efektów — menu typu fisheye. Użytkownicy systemu operacyjnego OS X komputerów Apple prawdopodobnie znają podobny efekt. Pojawia się on po umieszczeniu kursora myszy nad paskiem Dock — elementy są rozszerzane w sposób imitujący powiększenie widziane „rybim okiem”. Rysunek 3.2 przedstawia przykładową witrynę utworzoną za pomocą Dojo.
Dojo
|
93
Rysunek 3.2. Przykład menu typu fisheye, które rozszerza się po umieszczeniu nad nim kursora myszy
Dołączyłam moduł ładujący biblioteki Dojo, a także moduł fisheye, używając funkcji require biblioteki Dojo: ... dojo.require("dojo.widget.FisheyeList");
Skrypt ładujący dojo.js korzysta z innych modułów, których potrzebuje do działania, niezależnie od modułów używanych przez programistę. W tym skrypcie znajdują się także definicje funkcji dołączających biblioteki, choć ich analiza to dobra lekcja pisania nieczytelnego kodu. Dojo udostępnia funkcjonalność obejmującą wszystkie wyobrażalne zadania, a prawdopodobnie także te, o których programista nigdy nie pomyśli. Obejmuje to Ajaksa, szyfrowanie, rozbudowaną obsługę pamięci po stronie klienta, wykraczającą poza zastosowanie plików cookie, większość efektów wizualnych i tak dalej. Do obsługi wszystkich tych funkcji potrzebna jest skomplikowana infrastruktura, a kod źródłowy jest niezwykle trudny do odczytania i zrozumienia. Nawet uwzględniając możliwość minimalizacji ilości wczytywanego kodu, Dojo jest powolną biblioteką. Ma także pewne efekty uboczne, jeśli chodzi o współdziałanie z innymi bibliotekami. Jednak, w odróżnieniu od Prototype, efekty uboczne biblioteki Dojo wynikają raczej ze standardowego, niż niestandardowego działania języka JavaScript i są związane z metodami obsługi zdarzeń. Programiści z Dojo Foundation ciężko pracują nad optymalizacją biblioteki Dojo w celu zmniejszenia jej wielkości, a także „odchudzenia” jej podstawowych elementów. Może się okazać, że do czasu wydania tej książki Dojo stała się dużo bardziej atrakcyjna i udostępnia szybszy i bardziej zwięzły kod.
Łańcuchy funkcji obsługi zdarzeń Przed udostępnieniem przez W3C modelu DOM, poziom 2, zdarzenia były obsługiwane przez przypisanie metody ich obsługi do obiektu, na przykład w następujący sposób: window.onload=functionToRun;
94
|
Rozdział 3. Narzędzia i pojęcia związane z Ajaksem
Ta składnia wciąż jest obsługiwana, jednak przypisanie nowej funkcji powoduje zastąpienie funkcji przypisanych do procedury obsługi zdarzenia onload wcześniej, włączając w to funkcje przypisane przez Dojo. Oznacza to, że funkcje konfiguracyjne Dojo nie będą działać. Zamiast zastępować funkcje obsługi zdarzeń, w modelu DOM, poziom 2, można zastosować nowy sposób przypisywania takich funkcji, co prowadzi do powstawania ich łańcuchów. Poniższy kod przypisuje funkcję functionToRun do zdarzenia load: window.addEventListener("load",functionToRun,false);
Niestety, Microsoft nie obsługuje tej nowej techniki i udostępnia własny sposób dodawania procedur obsługi zdarzenia: window.attachEvent("onload",functionToRun);
Poniższy kod rozwiązuje problemy z różnicami pomiędzy przeglądarkami: if (window.addEventListener) { window.addEventListener("load",finish,false); } else if (window.attachEvent) { window.attachEvent("onload", finish); }
Zdarzenia i procedury ich obsługi są opisane bardziej szczegółowo w rozdziale 4., jednak powyższe rozważania pokazują, że korzystając z zewnętrznych bibliotek Ajaksa, trzeba zaplanować przypisywanie funkcji obsługi zdarzeń w przedstawiony sposób. W większości przypadków przykłady przedstawione w tej książce używają tej techniki lub pewnej jej odmiany.
Deklaratywny HTML Inny dawny problem z kodem HTML, który niestety ponownie ujawnił się przy nowszych zastosowaniach Ajaksa, to stosowanie niestandardowych lub przestarzałych atrybutów elementów. Jest wiele starszych atrybutów, które nie są już obsługiwane, na przykład align w przypadku tabel i rysunków HTML, bgcolor dla wszystkich elementów tabel, border dla rysunków, tabel i ciała dokumentu i tak dalej. Obecnie jednak w bibliotekach Ajaksa pojawia się całe mnóstwo niestandardowych atrybutów. Z czego to wynika? Autorzy bibliotek próbują tworzyć efekty, które nie wymagają korzystania z kodu JavaScript przez użytkowników. Przedstawię to na przykładzie. Wcześniej wspomniałam o efekcie fisheye biblioteki Dojo. Utworzenie takiego efektu nie jest proste. Nie tylko trzeba śledzić ruchy kursora myszy, ale także zmieniać rozmiar obiektów z uwzględnieniem innych oraz poruszeń myszy. Nie jest to efekt, którego tworzeniem byłabym zainteresowana. Na szczęście, dzięki Dojo, nie muszę. Aby zastosować funkcjonalność widgeta fisheye biblioteki Dojo, należy utworzyć grupę elementów div: element zewnętrzny, element sterujący oraz kilka elementów wewnętrznych, po jednym dla każdego elementu menu. Każdy element otrzymuje nazwę specyficznej klasy biblioteki Dojo, a także atrybuty elementu udostępniające informacje o nagłówku menu, plikach źródłowych z ikonami, minimalnym i maksymalnym rozmiarze rysunku i tak dalej. Listing 3.4 przedstawia elementy menu strony. Listing 3.4. Używanie widgeta fisheye biblioteki Dojo
To podejście, znane jako deklaratywny HTML, to ciekawe rozwiązanie. Polega na dodawaniu niezbędnych informacji jako atrybutów elementów, dzięki czemu program odczytujący stronę internetową nie musi przetwarzać kodu. Jednak stosowanie niestandardowych atrybutów ma dwie główne wady: taka strona nie przejdzie walidacji i nie będzie spełniać wymogów dostępności. Dojo, podobnie jak wiele innych nowych bibliotek Ajaksa, używa niestandardowych atrybutów dla standardowych obiektów HTML i XHTML, co powoduje, że strona nie przejdzie walidacji w trybie Strict HTML lub XHTML. Ponadto menu jest zarządzane za pomocą kodu JavaScript, dlatego użytkownicy, którzy wyłączą obsługę tego języka, nie będą mogli korzystać z menu. To podejście jest także zależne od myszy, przez co osoby używające samej klawiatury nie będą mogły skorzystać z takiego menu. W dalszej części książki opisuję, jak tworzyć menu, które zarówno przejdzie walidację, jak i będzie dostępne. Na razie skoncentrujmy się na walidacji. Zmiany potrzebne w kodzie są nieco skomplikowane, ale wykonalne. Trzeba usunąć atrybuty elementów ze znaczników i dodać je za pomocą modelu DOM.
Użycie kodu JavaScript do rozwiązania problemu niestandardowych atrybutów Na listingu 3.4 znajduje się sześć elementów menu. Aby zmodyfikować aplikację tak, żeby przeszła walidację, trzeba usunąć niestandardowe atrybuty kontrolnego elementu div oraz sześciu elementów div reprezentujących pozycje menu:
96
|
Rozdział 3. Narzędzia i pojęcia związane z Ajaksem
...
W kodzie JavaScript aplikacja używa obiektu document w celu uzyskania dostępu do każdego elementu, a następnie wywołuje metodę setAttribute, aby ustawić niestandardowe atrybuty. Poniższy kod ponownie ustawia atrybuty obiektu elementu sterującego: var cont = document.getElementById("controller"); cont.setAttribute("itemWidth","60"); cont.setAttribute("itemHeight","100"); cont.setAttribute("itemMaxWidth","200"); cont.setAttribute("itemMaxHeight","300"); cont.setAttribute("orientation","horizontal"); cont.setAttribute("effectUnits","2"); cont.setAttribute("itemPadding","10"); cont.setAttribute("attachEdge","top"); cont.setAttribute("itemPadding","bottom"); cont.setAttribute("enableCrappySvgSupport","false");
Ustawienia tych atrybutów są takie same jak wcześniej w znacznikach. Zmieniły się jedynie rozmiary rysunków, tak aby pasowały do obrazów używanych przeze mnie. W przypadku każdej opcji menu kod uzyskuje dostęp do elementu i dodaje atrybuty za pomocą metody setAttribute. Poniższy fragment ustawia atrybuty pierwszego elementu menu. Do modyfikacji wszystkich pozostałych pozycji służy prawie identyczny kod (zmieniają się jedynie rysunki i nagłówki): var menu1 = document.getElementById("menu1"); menu1.setAttribute("onClick","load_page('http://learningjavascript.info')"); menu1.setAttribute("iconsrc","/dotty/dotty.gif"); menu1.setAttribute("caption","Wprowadzenie do JavaScript");
Ponieważ biblioteka Dojo wymaga, aby elementy miały przypisane wartości atrybutów przed rozpoczęciem korzystania z jej funkcjonalności, a dane są przetwarzane bezpośrednio po wczytaniu strony, funkcję zawierającą kod ustawiający atrybuty trzeba wywołać bezpośrednio po utworzeniu elementów div na stronie. Jednym z rozwiązań jest umieszczenie bloku script z wywołaniem tej funkcji bezpośrednio po kodzie tworzącym ustawiane elementy div:
Warto zwrócić uwagę na sekcję CDATA wokół wywołania funkcji. Jest ona konieczna, aby strona przeszła walidację jako dokument XHTML. Po ustawieniu atrybutów przez programistę menu fisheye biblioteki Dojo będzie wyglądało przejrzyście bez konieczności stosowania atrybutów niestandardowych. Jednak wciąż brakuje jednego elementu — wymaganych atrybutów. W znacznikach nie ma atrybutu alt, który jest niezbędny, aby strona przeszła walidację. Inaczej niż w przypadku ustawiania atrybutów wyjściowych, atrybut alt trzeba dodać po wykonaniu operacji przez Dojo i utworzeniu menu. Dojo przechwytuje zdarzenie window.onload, Dojo
|
97
którego także trzeba użyć. Jednak trzeba to zrobić w odpowiedni sposób, aby nie „zepsuć” ani nie zastąpić funkcji obsługi tego zdarzenia używanej przez bibliotekę Dojo. W tym celu można użyć modelu DOM i dołączyć funkcję obsługi do zdarzenia onload, łącząc ją w łańcuch z funkcją obsługi z biblioteki Dojo. Służy do tego poniższy kod: function addWindowOnLoad(func) { // Sprawdzanie używanego modelu obiektowego if (window.addEventListener) { window.addEventListener("load",func,false); } else if (window.attachEvent) { window.attachEvent("onload", func); } } addWindowOnLoad(finish);
Metoda finish uzyskuje dostęp do każdego rysunku na stronie, sprawdza nazwę klasy, a jeśli odpowiada ona nazwie klasy z biblioteki Dojo, pobiera wartość atrybutu src rysunku i ustawia na jego podstawie odpowiedni atrybut alt: function finish() { for(var i = 0; i < document.images.length; i++) { var crntimg = document.images[i]; if (crntimg.className == "dojoHtmlFisheyeListItemImage") { switch(crntimg.src) { case "http://scriptteaser.com/dotty/dotty.gif" : crntimg.alt="Wprowadzenie do JavaScript"; break; case "http://scriptteaser.com/dotty/doomed.gif" : crntimg.alt="Trzy P oraz małe R"; break; case "http://scriptteaser.com/dotty/falling.gif" : crntimg.alt="Usługi sieciowe"; break; case "http://scriptteaser.com/dotty/impatience.gif" : crntimg.alt="Rozmaitości"; break; case "http://scriptteaser.com/dotty/mad.gif" : crntimg.alt="Szalona informatyczka na wolności"; break; case "http://scriptteaser.com/dotty/home.png" : crntimg.alt="Strona główna"; break; } } } }
Teraz strona przejdzie walidację jako dokument XHTML. Choć przedstawiona technika wymaga więcej kodu, jest lepsza niż uznanie formatu XHTML lub prawidłowych znaczników jako „nieprzydatnych” czy nieistotnych. Firmy, agencje rządowe i inne organizacje uznają obecnie, że strony internetowe muszą być zarówno prawidłowe, jak i muszą spełniać wymogi dostępności. Niektóre przeglądarki i inne narzędzia mogą w dziwny sposób interpretować nieprawidłowe, a przez to nieznane atrybuty. Ponieważ coraz więcej bibliotek Ajaksa używa niestandardowych atrybutów elementów do zarządzania efektami, warto pamiętać o tym podejściu, korzystając z takich rozwiązań.
98
|
Rozdział 3. Narzędzia i pojęcia związane z Ajaksem
Inne biblioteki Dostępnych jest coraz więcej bibliotek Ajaksa — od prostych do skomplikowanych. Pokrótce opiszę niektóre z nich, aby pokazać bogactwo przydatnych funkcji, z których można korzystać. Poręczna lista wielu bibliotek Ajaksa znajduje się na blogu eDevil — http://edevil. wordpress.com/2005/11/14/javascript-libraries-roundup.
jQuery Jedną z bibliotek, które szybko zyskują popularność, jest jQuery (http://jquery.com). Jest mała, szybka i zapewnia raczej działanie podstawowych metod do obsługi modelu DOM w różnych przeglądarkach, a nie efekty wyższego poziomu. Biblioteka Interface (http://interface.eyecon.ro) udostępnia efekty wyższego poziomu oparte na jQuery, podobnie jak biblioteka script.aculo.us zapewnia zaawansowane efekty na podstawie Prototype.
Jednak w odróżnieniu od innych bibliotek jQuery ma tylko jeden obiekt — jQuery. Niestety, jQuery używa sztuczki ze znakiem dolara, $(), aby korzystać z tego obiektu. Jeśli programista chce stosować jQuery wraz z innymi bibliotekami, zwłaszcza z Prototype, powinien wywołać funkcję jQuery.noConflict() na samym początku każdego bloku script, aby zapewnić normalne działanie innych bibliotek. Następnie należy używać obiektu jQuery za pomocą pełnej nazwy, aby uzyskać dostęp do całej funkcjonalności. jQuery udostępnia wysoki poziom obsługi łańcuchów, co umożliwia tworzenie łańcuchów wywołań funkcji jedno po drugim. Taka funkcjonalność wymaga, aby metody biblioteki zwracały referencje do obiektu this, którego może następnie użyć kolejna metoda w łańcuchu. Dzięki tym łańcuchom tworzenie efektów za pomocą jQuery jest bardzo proste. Poniższy kod sprawia, że po kliknięciu przycisku przez użytkownika aplikacja znajdzie czwarty akapit i przesunie go w dół: $("input.buttonBslidedown").click(function(){ $("div.contentToChange").find("p. fourthparagraph:hidden").slideDown("slow"); });
Dodawanie nowych elementów także jest niezwykle proste dzięki metodzie add, która przyjmuje jako dane wejściowe kod HTML. Znalezienie wszystkich elementów danej klasy także jest łatwe — służy do tego instrukcja children: $("div").children(".className");
Zainteresowanie jQuery rośnie tak szybko, że przed zakończeniem procesu redakcji niniejszej książki tę bibliotekę zaczęto wymieniać w jednym rzędzie z Prototype czy Dojo. Gorąco zachęcam do przetestowania tej biblioteki jako jednej z pierwszych przy rozpoczynaniu pracy z bibliotekami Ajaksa. Zalecam także zapoznanie się z bibliotekami base2 i base2.DOM autorstwa Deana Edwardsa (http://dean.edwards.name/weblog/ 2007/03/yet-another/). Te nowe, niezwykle zwięzłe biblioteki „standaryzacyjne” zostały udostępnione już po napisaniu wyjściowej wersji tej książki.
Inne biblioteki
|
99
Tej biblioteki można używać nie tylko do wyszukiwania na stronie elementów modelu DOM, ale także łańcuchów znaków.
MochiKit MochiKit to następna biblioteka Ajaksa typu „wszystko w jednym”, ma jednak niezwykle istotną cechę charakterystyczną: doskonałą dokumentację. Zarówno sama biblioteka, jak i dokumentacja znajdują się na stronie http://www.mochikit.com. MochiKit zawiera narzędzie do obsługi wyrażeń regularnych, które jest przydatne przy sprawdzaniu poprawności danych z formularzy. Nigdy nie byłam zbyt dobra w tworzeniu wyrażeń regularnych i chętnie korzystam z każdej dostępnej pomocy w tym zakresie. MochiKit ma także ciekawy zestaw ajaksowych tabel z danymi, które można synchronizować i sortować. Dostępny jest także oryginalny moduł do obsługi kolorów modułów. Jedną z najlepszych funkcji MochiKit jest wygodny panel logów, choć takie przeglądarki, jak Internet Explorer i Firefox, udostępniają logi, obsługę błędów i mechanizmy diagnostyczne, które działają niezależnie od zestawu narzędzi tej biblioteki.
Yahoo! UI Nie ma nic lepszego dla programistów niż współzawodnictwo o odbiorców i zainteresowanie twórców wyszukiwarek. Google udostępnia Google Web Toolkit (GWT) — oparte na języku Java środowisko do obsługi Ajaksa i strony serwera. Z kolei firma Yahoo!, nie chcąc pozostać w tyle, utworzyła Yahoo! UI (YUI). YUI różni się od GWT tym, że jest w większym stopniu biblioteką, niż platformą. Nie nakłada także ograniczeń co do języka programowania używanego po stronie serwera, podczas gdy GWT umożliwia stosowanie wyłącznie Javy. Ponieważ GWT to w rzeczywistości środowisko języka Java, które przy okazji pozwala tworzyć aplikacje ajaksowe, nie omawiam go w tej książce.
Biblioteka YUI jest używana we wszystkich aplikacjach sieciowych Yahoo! i jak można się domyślać, przechodzi wiele testów. Duży pobierany pakiet składa się z kilku folderów kodu JavaScript i HTML, a także rysunków. Ma szereg gotowych efektów wysokiego poziomu, takich jak: kalendarz, rejestrator (do zapisywania komunikatów), menu, suwak. Udostępnia także dużo podstawowych funkcji, na przykład animacje oraz efekt o nazwie easing — efekt odbicia w momencie zakończenia zmiany rozmiaru w poziomie lub przesuwania elementów, co dodaje „elegancji” danemu efektowi. W następnych rozdziałach znajduje się więcej informacji o YUI. Tymczasem możesz pobrać tę bibliotekę ze strony http://developers.yahoo.com/yui i samodzielnie wypróbować programy demonstracyjne. Warto zauważyć, że ten zestaw narzędzi ma także rozbudowaną dokumentację.
mooTools i moo.fx Biblioteki mooTools i moo.fx są mniejsze niż większość bibliotek Ajaksa. Są przeznaczone raczej do wzbogacania o nowe efekty, a nie są rozbudowaną, gotową infrastrukturą Ajaksa. Biblioteka mooTools składa się z kilku odrębnych plików (Moo.js, Function.js, Array.js, String.js 100
|
Rozdział 3. Narzędzia i pojęcia związane z Ajaksem
i tak dalej), z których każdy udostępnia własne rozszerzenia i klasy języka JavaScript. Efekty oparte na tych bibliotekach to tak zwane wtyczki, a choć są one zależne od różnych plików, wszystkie wymagają podstawowej klasy mooTools — Moo.js. Wszystkie klasy można pobrać z głównej witryny, http://mootools.net. Podobnie jak w przypadku wielu innych bibliotek Ajaksa, dokumentacja mooTools jest dość uboga i składa się bardziej z dokumentów dotyczących programu, niż samouczków i porad typu „jak to zrobić”. Na szczęście tę lukę wypełniły inne osoby i na stronie http://clientside.cnet. com/examples/mootools-primer można znaleźć dobry samouczek dotyczący mooTools. Biblioteka moo.fx to wtyczka dla mooTools, którą autorzy określają jako „superprostą, ultramałą, megadrobną bibliotekę efektów języka JavaScript”. Dwie odrębne implementacje interfejsu pozwalają korzystać z moo.fx wraz z mooTools lub Prototype. Ponieważ dostępne są dwie wersje, należy dobrze sprawdzić, czy zainstalowana jest odpowiednia biblioteka. Obie odmiany można pobrać ze strony http://moofx.mad4milk.net.
Sarissa Sarissa to działająca w różnych przeglądarkach biblioteka języka ECMAScript, która opakowuje wbudowane interfejsy API do obsługi danych XML, umożliwiając wczytywanie i serializację dokumentów. Ta biblioteka upraszcza także wywoływanie usług za pomocą Ajaksa, jednak jej wartość polega na serializacji danych XML oraz obsłudze języka XPath obok zarządzania transformacjami za pomocą XSLT. Nie jestem pewna, czy nie jest to żart, ale aplikacje ajaksowe używające bibliotek z API do obsługi danych XML (takich jak Sarissa) zaczyna się określać mianem aplikacji AJAX (duże litery), w odróżnieniu od aplikacji ajaksowych, które nie sięgają do „korzeni” tej technologii. Jak już wspomniałam w rozdziale 1., początkowo nazwa „Ajax” była akronimem, a technologia ta miała używać XML i XSLT do przetwarzania danych zwracanych przez usługi sieciowe. Jednak w dużej części projektów ajaksowych używany jest model DOM i, coraz częściej, format JSON, prawdopodobnie dlatego, że są one prostsze od XSLT, jeśli programista nie zna jeszcze tych technologii. Biblioteka Sarissa umożliwia uproszczenie stosowania XSLT (oraz przetwarzania z użyciem języków XPath i XML), powodując zwiększenie udziału AJAX-a w Ajaksie. Biblioteka Sarissa i jej dokumentacja są dostępne pod adresem http://dev.abiss.gr/sarissa.
WZ_jsGraphics i qForms WZ_jsGraphics to ciekawa i dopracowana biblioteka. Udostępnia funkcje umożliwiające rysowanie figur geometrycznych, takich jak linie, okręgi, kwadraty. Można ją pobrać ze strony http://www.walterzorn.com/jsgraphics/jsgraphics_e.htm. qForms to interfejs API do obsługi formularzy utworzony przez PengoWorks.com. Można go pobrać ze strony http://pengoworks.com/index.cfm?action=get:qforms. Jest to przydatna, wyspecjalizowana biblioteka, która koncentruje się na formularzach — prosta i przejrzysta.
Inne Dostępnych jest tyle bibliotek Ajaksa, że to wyliczanie można by ciągnąć przez wiele stron. Niektóre z tych bibliotek są opisane przy okazji przykładów w następnych rozdziałach, choć Inne biblioteki
|
101
większość operacji jest przedstawiona za pomocą niestandardowego kodu JavaScript, dzięki czemu można przyjrzeć się aplikacji bez analizy dużych ilości dodatkowych materiałów. Oczywiście, wyrażenie „nie kupuj kota w worku” dotyczy wszystkich bibliotek. Mimo elegancji Ajaksa należy pamiętać, że technologia ta daje duże możliwości, a jej nieprawidłowe zastosowanie może zatrzymać działanie witryny lub komputera użytkownika. Zdarzyło mi się kiedyś przypadkowo użyć wartości false zamiast true, co spowodowało całkowitą blokadę systemu wszystkich odwiedzających, którzy korzystali z przeglądarki Opera na komputerach Max OS X. Jednak zwykle najgorsze, co może się stać, to po prostu niefunkcjonowanie efektów. Może wtedy być konieczne wprowadzenie zmian w kodzie, aby biblioteki współdziałały z nim lub z sobą nawzajem. A teraz pora rozpocząć stosowanie Ajaksa.
102
|
Rozdział 3. Narzędzia i pojęcia związane z Ajaksem
ROZDZIAŁ 4.
Efekty interaktywne
Wszystkie efekty Ajaksa są interaktywne. Cały sens stosowania tej technologii polega na usprawnieniu reakcji strony. Jednak niektóre efekty są bardziej interaktywne od innych — szczególnie te polegające na udostępnianiu natychmiastowych informacji na podstawie pewnego zdarzenia. Osoby, które używały witryn Netflix lub Blockbuster Online, znają okna wyświetlające informacje o filmach, wyskakujące po umieszczeniu kursora myszy nad odnośnikiem. Jest to typowy interaktywny efekt Ajaksa, w którym aplikacja interpretuje zamiary użytkownika. W tym przypadku można dowiedzieć się czegoś więcej o filmie i na przykład dodać go do listy. Jeśli użytkownik dodaje komentarz w witrynie i może podejrzeć wyniki swej pracy w trakcie pisania lub przed ostatecznym przesłaniem danych, jest to następny efekt interaktywny. Umożliwia on sprawdzenie tekstu, poprawienie literówek lub znaków zapisanych w złej kolejności oraz zmianę składni. Taka funkcjonalność nie jest niezbędna do działania aplikacji. Jest ona raczej wynikiem domyślania się, jakie funkcje mogą przydać się użytkownikom. W tym przypadku jest to sprawdzanie tekstu. Aplikacje internetowe, które udostępniają informacje zwrotne po wykonaniu pewnych operacji przez użytkownika, na przykład wyróżniając dane na czerwono przy ich usuwaniu lub na żółto po aktualizacji, także zawierają efekty interaktywne. Żaden z nich nie jest niezbędny, ale upewniają użytkownika co do tego, że operacja została wykonana. Każdy z tych efektów stanowi sygnał, że aplikacja uwzględnia zamiary i działania użytkownika. Jest to odmienna sytuacja od kliknięcia przycisku i przesłania formularza lub przeciągnięcia produktu do koszyka zakupów, które są oczekiwane i kluczowe, a użytkownik zwróci na nie uwagę tylko wtedy, jeśli się nie powiodą. Efekty Ajaksa omówione w tym rozdziale to sposób informowania użytkownika przez aplikację sieciową: „słyszę cię; widzę, co robisz; chyba wiem, czego oczekujesz”. Żaden z tych efektów nie jest niezbędny, jednak w doskonałym zestawie narzędzi, jakim jest Ajax, mogą to być efekty najprostsze w implementacji i mające największy pozytywny wpływ na użytkowników korzystających z danych aplikacji. Te efekty są dość zróżnicowane: dymki z podpowiedziami, system pomocy JIT, zanikanie kolorów czy podgląd na żywo. Jednak wszystkie te elementy mają jedną cechę wspólną: stanowią reakcję na zdarzenia. Z powodu tej zależności od zdarzeń zacznę rozdział od przeglądu funkcji ich obsługi, a zwłaszcza obsługi zdarzeń w środowiskach Ajaksa, w których wiele bibliotek może współdziałać w jednej aplikacji.
103
Obsługa zdarzeń zgodna z Ajaksem Procedury obsługi zdarzeń były częścią języka JavaScript od jego powstania. Większość ma format onclick czy onload, czyli zaczyna się od słowa kluczowego on, po którym następuje nazwa zdarzenia. Zwykle funkcje obsługi zdarzeń są podawane w obiekcie, z którym są powiązane:
Użycie wewnątrzwierszowych funkcji obsługi zdarzeń to wciąż prawidłowe podejście do przechwytywania zdarzeń, jednak ma dwa ograniczenia: jedno związane z ich utrzymywaniem, drugie — z ich łączeniem.
Utrzymywanie funkcji obsługi zdarzeń Jeśli funkcje obsługi zdarzeń zostaną dodane bezpośrednio do elementów strony, a następnie niezbędne będzie wprowadzenie zmian w nazwie lub parametrach funkcji, programista będzie musiał znaleźć każde wystąpienie tekstu funkcji obsługi i zmodyfikować go. To podejście do dodawania funkcji obsługi zdarzeń jest niewydajne, a w przypadku współczesnych, bardziej złożonych i dynamicznie generowanych stron internetowych nie da się go stosować. Wyjątkiem jest tu treść generowana dynamicznie, jeśli kod tworzący zawartość strony znajduje się w jednym pliku. Choć wygenerowane dane mogą zajmować wiele stron, poprawki wystarczy wprowadzić w jednym miejscu.
Zamiast dodawać funkcje obsługi zdarzeń do obiektów, lepiej jest przypisać taką funkcję w bloku ze skryptami — albo w odrębnym pliku JavaScript, albo jako blok kodu w sekcji nagłówkowej dokumentu. Jest to tak zwana obsługa zdarzeń modelu DOM, poziom 0:
Także to rozwiązanie można stosować, jednak po przypisaniu funkcji do obsługi zdarzenia powstaje nowy problem. W powyższym przykładzie przypisanie funkcji somefunction do obsługi zdarzenia onclick obiektu document spowoduje zastąpienie funkcji przypisanych przed uruchomieniem tego skryptu. Jeśli programista dołącza zewnętrzne biblioteki Ajaksa lub łączy wiele takich bibliotek w jednej aplikacji, nie może założyć, że jest „właścicielem” danej obsługi zdarzenia. Jego kod musi współdziałać z kodem innych programistów, chyba że całe rozwiązanie składa się tylko i wyłącznie z kodu jego autora.
Łączenie funkcji obsługi zdarzeń Inne, zalecane podejście to obsługa zdarzeń modelu DOM, poziom 2. Ta technika polega na „łączeniu” funkcji obsługi zdarzeń tak, aby w momencie wystąpienia zdarzenia uruchamiane były wszystkie powiązane z nim funkcje. Przykład dodawania funkcji obsługi do zdarzenia click obiektu document wygląda tak: document.addEventListener("click",eventHandlerFunction,false);
104
|
Rozdział 4. Efekty interaktywne
W metodzie addEventListener pierwszy parametr to zdarzenie (click), a drugi to funkcja jego obsługi. Trzeci parametr jest opcjonalny i określa, czy przetwarzanie zdarzenia w zbiorze zagnieżdżonych obiektów ma się rozpocząć od zewnętrznego obiektu i przebiegać w kierunku wewnętrznego (true), czy od wewnętrznego do zewnętrznego (false). Pierwsze rozwiązanie to tak zwany etap przechwytywania, a drugie (domyślne) emuluje przetwarzanie ze starszego modelu DOM, poziom 0, obsługując zdarzenia na etapie ich generowania. Aby zademonstrować oba te podejścia, na listingu 4.1 przedstawiono stronę z dwoma elementami div, jeden wewnątrz drugiego. Uniwersalna funkcja obsługi zdarzeń, manageEvent, dołącza funkcje obsługi do obiektów, przyjmując jako parametry trzy elementy: obiekt docelowy, zdarzenie oraz funkcję obsługi zdarzenia. Jako pierwsze przechwytywane jest zdarzenie obiektu window w celu przypisania funkcji obsługi zdarzenia click do dwóch elementów div po ich wczytaniu. Funkcja obsługi zdarzenia load obiektu window przypisuje do obsługi zdarzenia click obu elementów div tę samą funkcję — clickHandler. Listing 4.1. Funkcja obsługi zdarzeń poprawnie współdziałająca z innymi Obsługa zdarzeń
Kliknięcie wewnętrznego elementu div na tej przykładowej stronie powoduje wyświetlenie ramki ostrzegawczej wyświetlającej słowo „inner”, po czym następuje wyświetlenie następnej takiej ramki, tym razem ze słowem „outer”. Po ustawieniu trzeciego parametru funkcji Obsługa zdarzeń zgodna z Ajaksem
|
105
manageEvent na true (włączenie trybu przechwytywania obsługi zdarzeń) kliknięcie elementów div spowoduje wyświetlenie najpierw ramki ostrzegawczej ze słowem „outer”, a dopie-
ro potem ze słowem „inner”. Przechwycenie zdarzenia w złym trybie może mieć bardzo negatywny wpływ na aplikację. Ponieważ większość funkcji obsługi zdarzeń jest zaprojektowana pod kątem trybu generowania, jeśli programista nie ma określonego powodu, aby stosować tryb przechwytywania, powinien zwykle ustawić trzeci parametr na false. Stosowanie metody addEventListener to doskonałe rozwiązanie, z jednym wyjątkiem: przeglądarki Internet Explorer (6.x i 7) nie obsługują jej. Te przeglądarki udostępniają inną funkcję — attachEvent: document.attachEvent("onclick",eventHandlerFunction);
Metody attachEvent i addEventListener różnią się w dwóch elementach. Po pierwsze, w metodzie attachEvent należy podać jako pierwszy parametr nazwę obsługi zdarzenia, a nie samego zdarzenia (onclick, a nie click). Po drugie, metoda attachEvent nie ma trzeciego parametru, ponieważ zawsze działa w trybie generowania. Aby zagwarantować, że obsługa zdarzeń będzie działać w Internet Explorer 6.x i 7, a także w innych przeglądarkach, trzeba użyć obu metod obsługi: modelu DOM W3C, poziom 2, oraz techniki Microsoftu. Oznacza to, że należy sprawdzić, czy określona właściwość jest dostępna, i użyć odpowiedniego kodu. Wersja funkcji manageEvent zmodyfikowana tak, aby działała w przeglądarce Internet Explorer oraz innych, wygląda tak: function manageEvent(eventObj, event, eventHandler) { if (eventObj.addEventListener) { eventObj.addEventListener(event, eventHandler, false); } else if (eventObj.attachEvent) { event = "on" + event; eventObj.attachEvent(event, eventHandler); } }
Funkcja sprawdza obiekt eventObj, aby określić, czy ma on właściwość addEventListener. Jeśli tak, funkcja używa go do dodania funkcji obsługi zdarzeń. W przeciwnym razie sprawdza metodę attachEvent, a jeśli ją znajdzie — używa jej. Jeśli używana przeglądarka nie obsługuje ani metody addEventListener, ani attachEvent, można zastosować starszy sposób obsługi zdarzeń z modelu DOM, poziom 0 — właściwość onclick. Obecnie nie jest to konieczne, ponieważ przeglądarki, które nie mają jednej z zaawansowanych technik obsługi zdarzeń, w mniejszym lub większym stopniu wyszły z użycia. Do kończenia odbioru zdarzeń służą uzupełniające metody — removeEventListener i detachEvent: function stopManagingEvent(eventObj,event,eventHandler) { if (eventObj.removeEventListener) { eventObj.removeEventListener(event,eventHandler,false); } else if (eventObj.detachEvent) { event = "on" + event; eventObj.detachEvent(event,eventHandler); } }
W sytuacji kiedy programista chce anulować zdarzenie, trzeba przerwać przekazywanie zdarzenia w przeglądarkach opartych na Mozilli i wstrzymać generowanie w przeglądarkach 106
|
Rozdział 4. Efekty interaktywne
Internet Explorer. Ponadto należy zatrzymać domyślną obsługę zdarzeń, do czego służy metoda preventDefault w przeglądarkach zgodnych z Mozillą i W3C oraz ustawienie wartości returnValue na false w przeglądarkach Internet Explorer. Poniższa metoda cancelEvent wykonuje te operacje: function cancelEvent(event) { if (event.preventDefault) { event.preventDefault(); event.stopPropagation(); } else { event.returnValue = false; event.cancelBubble = true; } }
Funkcje do zarządzania zdarzeniami są potrzebne tak często, że warto dodać je do używanych bibliotek. Ja dodałam je do biblioteki Ajaksa dołączonej do tej książki jako metody: aaManageEvent, aaStopEvent i aaCancelEvent, poprzedzając nazwy przedrostkiem „aa”, aby nie powodowały konfliktów z innymi używanymi bibliotekami. Jeśli programista używa w aplikacjach ajaksowych bibliotek zewnętrznych, w większości z nich znajdzie mechanizmy do obsługi zdarzeń. Następny punkt zawiera krótkie wprowadzenie do systemu obsługi zdarzeń jednej z takich bibliotek — Event System biblioteki Dojo.
System obsługi zdarzeń w Dojo i obiekty docelowe Większość bibliotek Ajaksa udostępnia mechanizm obsługi zdarzeń. Na przykład biblioteka Dojo posiada Dojo Event System, który znacznie upraszcza obsługę zdarzeń, a oprócz tego umożliwia zastosowanie niestandardowych sztuczek. W Dojo w celu zapewnienia funkcjonalności przedstawionej w poprzednim punkcie — czyli dołączenia odbiornika zdarzeń w celu przechwytywania zdarzenia load obiektu window i kliknięcia elementów div — wystarczy użyć metody connect obiektu dojo.event: dojo.event.connect(obj,"onclick",eventHandler);
Przypomina to funkcję przedstawioną na listingu 4.1, jednak możliwości systemu obsługi zdarzeń biblioteki Dojo znacznie wykraczają poza prostą funkcję manageEvent. Dojo między innymi zarządza różnicami pomiędzy przeglądarkami w zakresie przekazywania obiektu event do funkcji. Choć listing 4.1 tego nie demonstruje, zwykle jeśli programista chce uzyskać dostęp do obiektu event w funkcji obsługi zdarzenia w sposób, który będzie działał w różnych przeglądarkach, musi użyć w pierwszym wierszu tej funkcji kodu podobnego do poniższego: function eventHandler(evnt) { var event = evnt || window.event; ... }
Można też użyć następującego rozwiązania: function eventHandler(evnt) { var event = evnt ? evnt : window.evnt; ... }
Jeśli obiekt event przekazany do metody evnt istnieje, zostanie przypisany do zmiennej event. W przeciwnym razie można pobrać obiekt event z obiektu window. Jest to następna technika Obsługa zdarzeń zgodna z Ajaksem
|
107
zapewniająca zgodność z przeglądarkami Internet Explorer 6.x i 7, które nie przekazują automatycznie obiektu event do funkcji obsługi zdarzeń. W systemie obsługi zdarzeń Dojo to biblioteka obsługuje takie zadania, dlatego można założyć, że do funkcji obsługi zdarzeń zawsze zostanie przekazany parametr w postaci obiektu event. Listing 4.2 to zmodyfikowana wersja kodu listingu 4.1, używająca Dojo i obiektu event. Listing 4.2. Obsługa zdarzeń w bibliotece Dojo Obsługa zdarzeń
W tym przykładzie dostęp do identyfikatora obiektu odbywa się za pomocą obiektu event, a nie poprzez kontekst obiektu reprezentowany przez słowo kluczowe this. Zaletą tego podejścia jest to, że stosowanie obiektu event działa w różnych przeglądarkach. Jeśli programi108
|
Rozdział 4. Efekty interaktywne
sta użyje instrukcji this.id w przeglądarkach takich jak Internet Explorer 7, otrzyma wartość undefined, a nie identyfikator obiektu, którego dotyczyło zdarzenie. Jeśli program ma przestać oczekiwać na zdarzenie, należy użyć instrukcji dojo.event.disconnect z tymi samymi parametrami co w przypadku metody connect.
Oczywiście, dołączanie Dojo jedynie do prostej obsługi zdarzeń przypomina korzystanie z miecza do otwierania konserwy rybnej — jest to znaczna przesada. Jednak przedstawione przykłady demonstrują, że używając zewnętrznych bibliotek Ajaksa, warto sprawdzić, czy nie udostępniają obsługi zdarzeń, a jeśli tak, można użyć tych mechanizmów zamiast własnej biblioteki. Głównym powodem takiego postępowania jest zapewnienie maksymalnej zgodności w zakresie obsługi zdarzeń między biblioteką zewnętrzną a kodem aplikacji. Jest jeszcze jeden powód, dla którego wybrałam Dojo w celu przedstawienia obsługi zdarzeń za pomocą bibliotek. Dojo wykracza poza zarządzanie różnicami między przeglądarkami — dodatkowo umożliwia rozbudowę systemu obsługi zdarzeń tak, aby można było dołączyć funkcje obsługi zdarzeń invocation do samych funkcji. Funkcje obsługi zdarzeń invocation są wywoływane w momencie przetwarzania funkcji, z którymi są powiązane. Udostępnienie tego rozszerzenia wynika z tego, że Dojo, w odróżnieniu od wielu innych bibliotek Ajaksa, ma infrastrukturę opartą na wtyczkach, a wszystkie one współdziałają z tą biblioteką. Inne biblioteki stanowią platformy, na których można tworzyć aplikacje. Jeśli nowa wtyczka biblioteki Dojo zawiera funkcję, którą trzeba wywołać przy każdym uruchomieniu innej metody tej biblioteki, należy subskrybować tę metodę. Strona internetowa przedstawiona na listingu 4.3 składa się z tych samych dwóch elementów div co strona na listingach 4.1 i 4.2. Jednak na listingu 4.3 wystąpienie zdarzenia click w bloku
wewnętrznym powoduje zmianę jego koloru. Kiedy to się stanie, zmienić ma się także kolor bloku zewnętrznego. Kod aplikacji tworzy dwa nowe obiekty, change1 i change2, które modyfikują odpowiednio wewnętrzny i zewnętrzny blok. Aplikacja używa systemu obsługi zdarzeń biblioteki Dojo do zarejestrowania metody pierwszego obiektu, change1, jako „wydawcy zdarzenia”, przypisując do niego nazwę tematu — "/example". Następnie kod używa metody dojo.event.topic.subscribe, aby dodać subskrybenta nowego wydawcy, dzięki czemu po uruchomieniu pierwszej metody, change1, uruchomiona zostanie także druga — change2. Listing 4.3. Stosowanie techniki wydawca-subskrybent za pomocą Dojo Event System Obsługa zdarzeń
Po otwarciu przykładowej strony i kliknięciu wewnętrznego bloku jego kolor zmieni się z niebieskiego na czerwony, czego można oczekiwać z powodu działania funkcji obsługi zdarzeń przypisanej bezpośrednio do zdarzenia click bloku. Kolor bloku zewnętrznego także się zmienia — z żółtego na purpurowy — za sprawą techniki wydawca-subskrybent, dodanej za pomocą systemu obsługi zdarzeń biblioteki Dojo. Jest to prosty przykład zastosowania bardzo ciekawej funkcji, przeznaczonej do wzbogacania biblioteki Dojo o niestandardowe kontrolki. Jest to ciekawa infrastruktura, o której trzeba pamiętać, używając biblioteki Dojo. Ponadto należy pamiętać o niej przy tworzeniu własnych bibliotek, jeśli potrzebna jest tego typu architektura z obsługą wtyczek. Teraz wróćmy do interaktywnych efektów Ajaksa.
Informacje w trybie JIT Strony są interaktywne, jeśli reagują na działania użytkownika. Ta interakcja może polegać na kliknięciu przycisku lub odnośnika, przeciągnięciu elementu div czy wypełnieniu formularza. Zwykle kontekst strony udostępnia użytkownikom wystarczającą ilość informacji, aby potrafili określić, co należy zrobić, jednak nie zawsze tak jest. Na przykład łatwo zrozumieć 110
|
Rozdział 4. Efekty interaktywne
przeznaczenie pola formularza z etykietą „Nazwisko”, jednak inne etykiety, na przykład „Podaj powierzaną ilość”, mogą być mniej oczywiste. Wyświetlanie szczegółowych informacji na stronie może powodować bałagan, a zmuszanie użytkowników do otwierania następnej strony nie jest dobre. Otwieranie nowych okien może pomóc, jednak powoduje oddzielenie operacji od obiektu. W takich sytuacjach przydatny jest system pomocy JIT (ang. just-in-time, czyli „na czas”). Pomoc JIT zwykle obejmuje wyświetlanie pól z tekstem opisującym dany element lub zawierającym dodatkowe instrukcje. Takie pole można otworzyć przy aktywnym elemencie lub w standardowym miejscu strony, niezależnie od tego, który obiekt jest aktywny. Pomoc jest wyświetlana na podstawie działań użytkownika, takich jak umieszczenie kursora myszy nad polem formularza.
Pomoc w formularzach Etykiety elementów formularza mogą być oczywiste, na przykład „Imię” czy „Wiek”, jednak od czasu do czasu można natrafić na tekst podobny do „Post Slug” (z Wordpress — aplikacji do obsługi blogów), który powoduje, że użytkownicy drapią się po głowie i muszą zajrzeć do dokumentacji. Zamiast odsyłać użytkowników na odrębne strony, lepiej jest dodać informacje do samego formularza. Niestety, może brakować na nim miejsca. Strona do edycji wiadomości w aplikacji Wordpress to dobry przykład użycia zbyt wielu elementów i zbyt małej ilości miejsca. Najprostszy rodzaj pomocy w przypadku formularzy polega na przechwytywaniu zdarzeń zmiany aktywności każdego elementu i wyświetlaniu bloku z opisowym tekstem dotyczącym aktywnego obiektu. Zamiast zgadywać, przy którym elemencie użytkownik może wymagać pomocy, programista zakłada wtedy, że będzie ona potrzebna przy wszystkich obiektach, i udostępnia informacje dla każdego z nich. Te informacje można wstępnie wczytywać na stronę lub pobierać za pomocą wywołań Ajaksa kierowanych do usług sieciowych. Listing 4.4 przedstawia prostą aplikację z formularzem z pomocą JIT. Ponieważ aplikacja jest bardzo mała, skrypt, arkusz stylów i elementy strony internetowej znajdują się w jednym pliku. Kod strony dołącza funkcję obsługi zdarzenia onfocus do każdego elementu formularza, włączając w to przycisk do wysyłania go. Kiedy zajdzie zdarzenie focus dla danego elementu formularza, jego nazwa zostanie przesłana za pomocą obiektu XMLHttpRequest jako część zapytania do usługi sieciowej. Ta usługa zwraca definicję elementu, która jest następnie wyświetlana za pomocą właściwości innerHTML jako odrębny element strony — element div o identyfikatorze help. Listing 4.4. Formularz z systemem pomocy JIT opartym na zdarzeniu onfocus Pomoc dotycząca pól formularza
112
|
Rozdział 4. Efekty interaktywne
Jest to bardzo proste podejście do dynamicznego udostępniania pomocy. W przypadku tylko dwóch pól, z których żadne tak naprawdę nie wymaga dodatkowych wyjaśnień, zwykle nie warto stosować takiego rozwiązania, jednak uproszczenie przykładu pozwala jasno zaprezentować omawianą technikę. Komponent działający po stronie serwera tej aplikacji ajaksowej jest równie prosty: Podaj imię"; break; case "lastname" : $result = "
Jednak jeśli aplikacja obejmuje kilka formularzy rozproszonych na kilku stronach, udostępnienie tego samego systemu pomocy dla wszystkich stron umożliwia wprowadzanie zmian w informacjach w jednym miejscu, a nie w kilku różnych plikach. To podejście staje się wtedy dużo bardziej praktyczne. Ponadto jeśli aplikacja umieszcza szukane informacje w pamięci podręcznej przy ich pierwszym pobieraniu, zmniejsza się także liczba połączeń z serwerem: function printHelp() { if(xmlhttp.readyState == 4 && xmlhttp.status == 200) { document.getElementById('help').innerHTML=xmlhttp.responseText; helpObj[helpItem] = xmlhttp.responseText; } }
Po zastosowaniu pamięci podręcznej aplikacja będzie sprawdzać dostępność informacji o danym elemencie w tablicy asocjacyjnej znajdującej się w tej pamięci. Inne usprawnienie polega na zastosowaniu pomocy „na żądanie” zamiast wyświetlania jej po każdym aktywowaniu pola przez użytkownika. Jednym z rozwiązań jest tu przechwytywanie zdarzenia click etykiet zamiast zdarzenia focus pól i wyświetlanie pomocy po kliknięciu danej etykiety: function setUp() { var items = document.getElementsByTagName('label'); for (var i = 0; i < items.length; i++) { aaManageEvent(items[i],"click",showHelp); } }
Aby to rozwiązanie działało, w etykietach należy określić identyfikatory, a nie pola formularza:
Problem z używaniem etykiet polega na tym, że użytkownik zwykle nie wie, iż można je kliknąć. Jednym z rozwiązań tego problemu jest dodanie do etykiety efektu w momencie umieszczenia nad nią kursora myszy. Kursor powinien zmieniać się wtedy w znak zapytania. W tym celu należy dodać odpowiednie ustawienia do arkusza stylów CSS, zmieniając kursor myszy na wskaźnik systemu pomocy (zwykle jest to znak zapytania) po umieszczeniu wskaźnika nad etykietą: label { cursor: help; }
Oczywiście, użytkownik nadal musi umieścić kursor myszy nad etykietą, aby pojawił się odpowiedni wskaźnik. Ponadto trzeba korzystać z myszy, aby zauważyć, że najechanie na etykietę powoduje zmianę kursora, a obsługa skryptów musi być włączona. Bardziej dyskretne i intuicyjne rozwiązanie polega na otoczeniu etykiet odnośnikiem, ustawieniu atrybutu target na nazwę nowego okna i wyświetlaniu pomocy w wyniku zajścia zdarzenia mouseover, a nie click. Jeśli obsługa skryptów jest włączona, zdarzenie mouseover spowoduje wyświetlenie pomocy. Jeśli skrypty są wyłączone, kliknięcie odnośnika spowoduje otwarcie odrębnego okna z informacjami. Ta zmiana powoduje, że pomoc JIT będzie współdziałać z klawiaturą i będzie bardziej dostępna dla programów odczytujących tekst z ekranu. Rozwiązanie oparte na odnośniku powoduje dwa problemy. Jeden z nich polega na tym, że specyfikacja XHTML 1.0 Strict nie obejmuje atrybutu target. Drugi wiąże się z koniecznością informowania użytkowników używających programów odczytujących tekst z ekranu i innych narzędzi pomocniczych o tym, że kliknięcie odnośnika powoduje otwarcie nowego okna. Można uniknąć obu tych problemów, otwierając informacje systemu pomocy w obrębie tej samej strony, jednak nie jest to dobre rozwiązanie w przypadku wyświetlania pomocy na formularzach, zwłaszcza jeśli dany formularz jest już częściowo wypełniony. Popularne podejście do tego problemu polega na dołączeniu do odnośników atrybutu w postaci pary klucz-wartość rel="external", co prowadzi do otwierania takich odnośników w nowym oknie. Do tworzenia nowych okien w wyniku kliknięcia danego odnośnika służy tu kod JavaScript. Jednak jedną z głównych przyczyn wyświetlania pomocy w nowym oknie jest wyłączenie obsługi skryptów. Niektóre osoby i tak chcą kliknąć odnośnik, ponieważ używają programów odczytujących tekst ze strony, które nie mają dostępu do pomocy generowanej dynamicznie. Także w tym przypadku należy uważać, aby nie zastąpić częściowo wypełnionego formularza. W przypadku takich osób należy albo zrezygnować z walidacji strony jako dokumentu XHTML 1.0 Strict (kuszące rozwiązanie), albo utworzyć kod JavaScript, który przechwytuje operację kliknięcia i otwiera nowe okno. Nie mogę powiedzieć, które rozwiązanie uważam za najlepsze, ponieważ jedynie autor programu może określić, co jest ważne dla danej aplikacji i jej klientów. Na razie rozwinęłam plik addingajax.js, aby dodać procedurę przechwytującą odnośnik na podstawie atrybutu rel="external", co przynajmniej daje pewne możliwości. Listing 4.5 przedstawia nowe elementy tej biblioteki, włączając w to funkcję obsługi zdarzenia onload, która służy do przetwarzania odnośników.
114
|
Rozdział 4. Efekty interaktywne
Listing 4.5. Nowe elementy biblioteki, które obsługują zgodną z XHTML wersję atrybutu target elementów anchor function externalLinks() { var anchors = document.getElementsByTagName("a"); for (var i=0; i
Trzecia możliwość rozwiązania problemu ze specyfikacją XHTML Strict i atrybutem target polega na rozszerzeniu XHTML przez utworzenie niestandardowego dokumentu DTD. Rozważania na ten temat zawiera artykuł Extending XHTML: Target and Strict na blogu Wayne’a Burketta (http://dionidium.com/2004/05/xhtml-tests).
Choć statyczna zawartość strony przejdzie teraz walidację, to podejście powoduje dynamiczne dodawanie nieprawidłowych znaczników. Drugi sposób na napisanie tej funkcji, który całkowicie rozwiązuje problem walidacji, polega na zastosowaniu poniższego kodu w każdym znaczniku anchor: aaManageEvent(anchor,'click',function() { window.open(this.href); aaCancelEvent(event); });
Ten kod powoduje otwarcie nowego okna bez statycznego ani dynamicznego dodawania nieprawidłowych znaczników. To podejście wymaga dostępu do obiektu event w celu obsługi wywołania metody cancelEvent. Usprawniając aplikację, można dodać pewne ozdobniki do systemu pomocy, aby był bardziej elegancki. W tym przypadku ustawienia zmieniają kolor tła oraz dodają ramkę. Nowy arkusz stylu znajduje się w odrębnym pliku — jit.css: #help { background-color: #FFFF8F; border: 1px solid #82887e; left: 300px; padding: 10px; position: absolute; top: 20px; visibility: hidden; } form { margin: 50px; width: 500px; } input { margin: 10px; } label { cursor: help;
Informacje w trybie JIT
|
115
} a { text-decoration: none; }
Także kod JavaScript nowego rozwiązania znajduje się w odrębnym pliku — jit.js. Ten nowy skrypt, przedstawiony na listingu 4.6, dodaje obsługę pamięci podręcznej i zarządzanie zdarzeniami etykiet. Kod JavaScript wyświetlający pomoc jest rozbity na odrębne funkcje i wywoływany z poziomu funkcji przetwarzającej żądanie XMLHttpRequest, a także w funkcji showHelp — jeśli pomoc dotycząca danego elementu znajduje się w pamięci podręcznej. Listing 4.6. Pomoc JIT dla formularza, utworzona za pomocą Ajaksa var xmlhttp; var helpItem; var helpObj = new Object(); aaManageEvent(window,"load", function() { var items = document.getElementsByTagName('label'); for (var i = 0; i < items.length; i++) { aaManageEvent(items[i],"mouseover",showHelp); aaManageEvent(items[i],"mouseout",hideHelp); } }); // Pobieranie informacji z systemu pomocy function showHelp(evnt) { evnt = (evnt) ? evnt : window.event; // Tworzenie obiektu XMLHttpRequest, jeśli nie jest gotowy if (!xmlhttp) xmlhttp = aaGetXmlHttpRequest(); if (!xmlhttp) return; helpItem = (evnt.currentTarget) ? evnt.currentTarget.id : evnt.srcElement.id; var qry = "item=" + helpItem; // Jeśli element znajduje się w pamięci podręcznej, // należy wyświetlić go i zwrócić sterowanie if (helpObj[helpItem]) { printHelp(); return; } // Wywoływanie systemu pomocy var url = 'help.php?' + qry; xmlhttp.open('GET', url, true); xmlhttp.onreadystatechange = getHelp; xmlhttp.send(null); } // Ukrywa pole z pomocą function hideHelp() { document.getElementById('help').style.visibility="hidden"; } // Wyświetla pole z pomocą function getHelp() { if(xmlhttp.readyState == 4 && xmlhttp.status == 200) { helpObj[helpItem] = xmlhttp.responseText; printHelp(); } }
116
|
Rozdział 4. Efekty interaktywne
function printHelp() { var help = document.getElementById('help'); help.innerHTML = helpObj[helpItem]; help.style.visibility="visible"; }
Na stronie internetowej dołączane są pliki CSS i JavaScript, etykiety formularza są umieszczone wewnątrz odnośników hipertekstowych, a z adresem URL każdego z nich powiązany jest odpowiedni element systemu pomocy. Tę stronę przedstawia listing 4.7. Odnośniki mają atrybut rel="external", a także bezpośrednio określone nagłówki z jasnymi informacjami o tym, że kliknięcie odnośnika powoduje otwarcie drugiego okna. Listing 4.7. Strona z pomocą JIT — prawidłowa, dostępna i aktywna Pomoc dotycząca pól formularza
Rysunek 4.1 przedstawia tę stronę z wyświetloną pomocą dla jednego elementu. Jest to dużo kodu, jak na prosty efekt, jednak zastosowane techniki umożliwiają przekształcenie rozwiązania w bibliotekę funkcji języka JavaScript do udostępniania pomocy JIT nie tylko dla formularzy. Pomoc JIT to niemal odpowiednik innego efektu, znanego jako podpowiedzi. Wystarczy rozszerzyć funkcjonalność na inne obiekty i określić pozycję wyświetlanego tekstu tak, aby pojawiał się on w miejscu wystąpienia zdarzenia.
Informacje w trybie JIT
|
117
Rysunek 4.1. Formularz z pomocą JIT
Podpowiedzi Jak wspomniałam na początku tego rozdziału, użytkownicy witryny Netflix czy Blockbuster Online prawdopodobnie widzieli, jak po umieszczeniu kursora myszy nad odnośnikiem lub rysunkiem powiązanym z filmem pojawiają się informacje o nim, a także odnośnik umożliwiający dodanie danego filmu do listy. Jest to jedno z najlepszych zastosowań podpowiedzi, jakie widziałam. Bardziej szczegółowe informacje o danym obiekcie wyświetlane są szybko, bez zmuszania odwiedzających do otwarcia następnej strony. Jest to szczególnie przydatne, jeśli użytkownik witryny przegląda wiele elementów. Podpowiedzi można używać wszędzie: w sklepach internetowych, wyświetlając bardziej szczegółowe informacje o wierszach danych pobranych z bazy, do wyświetlania danych o aparacie użytym do wykonania zdjęcia i tak dalej — w każdej sytuacji, w której programista chce udostępniać informacje o obiekcie w kontekście otwartego okna. Jedyna zmiana, jaka jest potrzebna w celu przekształcenia aplikacji JIT z poprzedniego punktu na program z podpowiedziami, to udostępnienie tła w postaci dymku z tekstem i określenie pozycji podpowiedzi tak, aby była wyświetlana w pobliżu wystąpienia zdarzenia mouseover. Brzmi to prosto, jednak wymaga dodania jeszcze większej ilości kodu. Poniższy fragment strony internetowej jest bardzo podobny do kodu z listingu 4.7, jednak zawiera dwa nowe elementy strony: nagłówek h1 i niezależny odnośnik. Zostały one dodane w celu pokazania, że podpowiedzi mogą dotyczyć dowolnego obiektu na stronie, a nie wyłącznie elementów formularza:
Najbardziej istotna zmiana w nowej wersji aplikacji to kod JavaScript. W przypadku każdego elementu powiązanego z podpowiedzią przechwytywane jest zdarzenie mouseover w celu wyświetlenia wskazówki. Następnie trzeba przechwycić zdarzenie mouseout, aby ukryć podpowiedź. Po wystąpieniu zdarzenia mouseover kod sprawdza pozycję kursora myszy, aby element z pomocą umieścić w zbliżonym miejscu. Pozycja jest modyfikowana z uwzględnieniem rozmiaru dymka, aby umieścić jego ostrą część tak blisko kursora, jak to możliwe. Jedynym wyjątkiem jest pierwszy element od góry. Ponieważ znajduje się on blisko górnej krawędzi strony, trzeba dopasować pozycję podpowiedzi w pionie, aby jej górna część nie znalazła się ponad stroną. Listing 4.8 przedstawia nowy plik JavaScript. Listing 4.8. Wyświetlanie podpowiedzi dla formularza i innych elementów var var var var
xmlhttp; // Globalny obiekt XMLHttpRequest helpItem; // Bieżący element pomocy helpObj = new Object(); // Elementy pomocy z pamięci podręcznej posX; var posY;
// Przygotowanie zdarzeń związanych z podpowiedziami function addTooltip(ttObj) { aaManageEvent(ttObj,"mouseover",showHelp); aaManageEvent(ttObj,"mouseout",hideHelp); } // Dołączenie zdarzeń podpowiedzi do obiektów aaManageEvent(window,"load",function() { var items = document.getElementsByTagName('label'); for (var i = 0; i < items.length; i++) { addTooltip(items[i]); } addTooltip(document.getElementById('title')); addTooltip(document.getElementById('link')); }); // Pobieranie informacji z serwera function showHelp(evnt) {
Informacje w trybie JIT
|
119
evnt = (evnt) ? evnt : window.event; // Określanie pozycji posX = evnt.clientX; posY = evnt.clientY; // Tworzenie obiektu XMLHttpRequest, jeśli nie jest gotowy if (!xmlhttp) xmlhttp = aaGetXmlHttpRequest(); if (!xmlhttp) return; helpItem = (evnt.currentTarget) ? evnt.currentTarget.id : evnt.srcElement.id; var qry = "item=" + helpItem; // Jeśli element znajduje się w pamięci podręcznej, // należy wyświetlić go i zwrócić sterowanie if (helpObj[helpItem]) { printHelp(); return; } // Wywołanie systemu pomocy var url = 'help.php?' + qry; xmlhttp.open('GET', url, true); xmlhttp.onreadystatechange = getHelp; xmlhttp.send(null); } // Ukrycie dymku z informacjami function hideHelp() { // Można zasugerować zmianę nazwy klasy zamiast bezpośredniego manipulowania stylem document.getElementById('help').style.visibility="hidden"; } // Wyświetlanie pomocy function getHelp() { if(xmlhttp.readyState == 4 && xmlhttp.status == 200) { helpObj[helpItem] = xmlhttp.responseText; printHelp(); } } // Określenie pozycji podpowiedzi function printHelp() { var help = document.getElementById('help'); help.innerHTML = helpObj[helpItem]; y = posY - 130; if (y < 0) y = 10; help.style.top = y + "px"; help.style.left = (posX + 10) + "px"; help.style.visibility="visible"; }
Rysunek 4.2 przedstawia podpowiedzi w akcji. Także tym razem do utworzenia prostego efektu niezbędna jest duża ilość kodu, jednak można go łatwo zmodyfikować i wykorzystać w odrębnej bibliotece. Wystarczy umożliwić utworzenie podpowiedzi, przekazując lokalizację i treść, a obiekt tooltip wykona wszystkie potrzebne operacje. Można rozbudować funkcjonalność tak, aby wystarczało przekazanie samego elementu, a obiekt tooltip obsłuży zdarzenie, choć wtedy prawdopodobnie konieczne będzie także przekazanie treści, chyba że programista poda ją jako elementy strony i nie będzie pobierał jej z usługi sieciowej.
120
|
Rozdział 4. Efekty interaktywne
Rysunek 4.2. Podpowiedzi w akcji — udostępniają użytkownikom dodatkowe informacje Jeszcze prostsze podejście to użycie istniejącej biblioteki do wyświetlania podpowiedzi. Jedną z takich bibliotek jest Tooltip.js, oparta na Prototype i script.aculo.us. Najnowszą wersję tej biblioteki można pobrać ze strony http://tooltip.crtx.org.
W przypadku efektów takich jak te z witryn Netflix czy Blockbuster trzeba użyć rysunku o przezroczystym tle. Jedyna możliwość to przezroczysty plik GIF lub PNG, choć ten drugi format nie działa zbyt dobrze w przeglądarkach Internet Explorer (nawet w wersji 7, w której pojawiają się dziwne efekty związane z kolorami). Trzeba zastosować warstwy dla efektu, udostępniając rysunek dla nagłówka i stopki, a także odrębny rysunek dla ciała, który będzie można powielać w pionie, co umożliwi zmianę jego rozmiaru w zależności od długości treści. Następną ważną rzeczą, o której należy pamiętać, udostępniając omawiany mechanizm, jest to, że podpowiedzi nie działają przy wyłączonej obsłudze skryptów. Jednym ze sposobów na obejście tego problemu jest udostępnianie odnośników hipertekstowych wokół odpowiednich elementów, pozwalających otworzyć nową stronę, i stosowanie znaczników anchor dla poszczególnych informacji w systemie pomocy.
Podgląd na stronie Czasem najprostsza technologia może dać najlepsze efekty. Spośród wszystkich zmian, które wprowadziłam na moich witrynach, użytkownicy najbardziej polubili tę, która była jednocześnie najłatwiejsza w implementacji: podgląd na żywo. Podgląd na żywo odzwierciedla informacje podawane przez użytkownika w trakcie ich wpisywania. Pozwala użytkownikom zobaczyć, jak tekst będzie wyglądał w kontekście, w którym zostanie opublikowany, a nie w małych okienkach w niewielkich formularzach. Jest to możliwe jeszcze przed przesłaniem nowego lub zmodyfikowanego materiału. Stosunkowo łatwa jest także implementacja alternatywnego rozwiązania, działającego przy wyłączonej obsłudze
Podgląd na stronie
|
121
skryptów i zapewniającego zgodność z wymogami dostępności. Wystarczy udostępnić stronę z podglądem, którą użytkownik może sprawdzić przed ostatecznym przesłaniem danych. Można nawet użyć obu technik: podglądu na żywo i przycisku Podgląd. W jakich sytuacjach podgląd na żywo jest najbardziej użyteczny? Wszędzie tam, gdzie użytkownicy mogą zamieszczać komentarze. Obejmuje to wiadomości e-mail wysyłane do działu obsługi klienta, komentarze na blogach, recenzje produktów — we wszystkich przypadkach, kiedy użytkownik wpisuje więcej niż kilka słów. Podgląd na żywo składa się z wykrywania aktywności w polu formularza, przechwytywania wciśniętych klawiszy i wyświetlania ich w innym elemencie strony. Sposób wyświetlania zależy od tego, jak aktywny ma być podgląd. Można wyświetlać litery bezpośrednio po ich wpisaniu, co daje prawdziwe wrażenie działania „na żywo”. Można też udostępnić przycisk Podgląd, jednak zamiast otwierać nową stronę, można wyświetlać komentarz bezpośrednio na stronie wyjściowej. To podejście nie wymaga przechwytywania zdarzenia keypress, a dostęp do danych z pola z komentarzem jest potrzebny tylko po użyciu przycisku Podgląd. Obie wymienione techniki mają wady i zalety, które omawiam, analizując każde z tych podejść.
Podgląd na żywo Stosunkowo łatwo jest dodać podgląd na żywo, odzwierciedlający wpisywany tekst. Należy rozpocząć od przechwycenia zdarzenia keyup w elemencie textarea lub input formularza, a następnie pobrać cały tekst i wyświetlić go w odpowiednim elemencie div, używając właściwości innerHTML. W kodzie trzeba dostosować tekst, zastępując znak końca wiersza znacznikiem br. Dzięki temu tekst będzie w mniejszym lub większym stopniu zgodny z wpisanymi danymi. Listing 4.9 przedstawia kompletną aplikację używającą podglądu na żywo. Ponieważ ten przykład jest bardzo krótki, arkusz stylów i skrypty znajdują się bezpośrednio na stronie internetowej. Tekst podglądu nie wymaga modyfikacji innych niż przekształcenie znaku końca wiersza na znacznik br. Jeśli tekst podglądu wymaga innych zmian, na przykład usunięcia znaczników HTML, aby pokazać, jak komentarz będzie wyglądał po opublikowaniu, trzeba odpowiednio zmodyfikować skrypt. Należy jednak pamiętać, aby wyświetlany podgląd był prosty, ponieważ funkcja zapewniająca podgląd danych wejściowych na żywo jest wywoływana za każdym razem, kiedy w elemencie form zajdzie zdarzenie keyup. Listing 4.9. Prosty podgląd na żywo Podgląd na żywo
Informacje wpisane w polu textarea zostaną odzwierciedlone w elemencie preview. Formularz ma także przyciski Podgląd i Zapisz, które prowadzą do strony preview.php. Na tej stronie użytkownik może zapisać komentarz lub kliknąć przycisk Wstecz, aby powrócić do formularza i wprowadzić poprawki. Żadna z tych operacji nie wymaga skryptów. Z dalszej części rozdziału dowiesz się, jak przesłać formularz i wyświetlić nowy komentarz bez konieczności otwierania nowej strony. Podgląd na żywo wiąże się z jednym istotnym problemem. Co się stanie, jeśli strona zostanie zwrócona jako dokument o typie MIME XHTML? Implementacja właściwości innerHTML jest odmienna w różnych przeglądarkach, choć większość z nich obsługuje pewną wersję tej właściwości. Jednak wyświetlanie podglądu na żywo przez dodanie znacznika XHTML odbywa się zupełnie inaczej w różnych przeglądarkach. W przeglądarkach Firefox 2.x i nowszych niepełny znacznik spowoduje zgłoszenie dziesiątków błędów, zanim aplikacja zdąży go uzupełnić. Wszystkie te błędy informują o tym, że tekst nie jest prawidłowym kodem XML. Z kolei w Operze niepełne znaczniki są traktowane jako sekwencja sterująca do czasu zamknięcia znacznika, a następnie — jako kod XHTML.
Podgląd na stronie
|
123
Obejście tego ograniczenia polega na zastosowaniu sekwencji ucieczki dla symboli HTML: <, > i &. W tym celu należy zastąpić generowanie łańcucha modText następującym kodem: modText = commentText.replace(/&/g, '&').replace(/>/g, >'>').replace(/");
Powoduje to wyłączenie odnośników i innych elementów kodu HTML. Jeśli programista nie ma zaufania do źródła danych, może usunąć cały kod HTML.
Różnice w działaniu między różnymi przeglądarkami są wystarczająco istotne, aby stosować podgląd na żywo wyłącznie na stronach HTML, przynajmniej do czasu pojawienia się nowego standardu, HTML 5.0, który powinien jasno określać zarządzanie fragmentami dokumentów. Do tego czasu, jeśli programista chce zastosować podgląd na żywo dla stron w formacie XHTML, zalecam stosowanie „ajaksowej” wersji podglądu komentarzy. W internecie dostępnych jest wiele zastosowań podglądu na żywo, jednak po raz pierwszy zetknęłam się z tą techniką poprzez wtyczkę witryny Wordpress utworzoną przez Chrisa Davisa (http://www.chrisjdavis.org).
Podgląd za pomocą Ajaksa Ajaksowa wersja podglądu na żywo nie wyświetla tekstu bezpośrednio po jego wpisaniu. Pobiera cały tekst po kliknięciu przycisku Podgląd, ale zamiast wyświetlać go na odrębnej stronie z podglądem, używa do tego specjalnego obszaru. Zaletą tej metody jest to, że aplikacja w małym stopniu obciąża procesor, ponieważ nie musi przechwytywać wszystkich zdarzeń keyup. Jest także dużo bardziej zgodna z XHTML, ponieważ dane wejściowe można sformatować pod kątem XHTML przed ich wyświetleniem. Podgląd na żywo działa tak, jak wskazuje na to nazwa — na żywo. Nie można zastosować innego formatowania niż usunięcie znaczników. Strona demonstrująca podgląd za pomocą Ajaksa jest prawie taka sama jak strona z listingu 4.9. Wyjątkiem jest kilka elementów skryptu oraz dołączenie identyfikatora do przycisku Podgląd. Listing 4.10 przedstawia kod tej strony z wyróżnionymi modyfikacjami. Listing 4.10. Podgląd komentarzy za pomocą Ajaksa Podgląd za pomocą Ajaksa
Obecnie zdarzeniem, które powoduje wyświetlenie podglądu, jest kliknięcie przycisku Podgląd. Aby zapobiec domyślnemu działaniu tego przycisku (przesłaniu formularza), funkcja obsługi tego zdarzenia kończy je. W przypadku ajaksowego podglądu zagnieżdżenie elementów XHTML w komentarzu nie spowoduje błędu wynikającego z tego, że tekst jest niekompletny, jak dzieje się to w przeglądarkach Firefox 2.x (przynajmniej w czasie pisania tej książki). Jednak użycie „nieprawidłowego” kodu XHTML w przeglądarce, która sprawdza zawartość za pomocą właściwości innerHTML, spowoduje błędy języka JavaScript w czasie przypisywania tekstu do elementu innerHTML. Zgłoszenia błędów nie są złe, jednak błędy JavaScript są niekorzystne.
Podgląd na stronie
|
125
Lepsze rozwiązanie polega na umieszczeniu kodu używającego właściwości innerHTML w bloku try...catch, aby udostępniać komunikaty o błędach przeznaczone dla użytkowników, a nie dla programistów: modText = commentText.split(/\n/).joint(" "); var previewElem = document.getElementById("preview"); try { previewElem.innerHTML = modText; } catch(err) { previewElem.innerHTML = "
Wystąpił błąd. Sprawdź, czy w komentarzu nie występują błędy (X)HTML.
"; }
Opera po prostu poprawi (lub przyjmie) nieprawidłowy znacznik, podobnie jak Safari, Internet Explorer czy OmniWeb, jednak Firefox i WebKit uruchomią obsługę błędów. Niestety, nie ma łatwego sposobu na przechwycenie tego błędu. Wymaga to próby utworzenia dokumentu XML i sprawdzenia, co się stanie. Jest to dość ekstremalne rozwiązanie, a jego koszty przekraczają użyteczność omawianego efektu. Lepsza technika polega na unikaniu wszelkich błędów przez usunięcie znaczników i udostępnienie przycisków, co umożliwia formatowanie odnośników hipertekstowych i podobnych elementów. Także w tym przypadku, jeśli programista nie chce ryzykować używania XHTML w komentarzach, może zastosować sekwencje ucieczki w kodzie HTML lub całkowicie usunąć znaczniki.
Następny ciekawy efekt, którego można użyć do obsługi komentarzy i podobnych elementów, zwłaszcza jeśli wszystkie one znajdują się na jednej stronie, to zastosowanie Ajaksa do aktualizacji magazynu danych, a następnie „odświeżenia” listy bez konieczności odświeżania strony. Po dodaniu efektu zanikania kolorów można uzyskać nieco elegancji bez konieczności dodawania dużej ilości kodu.
Zanikanie kolorów w wyniku sukcesu lub niepowodzenia Jednym z popularnych efektów Ajaksa jest zmiana lub zanikanie kolorów w celu zasygnalizowania udanej (lub nie) aktualizacji. Zwykle efekt ten jest powiązany z aktualizacją danych, ale można go użyć do obsługi dowolnej aktywności, jeśli program ma sygnalizować użytkownikom wydarzenia, na które należy zwrócić uwagę. Zanikanie polega na zmianie koloru tła elementu lub grupy obiektów od ciemniejszego do jaśniejszego i zwykle z powrotem. Efekt może obejmować różne odcienie jednego koloru; może to być nagłe wyświetlenie czerwonego tła w celu zasygnalizowania usunięcia danych lub zanikającego żółtego jako wyróżnienia informacji. Można także użyć różnych kolorów, na przykład przejścia od niebieskiego do żółtego w celu poinformowania o udanym zakończeniu zadania. Niezależnie od dokładnego działania efektu zanikanie zawsze wymaga zegarów do obsługi niezbędnej animacji. Przed zapoznaniem się z kodem potrzebnym do zaimplementowania efektu zanikania warto przypomnieć sobie działanie zegarów i animacji.
126
|
Rozdział 4. Efekty interaktywne
Jeśli uważasz, że wystarczająco dobrze rozumiesz działanie zegarów i animacji, możesz przejść do następnego punktu.
Zegary i animacje JavaScript udostępnia kilka sposobów kontrolowania animacji. Jednym z nich jest metoda setTimeout. Można ją wywołać raz, a następnie trzeba ją wyzerować, jeśli zegar ma obsługiwać kilka zdarzeń. Inna technika to metoda setInterval, która jest uruchamiana w określonych odstępach czasu do momentu jej zatrzymania. Jednak w przypadku metody setTimeout można użyć różnych parametrów funkcji zegara przy każdej iteracji i dlatego jest ona najbardziej popularnym narzędziem do tworzenia takich animacji, jak zanikanie kolorów. Metoda setTimeout przyjmuje dwa parametry: funkcję lub wyrażenie jako pierwszy i liczbę milisekund między kolejnymi wywołaniami funkcji lub wyrażenia jako drugi. Ten drugi parametr jest stosunkowo prosty, jednak pierwszy przeszedł znaczącą ewolucję w różnych wersjach języka JavaScript — od prostych zastosowań w pierwszych aplikacjach DHTML po bardziej skomplikowane w przypadku użycia w Ajaksie. Listing 4.11 przedstawia sposób używania metody setTimeout w starszych wersjach języka JavaScript. Funkcja zegara rozpoczyna odliczanie od 10 i kończy je, kiedy licznik dojdzie do zera. W każdej iteracji funkcja pobiera element item i używa jego właściwości innerHTML w celu odtworzenia danego fragmentu strony. W wywołaniu metody setTimeout funkcja zegara i wartość licznika są podawane jako pierwszy parametr, a czas do następnego wykonania — jako drugi. Proste. Listing 4.11. Nieskomplikowana funkcja zegara Starsze zegary
10
To podejście jest proste, jednak używanie metod obiektu zamiast odrębnych funkcji powoduje, że zegar nie może przekazać wraz z metodą kontekstu obiektu jako parametru metody setTimeout. Wraz z rosnącą popularnością Ajaksa, a zwłaszcza z powodu rozwoju bibliotek takich jak Prototype, działanie funkcji setTimeout znacznie się zmieniło. Te zmiany są na tyle duże, że trudno jest dokładnie zrozumieć, jakie operacje zachodzą w kodzie. Następny punkt przedstawia zastosowanie metody setTimeout w bibliotece Prototype, a także pokazuje, jak użyć tej funkcji niezależnie od biblioteki. Pozwala to zademonstrować wpływ Ajaksa na zegary i funkcje obsługi zdarzenia timer.
Zegary ajaksowe Biblioteka Prototype zawiera metodę bind, która jest dołączona do obiektu Function poprzez właściwość prototype języka JavaScript. Krótkie przypomnienie: właściwość prototype umożliwia dołączanie nowych metod lub właściwości do podstawowej wersji obiektu w taki sposób, aby wszystkie egzemplarze tego obiektu „dziedziczyły” to rozszerzenie. Metoda bind biblioteki Prototype zwraca funkcję, która wywołuje metodę apply obiektu Function, przekazując do niej łańcuch argumentów funkcji zewnętrznej. Oryginalny kod metody bind wygląda tak: Function.prototype.bind = function() { var __method = this, args = $A(arguments), object = args.shift(); return function() { return __method.apply(object, args.concat($A(arguments))); } }
Metoda apply języka JavaScript pozwala użyć metody jednego obiektu w kontekście metody innego obiektu. Przyjmuje kontekst obiektu zewnętrznego reprezentowany jako obiekt (pierwszy parametr na liście argumentów) i przekazuje go jako pierwszy parametr. Drugi parametr to lista argumentów otrzymana za pomocą metody $A biblioteki Prototype, która zwraca tablicę obiektów, które można iterować. Ta tablica jest niezbędna przy modyfikowaniu parametrów obiektów wbudowanych, a Prototype wykonuje takie zmiany na przykład w przypadku obiektów Function czy Array. Jak metoda bind używa metody setTimeout? Zapisuje stan obiektu po każdym wywołaniu setTimeout, włączając w to wartości właściwości obiektu. Ponieważ ten stan jest zapisany, programiści używający Ajaksa nie muszą przejmować się przekazywaniem parametrów funkcji wraz z zegarem lub za pomocą zmiennej globalnej.
128
|
Rozdział 4. Efekty interaktywne
Ten mechanizm będzie potrzebny w aplikacjach przedstawionych w dalszej części książki, dlatego warto przekształcić go w funkcję i dodać do biblioteki addingajax.js. To podejście jest inne niż to zastosowane w bibliotece Prototype, ponieważ w książce potrzebne są inne zastosowania, jednak mechanizm jest podobny: funkcja wiąże kontekst obiektu z metodą wywoływaną jako funkcja obsługi zdarzeń: function aaBindEventListener(obj, method) { return function(event) { method.call(obj, event || window.event)}; }
Listing 4.12 to zmodyfikowana wersja kodu z listingu 4.11, wykorzystująca obiekty i nową funkcję aaBindEventListener. Zamiast przekazywać funkcję bezpośrednio w wywołaniu metody setTimeout, ten kod wywołuje metodę aaBindEventListener, która zwraca potrzebną funkcję. Takie rozwiązanie pozwala zachować stan obiektu, włączając w to wartość licznika, która jest obecnie właściwością obiektu. Listing 4.12. Bliższe spojrzenie na zegary ajaksowe Nowe zegary
10
Zanikanie kolorów w wyniku sukcesu lub niepowodzenia
|
129
Powód ustawienia metody countDown obiektu Counter na null wynika z wycieku pamięci w przeglądarce Internet Explorer 6.x przy używaniu rekurencji lub techniki domknięć funkcji (wywoływania funkcji wewnątrz funkcji). Ten problem został rozwiązany w przeglądarce Internet Explorer 7, jednak programiści używający Ajaksa muszą uwzględniać działanie przeglądarki Internet Explorer 6.x dopóty, dopóki użytkownicy nie przestaną z niej korzystać. Zastosowanie instrukcji Function.call do zarządzania zegarami to ciekawa technika, choć początkowo może być nieco trudna do zrozumienia. Jest to lepsze rozwiązanie niż ustawianie zmiennych globalnych w różnych miejscach, ponieważ znacznie ułatwia zarządzanie wartościami pomiędzy kolejnymi wywołaniami zegara. Następny punkt opisuje zastosowanie zegarów do tworzenia chwilowych powiadomień wykorzystujących efekt zanikania.
Tworzenie chwilowych powiadomień Nie jestem pewna, dlaczego żółty stał się kolorem stosowanym w technice zanikania. Kiedy się nad tym zastanowić, nie wiadomo nawet, dlaczego efekt ten nazywany jest zanikaniem — może dlatego, że kolor zanika po rozbłyśnięciu. Efekt żółtego zanikania został po raz pierwszy użyty w witrynie 37signals (http://www.37signals.com/svn/archives/000558.php) i możliwe, że zarówno kolor, jak i nazwa techniki to po prostu wynik przyzwyczajenia. Jednak żółty to dobry wybór, ponieważ niezdolność rozróżniania kolorów w przypadku żółtego i niebieskiego koloru jest rzadsza niż w przypadku czerwonego i zielonego (ten problem dotyka około 8 procent osób, choć dokładne wyniki nie są znane). Jednak nawet w przypadku osób całkowicie niewrażliwych na kolory zanikanie jest zauważalne jako błysk związany ze zmianą nasycenia, a nie koloru. Technika zanikania opiera się na zegarach, a polega na przejściu przez zestaw odcieni (od jaskrawego do stonowanego danej barwy) i zmianie koloru tła elementu w każdej iteracji. Obecnie używanych jest wiele odmian tego efektu, włączając w to te, które przechodzą od jednego koloru do innego i nie są ograniczone do żółtej barwy. Technika zanikania prawdopodobnie nie jest najbardziej zaawansowanym efektem Ajaksa, jednak jej implementacja nie jest prosta, jeśli programista nie używa stałej tablicy z wartościami kolorów. W przypadku bardziej uniwersalnego zanikania w każdej iteracji obejmującej blednięcie koloru aplikacja pobiera kolor tła, przetwarza dwuznakowy łańcuch z heksadecymalną wartością czerwieni, błękitu i zieleni, przekształca ten łańcuch na wartość liczbową, modyfikuje tę wartość na potrzeby najbliższej zmiany, a następnie przekształca ją ponownie na łańcuch. Mając wartość początkową i końcową, aplikacja musi obliczyć zmianę odcieni między poszczególnymi krokami, zachować te wartości i użyć ich do dostosowania koloru. Nie ma metody, która pozwalałaby na utworzenie dyskretnej i dostępnej wersji zanikania, ponieważ efekt ten to wskazówka wizualna. Jednak zanikanie to bardziej dodatek niż konieczność — o wystąpieniu zdarzenia powinny informować inne efekty na stronie. Niestety, takie drugorzędne wskazówki zwykle nie są uwzględniane przez programy odczytujące tekst z ekranu. Rozdział 7. opisuje niektóre techniki zarządzania efektami wizualnymi i dynamicznymi pod kątem takich programów.
130
|
Rozdział 4. Efekty interaktywne
Ostatnia aplikacja w tym rozdziale łączy w sobie przedstawione wcześniej przykłady. Strona zawiera formularz z przeznaczonym na komentarz polem textarea oraz dwoma przyciskami. Jeden z nich służy do wyświetlania podglądu, a drugi — zapisywania danych. Jeśli obsługa skryptów jest włączona, w momencie zapisywania komentarza program nie wysyła danych za pomocą tradycyjnego przesyłania formularza, ale wywołuje metodę Ajaksa. W tym przykładzie wywoływany program nie wykonuje żadnych operacji na danych, a jedynie wyświetla komentarz, kiedy użytkownik kliknie przycisk Zapisz, a obsługa skryptów jest włączona. Jeśli skrypty nie są obsługiwane lub jeśli użytkownik kliknie przycisk Podgląd, aplikacja wyświetli komentarz w przeglądarce:
Ten przykład jest uproszczony. W innych aplikacjach warto zastosować sekwencje ucieczki dla danych, aby upewnić się przed aktualizacją bazy, że zapisywane informacje są bezpieczne. W przeciwnym razie programista naraża się na atak polegający na „wstrzyknięciu” szkodliwego kodu SQL. Używany arkusz stylów nie jest zbyt skomplikowany, jednak dodaje nieco elegancji i kolorów, co urozmaica stronę: #list { border: 1px solid #ccc; margin: 20px; padding: 10px; width: 600px; } .comment { margin: 10px 0; width: 400px; } form, #preview { border: 1px solid #cc0; margin: 20px; padding: 10px; width: 600px; }
Zaskakujące, jak krótka jest strona internetowa po usunięciu z niej arkusza stylów i kodu JavaScript: Zegary, Ajax i efekt zanikania
Bieżące komentarze:
Listing 4.13 przedstawia zawartość pliku JavaScript comments.js. Ponieważ dane są aktualizowane, wywołanie Ajaksa używa metody POST, a nie GET. Po zapisaniu komentarza jest on umieszczany na liście komentarzy, a o jego dodaniu informuje efekt żółtego zanikania. Ten przykład używa tradycyjnego, żółtego koloru, rozpoczynając od wartości #ffff00, a kończąc na białym tle — #ffffff. Ponadto skrypt używa techniki wiązania z biblioteki Adding Ajax do zarządzania zdarzeniami zegarów. Listing 4.13. Połączenie podglądu komentarzy, metody send Ajaksa i żółtego zanikania // Zmienne globalne var commentCount = 0; var xmlhttp; function yellowColor(val) { var r="ff"; var g="ff"; var b=val.toString(16); var newval = "#"+r+g+b; return newval; } aaManageEvent(window,"load", function() { aaManageEvent(document.getElementById('save'),"click",saveComment); }); function saveComment(evnt) { // Anuluje zdarzenie evnt = evnt ? evnt : window.event; aaCancelEvent(evnt); // Tworzenie obiektu XHR if (!xmlhttp) xmlhttp = aaGetXmlHttpRequest(); if (!xmlhttp) return; // Pobieranie komentarza var commentText = document.getElementById("comment").value; modText = commentText.split(/\n/).join(" "); var param = "comment=" + modText;
132
|
Rozdział 4. Efekty interaktywne
var url = 'addcomment.php?' + param; xmlhttp.open('POST', url, true);
// Dodawanie komentarza do listy z wyróżnieniem za pomocą koloru function addComment() { if(xmlhttp.readyState == 4 && xmlhttp.status==200) { var modText=xmlhttp.responseText; var newDiv = document.createElement("div"); commentCount++; newDiv.setAttribute("id","div"+commentCount); newDiv.setAttribute("class","comment"); newDiv.innerHTML = modText; // Dodawanie obiektu do strony document.getElementById("list").appendChild(newDiv); // Uruchomienie licznika do zmiany kolorów var ctrObj = new Counter("div"+commentCount,0,255); ctrObj.countDown(); }
Zaczynając od początku kodu: kiedy użytkownik zapisze komentarz, przesyłanie formularza zostaje wstrzymane, ponieważ to Ajax odpowiada za aktualizację. Aplikacja pobiera komentarz i przetwarza go w prosty sposób przed przesłaniem za pomocą obiektu XMLHttpRequest i metody POST. Kiedy żądanie Ajaksa zostanie prawidłowo przetworzone, aplikacja tworzy nowy element div przeznaczony na komentarz, który następnie dodaje do istniejącej listy komentarzy. Po dołączeniu elementu uruchamiany jest efekt zanikania w celu wyróżnienia dodanego komentarza. Zanikanie kolorów w wyniku sukcesu lub niepowodzenia
|
133
Rysunek 4.3 przedstawia efekt uruchomienia ostatniego przykładu. Niestety, nie potrafię się wystarczająco sprawnie posługiwać programami do wykonywania zrzutów ekranu, aby uchwycić efekt żółtego zanikania, ale omawiany przykład jest dostępny w kodzie źródłowym dołączonym do książki.
Rysunek 4.3. Podgląd, efekt zanikania i Ajax w jednym
Oczywiście, jest to uproszczone spojrzenie na współdziałanie komentarzy i aktualizacji na żywo. Jednak to rozwiązanie można łatwo zintegrować z istniejącą aplikacją, wywołując funkcje służące do obsługi komentarzy po stronie serwera. Funkcje te powinny także gwarantować, że tekst komentarza nie powoduje zagrożenia. Później komentarz można pobrać albo z takiej funkcji, albo z bazy danych, a następnie wyświetlić w odpowiednim polu. Cała operacja powinna zająć ułamek sekundy i z punktu widzenia użytkownika powinna wyglądać na natychmiastową. Co najlepsze, nie wymaga to odświeżania strony, co rozprasza uwagę. A co, jeśli obsługa skryptów jest wyłączona? Nie ma problemu — kod do standardowego zarządzania komentarzami wciąż jest częścią strony. Ta aplikacja to w pewnym sensie Ajax w kapsułce — połączenie interakcji z użytkownikiem, żądań Ajaksa, obiektów, zegarów i efektów wizualnych. Możesz sobie pogratulować — jesteś już programistą Ajaksa. Jednak nie zalecam rezygnacji z lektury pozostałej części książki.
134
|
Rozdział 4. Efekty interaktywne
ROZDZIAŁ 5.
Przestrzeń — ostateczna granica
Zarządzanie przestrzenią współczesnych stron internetowych obejmuje cztery aspekty: obszar zajmowany przez każdy element, rozmieszczenie elementów w poziomym i pionowym układzie strony, ich pozycje w warstwach strony oraz to, czy poszczególne elementy są w danej chwili widoczne. Przed pojawieniem się stylów CSS i języka JavaScript przestrzeń stron internetowych była statyczna. Można było użyć elastycznego kontenera, na przykład tabel HTML dostosowujących się do rozmiaru okna przeglądarki, jednak nie było żadnych innych możliwości. Jeśli programista używał formularza z dużą liczbą pól, mógł albo umieścić je wszystkie na jednej długiej stronie, albo rozbić formularz na wiele stron i aktualizować dane we fragmentach. Jeśli użytkownik aplikacji zrobił błąd, nie wiedział o tym do czasu przesłania formularza na serwer. Wtedy aplikacja zwykle kierowała użytkownika do nowej strony tylko po to, aby wyświetlić komunikat o błędzie. Obecnie, dzięki pewnym technologiom Ajaksa, elementy można rozmieszczać w przestrzeni i układać w warstwach, określając ich pozycję bezwzględnie. Każdy element ma cechy takie, jak: szerokość, wysokość, dopełnienie i margines, które można modyfikować. Elementy mają także obszar przycięcia (ang. clipping), który określa, jaka część obiektu jest widoczna, a ile zostanie „przycięte” (taki fragment jest niewidoczny i nie ma wpływu na układ strony wokół siebie). Jeśli atrybut display elementu ma wartość block, a nie inline, przed i po danym obiekcie dołączane są oznaczenia nowego wiersza, co powoduje, że dalsze elementy zostaną przesunięte w dół strony. Element można także całkowicie usunąć z układu strony, ustawiając atrybut display na none. Podsumowując, dzięki stylom CSS i językowi JavaScript można: • zmieniać wysokość i szerokość elementów; • zmieniać obramowanie, dopełnienie i margines elementów; • modyfikować obszar przycięcia; • układać elementy w warstwach; • przenosić i ukrywać elementy; • zmieniać sposób wyświetlania elementów, na przykład przekształcać je na wewnątrzwier-
szowe lub blokowe, a nawet usuwać z widoku.
135
Manipulowanie poszczególnymi cechami elementów jest wygodne, a połączenie takich zmian pozwala tworzyć efekty wyższego poziomu, od których zależy wiele aplikacji Ajaksa. Te efekty obejmują: • kontrolkę accordion, którą można rozwijać i zwijać na podstawie działań użytkownika; • strony z zakładkami, które w danym momencie wyświetlają pojedynczą stronę lub panel; • nakładanie (ang. overlay), które polega na umieszczaniu elementu nad resztą zawartości
strony;
• stronicowanie, które łączy przenoszenie zawartości i przycinanie jej do rozmiaru „strony”; • latające elementy (ang. fly-ins), które pojawiają się zza pewnego obszaru, na przykład kra-
wędzi strony;
• przeciąganie, które umożliwia użytkownikom aplikacji przenoszenie elementów.
W tym rozdziale przedstawione są trzy takie efekty (accordion, strony z zakładkami i nakładanie), które manipulują zarówno przestrzenią strony, jak i wyświetlaniem elementów. Poznasz także wpływ tych efektów na dostępność oraz techniki, które pozwalają go zmniejszyć. Przy okazji zobaczysz, jak umieścić efekty wysokiego poziomu w nadających się do wielokrotnego wykorzystania bibliotekach, których można użyć dla więcej niż jednej strony i aplikacji. Stosowanie innych efektów Ajaksa oszczędzających miejsce, na przykład przycinania, przeciągania czy stronicowania, jest opisane w dalszych rozdziałach.
Przestrzeń w poziomie — accordion Jednym z moich ulubionych efektów ajaksowych związanych z przestrzenią jest kontrolka accordion. Cenię ją zarówno za wygląd, jak i działanie. Efekt ten polega na tym, że kilka bloków z treścią, zwykle nazywanych panelami, jest ułożonych jeden nad drugim i ukrywanych za pomocą jednej z trzech technik: • atrybut display każdego bloku ma wartość none; • elementy są całkowicie przycięte w pionie; • wysokość każdego elementu jest ustawiona na zero.
Przy pierwszym wyświetleniu kontrolki accordion zwykle widoczny jest nagłówek (lub etykieta) każdego panelu wraz z ikoną informującą, że użytkownik musi kliknąć daną nazwę, aby rozwinąć (lub zwinąć) dany panel. W kodzie poszczególne etykiety i panele to dwa oddzielne, ale powiązane ze sobą elementy, które mogą, ale nie muszą znajdować się we wspólnym kontenerze. Wszystkie panele i etykiety mogą być połączone i zawarte w ogólnym kontenerze. Accordion umożliwia wydajne wykorzystanie przestrzeni, zwłaszcza kiedy programista użyje go do obsługi długiego, wieloczęściowego formularza. Ponadto łatwo utworzyć alternatywę dla tego efektu, jeśli użytkownik wyłączy obsługę języka JavaScript — przy wyłączonych skryptach wszystkie panele są rozwinięte po wczytaniu strony. Rysunek 5.1 przedstawia typową kontrolkę accordion z rozwiniętym pierwszym i trzecim panelem, podczas gdy drugi jest zwinięty. W dalszej części niniejszego punktu opisuję, jak utworzyć tę aplikację. 136
|
Rozdział 5. Przestrzeń — ostateczna granica
Rysunek 5.1. Efekt accordion obejmujący trzy bloki
Tworzenie efektu Najprostszym sposobem rozwijania paneli jest zastosowanie właściwości display stylów CSS. Ustawienie atrybutu display na block powoduje wyświetlenie panelu i przy okazji przenosi pozostałą część zawartości strony w dół, aby zrobić miejsce na rozwijany obiekt. Ustawienie tego atrybutu na none usuwa blok z widoku, a elementy znajdujące się poniżej są przenoszone w górę, aby zapełnić puste miejsce. Accordion może mieć wskaźniki na pasku tytułu każdego bloku, określające działanie kontrolki po kliknięciu danego nagłówka. Najczęściej jest to znak plus (+), oznaczający, że można rozwinąć ukryte dane, oraz znak minus (–), informujący, że treść można ukryć. W niektórych kontrolkach accordion nie ma wskaźnika wizualnego. Programista zakłada wtedy, że użytkownicy potrafią „intuicyjnie” wykryć, iż mogą bezpośrednio kliknąć etykiety kontrolki accordion, aby kontrolować efekt. Jeśli kontekst nie udostępnia wystarczających informacji, lepiej jest użyć bezpośredniego wskaźnika informującego o ukrytej zawartości.
Accordion to grupa bloków z panelami, umieszczonych jeden na drugim. Każdy panel jest powiązany z paskiem tytułu. Styl CSS bloków z panelami jest ustawiony na display:block (jest to domyślne ustawienie elementów div), dlatego jeśli obsługa skryptów jest wyłączona, po wczytaniu strony bloki będą wyświetlane jako całkowicie rozwinięte. Listing 5.1 przedstawia stronę internetową składającą się z trzech poziomych bloków, które stanowią fragmenty formularza. Każdy wskaźnik rozwijania i ukrywania kontrolki accordion
Przestrzeń w poziomie — accordion
|
137
znajduje się w odnośniku hipertekstowym, a do uchwytu zdarzenia onclick każdego odnośnika przypisana jest funkcja, do której aplikacja przekazuje jako parametr id danego bloku. W dalszej części rozdziału, kiedy kod kontrolki accordion zostanie umieszczony w bibliotece, co ułatwia jego powtórne wykorzystanie, uchwyty zdarzeń onclick zostaną usunięte, dzięki czemu elementy strony nie będą zawierały skryptów. Jednak na razie ich obecność nie wpływa na dostępność ani poprawność, a jedynie umożliwia ponowne użycie kodu. Listing 5.1. Strona internetowa z trzema blokami kontrolki accordion Accordion
Inne dane lub informacje.
Ponieważ aplikacja działa wyłącznie na podstawie skryptów, kusi, aby usunąć odnośniki z zakładek kontrolki accordion i dołączyć metody obsługi zdarzenia onclick bezpośrednio do elementów div. Jednak takie postępowanie może uniemożliwić obsługę kontrolki accordion za pomocą klawiatury. Dodatkowy atrybut elementu, który pomaga w zwiększaniu dostępności, to accesskey lub, w nowszym XHTML 2.0, access. Oba te atrybuty wiążą klawisz klawiatury bezpośrednio z danym elementem. Dzięki temu użytkownik klawiatury może przejść bezpośrednio do tego elementu, używając skrótu Alt+klawisz w systemie Windows lub Ctrl+klawisz w systemie Mac. Niestety, te skróty mogą powodować konflikt ze skrótami przeglądarki, użytkownika, a nawet narzędzi pomocniczych.
Aplikacja modyfikuje właściwość display stylów CSS panelu, ustawiając ją albo na block, albo na none. Zależy to od tego, czy blok ma zostać rozwinięty czy ukryty. Pozostała część arkusza stylów CSS używanego dla strony z listingu 5.1 jest przedstawiona poniżej. Warto zwrócić uwagę na specyficzny dla CSS2 selektor (type="checkbox"), który służy do odróżnienia pól tekstowych od pól wyboru. Choć ten selektor działa w większości docelowych przeglądarek, Internet Explorer ignoruje go, a pola wyboru są wyświetlane w domyślnym trybie blokowym. .name { margin: 0; padding: 0; } .label { background-color: #003; border-bottom: 1px solid #fff;
W wyjściowych ustawieniach arkusza stylów elementy wskaźników rozwijania i zwijania są ukryte, a wszystkie bloki są widoczne. Wynika to z tego, że jeśli użytkownik odwiedzający stronę wyłączył obsługę skryptów lub używa narzędzi, które nie obsługują języka JavaScript, wskaźniki zostaną ukryte, a wszystkie elementy formularza będą domyślnie widoczne. Kod JavaScript służący do obsługi kontrolki accordion (accordion1.js) jest dość prosty. Ponieważ aplikacja używa funkcji obsługi zdarzenia onclick wewnątrzwierszowo, wystarczy zmienić atrybut display wskaźnika i bloku: aaManageEvent(window,"load", function() { document.getElementById('one').style.display='none'; document.getElementById('two').style.display='none'; document.getElementById('three').style.display='none'; document.getElementById('oneplus').style.display='block'; document.getElementById('twoplus').style.display='block'; document.getElementById('threeplus').style.display='block'; }); // Rozwijanie bloku kontrolki accordion function expand(newItem) { document.getElementById(newItem).style.display='block'; document.getElementById(newItem + 'plus').style.display='none';
Funkcja obsługi zdarzenia load obiektu window to funkcja anonimowa, która zwija wszystkie bloki (ustawia atrybut display na none) i wyświetla wskaźniki informujące o możliwości rozwinięcia. Dwie dodatkowe funkcje, expand i collapse, odpowiednio rozwijają i zwijają blok, którego identyfikator otrzymają. Ponadto funkcja expand ukrywa wskaźnik rozwijania i wyświetla wskaźnik zwijania, a funkcja collapse wykonuje odwrotne operacje. Wypróbuj aplikację samodzielnie, a prawdopodobnie zrozumiesz, dlaczego technika ta jest tak popularna — na tyle, że jest kilka sposobów implementacji tego efektu, a niektóre z nich obejmują animację opartą na właściwości height stylów CSS, co ilustruje następny podpunkt.
Accordion z efektem przejścia Listing 5.1 przedstawia kontrolkę accordion bez efektu przejścia pomiędzy wyświetleniem a ukryciem bloku. Następna technika dodaje przejście pomiędzy stanem „ukryty” a „w pełni widoczny”. Jednak nie można użyć tu ustawienia display: none stylów CSS, ponieważ określa ono tylko, czy element jest widoczny, czy nie, bez stanów pośrednich. Aby utworzyć płynne przejście, trzeba użyć innej właściwości CSS, na przykład wysokości lub przycięcia, a nawet połączenia przycięcia i przesuwania, zwiększając widoczny obszar wraz z „przenoszeniem” go na odpowiednie miejsce. W przypadku kontrolki accordion najprostszym rozwiązaniem jest zastosowanie atrybutu height stylów. Aby utworzyć efekt accordion przy użyciu atrybutu height, aplikacja powinna określać wysokość każdego panelu, a następnie odpowiednio zmniejszać ją lub zwiększać dla każdego bloku. Inne rozwiązanie polega na sprawdzaniu wysokości elementów tuż przed ich ustawieniem na zero w czasie pierwszego wczytywania strony. Ja preferuję to drugie podejście, ponieważ przy jego zastosowaniu kontrolki accordion mogą być tak długie, jak wymaga tego ich zawartość, bez narażania się na ograniczenia związane z wysokością. Jednak pobieranie wysokości elementów może sprawiać problemy.
Pobieranie wysokości i szerokości elementów Sprawdzanie wysokości elementu po ustawieniu jego wyjściowej wielkości za pomocą wewnątrzwierszowego atrybutu stylu jest proste — wystarczy uzyskać dostęp do właściwości style.height elementu. Można to zrobić bezpośrednio albo za pomocą metody zalecanej przez W3C — getProperty: var height = elem.style.height; var height = elem.style.getProperty('height');
Ponieważ jednak w omawianej aplikacji unikam stylów wewnątrzwierszowych, należy zastosować inne rozwiązanie: uzyskać dostęp do obliczanej wysokości elementu poprzez obliczaną właściwość stylu.
Przestrzeń w poziomie — accordion
|
141
Jedną z technik określania obliczanej wartości stylu w sposób działający w różnych przeglądarkach można znaleźć w bibliotece Prototype Ajaksa. Metoda getStyle obiektu Element z tej biblioteki to dość złożona funkcja, która musi uwzględniać różnice między przeglądarkami w zakresie obsługi stylów. Obsługi wymagają elementy pływające i różne wartości zwracane, na przykład auto, nie wspominając o tym, co się stanie w niektórych przeglądarkach po zmianie układu z fixed na relative lub absolute albo w przypadku zastosowania prawdopodobnie najbardziej kłopotliwego ustawienia stylów, opacity: Element.Methods = { ... getStyle: function(element, style) { element = $(element); if (['float','cssFloat'].include(style)) style = (typeof element.style.styleFloat != 'undefined' ? 'styleFloat' : 'cssFloat'); style = style.camelize(); var value = element.style[style]; if (!value) { if (document.defaultView && document.defaultView.getComputedStyle) { var css = document.defaultView.getComputedStyle(element, null); value = css ? css[style] : null; } else if (element.currentStyle) { value = element.currentStyle[style]; } } if((value == 'auto') && ['widht','height'].include(style) && (element. getStyle('display') != 'none')) value = element['offset'+style.capitalize()] + 'px'; if (window.opera && ['left', 'top', 'right', 'bottom'].include(style)) if (Element.getStyle(element, 'position') == 'static') value = 'auto'; if(style == 'opacity') { if(value) return parseFloat(value); if(value = (element.getStyle('filter') || '').match(/alpha\(opacity=(.*)\)/)) if(value[1] return parseFloat(value[1]) / 100; return 1.0; } return value == 'auto' ? null : value; }, ... };
W tym przypadku nie jest potrzebny aż tak skomplikowany kod. Aby określić wysokość elementu, można użyć albo właściwości clientHeight, albo offsetHeight. Niestety, wadą tych właściwości jest to, że żadna z nich nie jest standardowym ele-
mentem W3C ani nie jest zgodna z technicznymi zaleceniami (obie pochodzą z przeglądarki Internet Explorer). Ponadto obie właściwości mogą zwracać różne wartości w zależności od przeglądarki, ustawień CSS danego elementu i tego, czy przeglądarka działa w trybie standardowym czy w trybie quirks.
Wartość właściwości clientHeight elementu to jego wysokość pomniejszona o margines i obramowanie, które znajdują się poza elementem. Wartość offsetHeight powinna zawierać wysokość elementu, włączając w to margines i obramowanie. Niestety, jednym z problemów (który wkrótce zobaczysz) przy dostępie do elementów za pomocą funkcji takich jak getElementsByTagName, a nie poprzez identyfikatory poszczególnych elementów, jest to, że właściwość clientHeight zwraca zero. Jednak właściwość offsetHeight wciąż działa prawidłowo, zwracając pełną wysokość danego elementu. Uwzględniając to, że potrzebna jest całkowita 142
|
Rozdział 5. Przestrzeń — ostateczna granica
wysokość elementów, można dojść do wniosku, że warto użyć tej ostatniej właściwości. Ponieważ jest ona obsługiwana w przeglądarkach docelowych używanych w tej książce, to właśnie ona zostanie wykorzystana w aplikacji. Większość osób do upraszczania żądań Ajaksa zaleca używanie bibliotek takich jak Prototype. Moim zdaniem to sposób obsługi metod modelu DOM i efektów w różnych przeglądarkach usprawiedliwia stosowanie tych bibliotek. Ajaksowe metody kierowania żądań do usług są dość proste w porównaniu z zarządzaniem modelem DOM.
Kończenie przejścia Elementy strony w wersji z kontrolką accordion z przejściem nie zmieniają się. Nowy jest tylko dołączany plik z kodem JavaScript, accordion2.js:
Arkusz stylów także nie wymaga modyfikacji. Jedyny komponent aplikacji, który wymaga istotnych zmian, to kod JavaScript przedstawiony na listingu 5.2. Funkcja obsługi zdarzenia load obiektu window wywołuje inną funkcję, setHeight, aby pobrać obliczaną wysokość każdego bloku i przypisać ją, według nazwy bloku, do tablicy asocjacyjnej. Ta ostatnia funkcja ustawia następnie wyjściową wysokość każdego elementu na zero. Listing 5.2. Kod JavaScript do zarządzania efektem accordion z przejściem // Globalna tablica heightObjs na obliczoną wysokość var heightObjs = new Array(); // Pobieranie obliczonej wysokości i ustawianie jej na zero function setHeight(objName) { var obj = document.getElementById(objName); var height = aaGetStyle(obj,'height'); heightObjs[objName] = Math.round(parseFloat(height.substr(0,height.length-2))); obj.style.height="0"; } // Przygotowywanie paneli aaManageEvent(window,"load", function() { setHeight('one'); setHeight('two'); setHeight('three'); document.getElementById('oneplus').style.display='block'; document.getElementById('twoplus').style.display='block'; document.getElementById('threeplus').style.display='block'; }); // Animowane rozwijanie elementu function expand(newItem) { document.getElementById(newItem + 'plus').style.display='none'; document.getElementById(newItem + 'negative').style.display='block'; var currentItem = document.getElementById(newItem); // Określanie przyrostu przez dzielenie obliczonej // wysokości przez liczbę powtórzeń pętli - 20 var incr = heightObjs[newItem] / 20; for (var i=0; i < 20; i++) { var val = (i+1) * incr;
Przestrzeń w poziomie — accordion
|
143
var func = "adjustItem("+val+",'"+newItem+"')"; setTimeout(func,(i+1)*30); } } // Animowane ukrywanie elementu function collapse(newItem) { document.getElementById(newItem + 'plus').style.display='block'; document.getElementById(newItem + 'negative').style.display='none'; var currentItem = document.getElementById(newItem); // Określanie pomniejszenia przez dzielenie obliczonej // wysokości przez liczbę powtórzeń pętli - 20 var decr = heightObjs[newItem] / 20; for (var i=0; i<20; i++) { var val = heightObjs[newItem]-(decr*(i+1)); var func = "adjustItem("+val+",'"+newItem+"')"; setTimeout(func,(i+1)*30); } } // Dostosowywanie poszczególnych elementów function adjustItem(val, newItem) { document.getElementById(newItem).style.height=val + "px"; }
Funkcje rozwijające i zwijające elementy nadal rozwijają i zwijają panele, jednak obecnie każda z nich używa pętli do zwiększania lub zmniejszania wysokości, sterując wielkością widocznego fragmentu. Rozmiar zmiany jest określany przez podzielenie całkowitej wysokości elementu przez liczbę powtórzeń pętli — w tym przypadku jest to 20. Za zmianę wysokości odpowiada odrębna funkcja, adjustHeight. Stopniowe zmiany wysokości są obsługiwane za pomocą zegarów języka JavaScript, co daje efekt animacji. Ponieważ w tym przypadku nie trzeba zachowywać kontekstu obiektu w funkcjach obsługi zdarzeń, co było konieczne w animacjach opisanych w rozdziale 4., aplikacja nie musi używać wyspecjalizowanej funkcji aaBindEventListener z biblioteki addingajax.js. Wystarczy użyć wygenerowanego łańcucha znaków, który zawiera nazwę funkcji i obliczone parametry. Wysokość można określić przez pobranie wartości wysokości (usuwając jednostki — px). Następnie wartość tę należy przekazać do funkcji parseFloat, która gwarantuje, że wysokość zostanie zinterpretowana jako liczba. Na zakończenie wartość jest zaokrąglana do najbliższej liczby całkowitej. Po dodaniu tego kodu kliknięcie wskaźnika rozwijania lub zwijania spowoduje animowane otwarcie lub zamknięcie panelu, co dodaje nieco elegancji, choć niekoniecznie zwiększa ogólną wydajność efektu. Po przyjrzeniu się „domowej” wersji efektu accordion warto zobaczyć, jak inni programiści udostępniają tę samą funkcjonalność.
Używanie gotowych kontrolek accordion Zamiast tworzyć własny kod kontrolki accordion, można użyć gotowego efektu z zewnętrznej biblioteki Ajaksa. Jedną z takich bibliotek jest Accordion, dostępna wraz z biblioteką Rico, opisaną w rozdziale 3.
144
|
Rozdział 5. Przestrzeń — ostateczna granica
Efekt accordion biblioteki Rico różni się od wersji z poprzedniego punktu, ponieważ nie udostępnia wskaźników rozwijania i zamykania. Ponadto panele w kontrolce accordion biblioteki Rico nie mogą mieć ramek ani dopełnienia. Jeśli dopełnienie jest potrzebne, trzeba użyć zagnieżdżonego elementu div lub czegoś podobnego do elementu fieldset użytego w dwóch poprzednich przykładach. Relacje między elementami muszą być także zgodne z określonym wzorcem układu strony, na której obszary z treścią znajdują się w ogólnym kontenerze. Aby utworzyć kontrolkę accordion z biblioteki Rico, trzeba dołączyć do strony internetowej biblioteki Rico i Prototype. Poniższa składnia tworzy podstawową kontrolkę accordion biblioteki Rico bez żadnych opcji: new Rico.Accordion( $('accordionDiv'));
Jako drugi parametr można przekazać pewne opcje. Poniższy fragment pochodzi z kodu biblioteki: this.options = { expandedBg hoverBg collapsedBg expandedTextColor expandedFontWeight hoverTextColor collapsedTextColor collapsedFontWeight hoverTextColor borderColor panelHeight onHideTab onShowTab onLoadShowTab }
Każdą z tych domyślnych opcji można przesłonić. Najczęściej postępuje się tak z opcją panelHeight, ponieważ panele muszą być wystarczająco wysokie, aby móc pomieścić treść (przewijanie w pionie jest niewskazane). Aby jak najlepiej dopasować ustawienia do istniejących paneli, można użyć wysokości 200px, a kolor tła nagłówka należy przekazać do używanego obiektu. Choć domyślnie biblioteka Rico używa niebieskiego, w aplikacji użyty zostanie inny odcień tej barwy. Ponadto trzeba dopasować do poprzedniego przykładu wielkość i kolor czcionki: new Rico.Accordion( $('accordionDiv'), {expandedBg : '#003', collapsedBg : '#003', expandedFontWeight : 'normal', collapsedTextColor : '#fff', });
Aby dopasować rozwiązanie do nowej struktury elementów, trzeba dostosować używany arkusz stylów CSS: #accordionDiv { margin: 0 20px; width: 400px; } .name { background-color: #003; border-bottom: 1px solid #fff;
Ponieważ biblioteka Prototype udostępnia obsługę zdarzeń, aplikacja nie wymaga żadnej innej biblioteki do utworzenia efektu accordion po wczytaniu strony. Listing 5.3 zawiera kompletny kod strony internetowej, włączając w to kod JavaScript tworzący kontrolkę accordion i używający obsługi zdarzeń z biblioteki Prototype. Listing 5.3. Efekt accordion przy użyciu biblioteki Rico Accordion
Kontrolka accordion biblioteki Rico wyświetlana jest z jednym otwartym blokiem. Biblioteka obsługuje ten efekt, otwierając nowy panel w momencie zamykania innego, i nie manipuluje poszczególnymi blokami niezależnie od innych. Kliknięcie paska tytułu innego panelu powoduje zamknięcie otwartego, a następnie otwarcie nowego. Listing 5.3 dobitnie pokazuje zmniejszenie się ilości kodu, co jest korzystne. Jednak ta wygoda ma pewną cenę: trzeba zaakceptować działanie gotowego efektu, włączając w to wyjściowy otwarty panel, a także jednoczesne otwieranie i zamykanie. Gotowy efekt nie umożliwia także określania wysokości poszczególnych paneli. Jeśli programista chce używać własnego efektu accordion, a jednocześnie zachować wygodę związaną z korzystaniem z gotowej biblioteki, może zastanowić się nad umieszczeniem w bibliotece własnego kodu. Opisuje to następny punkt.
Przestrzeń w poziomie — accordion
|
147
Inna istotna różnica pomiędzy przedstawionymi aplikacjami polega na tym, że efekt oparty na bibliotekach Prototype i Rico nie będzie działał dla stron w formacie XHTML. Dlatego nagłówek DOCTYPE strony to HTML 4.01 Strict, a strona jest przesyłana jako dokument HTML.
Umieszczanie kodu efektu accordion w bibliotece Efekt accordion jest popularny nie tylko z powodu wydajnego zarządzania przestrzenią i prostoty. Jest także łatwy do przekształcenia w kod nadający się do wielokrotnego wykorzystania. Najpierw jednak warto przyjrzeć się utworzonym aplikacjom i poszukać obszarów, które można usprawnić. Jedną z pierwszych zmian w kodzie jest wskaźnik rozwijania i ukrywania. Zamiast znaku plus lub minus można użyć ikony trójkąta wskazującego w dół po rozwinięciu panelu i w prawo po jego ukryciu. W większym stopniu przypomina to funkcjonalność znaną z aplikacji stacjonarnych. Skoro zwykle nagłówki w kontrolkach accordion mają ciemny kolor, białe trójkąty z przezroczystym tłem to najlepszy wybór. Rysunki są w formacie GIF, ponieważ przeglądarki Internet Explorer 6.x nie obsługują przezroczystości obrazów w formacie PNG. Zmienić trzeba także strukturę efektu. Grupowanie zastosowane w Rico jest dużo lepsze niż próba połączenia elementów na podstawie nazw identyfikatorów. Potrzebne fragmenty kontrolki accordion to ogólny kontener i panele składające się z etykiety, która przyjmuje zdarzenia, oraz rozwijanej i ukrywanej treści. Podobnie jak w Rico struktura kontrolki accordion biblioteki Adding Ajax składa się z ogólnego kontenera dla każdego panelu. Taki kontener zawiera etykietę i treść, a kliknięcie etykiety powoduje rozwinięcie lub ukrycie go przy użyciu omówionej wcześniej techniki animacji. Jedną z popularnych technik wyszukiwania elementów jest programowe oznaczenie ich za pomocą atrybutu class. Pozwala to nie tylko sformatować obiekty wizualnie, ale także „oznaczyć”, które elementy strony są używane w danym efekcie. Można pójść o krok dalej i dodać klasy CSS dla etykiet kontrolki accordion. Te etykiety różnią się od siebie tylko tym, czy dany panel jest rozwinięty czy ukryty. Każdy pasek jest niemal taki sam, a odmienny jest wyłącznie rysunek tła, za co odpowiada wyróżniony kod: #accordionDiv { margin: 0 20px; width: 400px; } .nameExpanded, .nameCollapsed { background-position: center right; background-repeat: no-repeat; background-color: #003; border-bottom: 1px solid #fff; color: #fff; padding: 10px 0; text-align: center; background-image: url('expanded.gif'); } .nameCollapsed { background-image: url('collapsed.gif'); cursor: pointer;
Strona internetowa jest podobna do tej użytej w przykładzie zastosowania biblioteki Rico, jednak tym razem ma nagłówek DOCTYPE XHTML i można ją przesłać jako dokument w tym formacie. Przykład korzystający z biblioteki Rico nie jest zależny od stosowania klas, co ma miejsce w przypadku biblioteki Adding Ajax: Accordion
Inne dane i informacje.
Po pierwszym wczytaniu strony domyślną klasą etykiet paneli jest nameCollapsed, a wszystkie bloki są ukryte. Listing 5.4 zawiera kod JavaScript zarządzający bibliotecznym efektem accordion. Przykład oparty na bibliotece Rico nie zależał od atrybutu class, natomiast zależy od niego kod efektu accordion biblioteki Adding Ajax. Listing 5.4. Kod JavaScript z elementami kontrolki accordion o specyficznych klasach // Przygotowanie elementów kontrolki accordion aaManageEvent(window,"load", function() { var divs = document.getElementsByTagName('div'); for (var i = 0; i < divs.length; i++) { // Konfigurowanie kliknięcia paska tytułu if (divs[i].className == 'nameCollapsed') { aaManageEvent(divs[i],'click',Accordion.expandOrCollapse); // Przypisanie wysokości jako właściwości niestandardowej // i zwinięcie elementu } else if (divs[i].className == 'elements') { // IE 6.x obsługuje elementy zwrócone przez funkcję getElementsByTagName // w inny sposób, dlatego clientHeight nie działa; w zamian // użyto funkcji offsetHeight
150
|
Rozdział 5. Przestrzeń — ostateczna granica
var height = divs[i].offsetHeight; divs[i].height = height; if (divs[i].id == "") divs[i].id = "div" + i; divs[i].style.height = "0"; } } }); // Zarządzanie kontrolką accordion var Accordion = { // Dostosowanie wysokości adjustItem : function(val, newItem) { aaElem(newItem).style.height=val + "px"; }, // Sprawdzanie, czy panel należy rozwinąć czy ukryć expandOrCollapse : function (evnt) { evnt = evnt ? evnt : window.event; var target = evnt.target ? evnt.target : evnt.srcElement; if (target.className == 'nameCollapsed') Accordion.expand(target); else Accordion.collapse(target); }, // Rozwijanie panelu expand : function(target) { target.className = 'nameExpanded'; // Zmiana sygnału var children = target.parentNode.childNodes; var panel; for (var i = 0; i < children.length; i++) { if (children[i].className == 'elements') { panel = children[i]; break; } } var height = panel.height; // Obliczanie przyrostu przez podzielenie wysokości przez // liczbę iteracji pętli - 20 var incr = height / 20; for (var i=0; i < 20; i++) { var val = (i+1) * incr; var func = "Accordion.adjustItem("+val+",'"+ panel.id +"')"; setTimeout(func,(i+1)*30); } }, // Zwijanie panelu collapse : function (target) { target.className = 'nameCollapsed'; // Wyszukiwanie panelu var children = target.parentNode.childNodes; var panel; for (var i = 0; i < children.length; i++) { if (children[i].className == 'elements') { panel = children[i]; break; } } var height = panel.height;
Przestrzeń w poziomie — accordion
|
151
// Obliczanie pomniejszenia przez podzielenie wysokości przez // liczbę iteracji pętli - 20 var decr = height / 20; for (var i=0; i < 20; i++) { var val = height-(decr*(i+1));; var func = "Accordion.adjustItem("+val+",'"+ panel.id +"')"; setTimeout(func,(i+1)*30); } } };
Analizując kod od początku: do każdego elementu o klasie nameCollapsed przypisywana jest funkcja obsługi zdarzenia click w postaci metody expandOrCollapse obiektu Accordion. Ponadto po wczytaniu obliczana jest właściwość height elementu na podstawie jego bieżącej wysokości, a następnie aplikacja ustawia tę wysokość na zero w celu ukrycia panelu. Uzyskana wartość jest przypisywana do niestandardowej właściwości height. Zwykle w Ajaksie właściwość height jest albo częścią specyfikacji klasy obiektu, albo jest dołączana za pomocą właściwości prototype. Jednak całkowicie poprawne jest przypisywanie niestandardowych właściwości bezpośrednio do poszczególnych obiektów za pomocą kodu JavaScript. Jedyne ryzyko polega na tym, że jeden egzemplarz obiektu może mieć tę właściwość, a inne — nie. W tej aplikacji jest to niemożliwe. Przypisywanie niestandardowych właściwości bezpośrednio do egzemplarza może wydłużać czas przetwarzania. Silniki przetwarzające skrypty najpierw szukają właściwości w oryginalnej definicji obiektu, następnie w kolekcji prototype obiektu, a dopiero na końcu w egzemplarzu obiektu.
W obiekcie Accordion metoda expandOrCollapse pobiera element target zdarzenia click, a jeśli klasa tego elementu to nameCollapsed, wywołuje metodę expand. W przeciwnym razie wywoływana jest metoda collapse. Te metody zmieniają klasę etykiety, dzięki czemu widoczna w tle „strzałka” odzwierciedla stan panelu. Kod uzyskuje dostęp do etykiety kontrolki accordion i przechodzi po elementach podrzędnych do czasu wykrycia obiektu klasy elements. Jest to panel, który zostaje następnie stopniowo rozwinięty lub zwinięty. Rozwijanie panelu w pionie odbywa się do momentu wyświetlenia całej treści. Dzięki nowej bibliotece do obsługi kontrolki accordion elementy o odpowiednich klasach udostępniają wygląd i działanie tej kontrolki po wczytaniu strony. Nie wymaga to pracy autora strony internetowej, który musi jedynie użyć odpowiednich klas dla danych elementów — programista nie musi modyfikować kodu JavaScript. Podsumowując kolejne etapy:
1. Obiekt Accordion zawiera wszystkie potrzebne funkcje. 2. Po wczytaniu strony do wszystkich elementów div o klasie nameCollapsed przypisywana jest funkcja obsługi zdarzenia onclick, która kontroluje działanie kontrolki.
3. Wszystkie panele są w pełni rozwinięte w czasie wczytywania strony, dzięki czemu informacje są dostępne w przypadku, kiedy użytkownik wyłączył obsługę skryptów, a ponadto umożliwia to określenie wysokości każdego panelu.
4. Po określeniu wysokości każdego panelu uzyskana wartość jest przypisywana do obiektu reprezentującego dany element.
152
|
Rozdział 5. Przestrzeń — ostateczna granica
5. Następnie aplikacja zwija wszystkie panele. 6. Po kliknięciu etykiety panelu zmienia się jego klasa, a wygląd etykiety odzwierciedla stan panelu.
7. Aplikacja przechodzi po wszystkich elementach div kontenera nadrzędnego klikniętej etykiety, dopóki nie znajdzie obiektu klasy elements. Jest to bieżący panel.
8. Po znalezieniu panelu aplikacja rozwija go lub ukrywa w zależności od sytuacji. Używanie nazw klas jako części efektu wiąże się z pewnym zagrożeniem: jeśli nowy programista, który nie zna zależności skryptu od specyficznych klas, rozpocznie pracę z kodem, może przypadkowo zmodyfikować nazwy klas i zepsuć efekt. Przy stosowaniu złożonych efektów Ajaksa dobra komunikacja między projektantem a programistą strony internetowej jest kluczowa. W podtrzymywaniu komunikacji wraz z upływem czasu ważna staje się także dokumentacja.
Można połączyć dwa odrębne pliki JavaScript, addingajax.js i accordion.js, jednak zwykle podstawowe pliki JavaScript powinny być tak małe jak to możliwe, a efekty wyższego poziomu warto udostępniać osobno. Plik addingajax.js zawiera mechanizm obsługi zdarzeń i funkcje Ajaksa — oba te elementy będą potrzebne dużo częściej niż efekt accordion. Skoro już jesteśmy przy odrębnych bibliotekach — co się stanie, jeśli programista połączy niestandardowe operacje z bibliotekami zewnętrznymi? Do tego miejsca panele kontrolki accordion miały zawartość statyczną; co ma zrobić programista, jeśli chce generować taką zawartość dynamicznie i wciąż używać zewnętrznej biblioteki Ajaksa, na przykład Rico?
Rozszerzanie i żądania — łączenie kontrolek accordion z wywołaniami Ajaksa Kontrolki accordion mogą być doskonałym narzędziem do udostępniania danych na żądanie. Zamiast wyświetlać kilka wierszy danych wraz z nawigacyjnymi odnośnikami do następnej i poprzedniej strony, można rozbić dane na podstawie istotnych parametrów i umieścić je w odrębnych sekcjach kontrolki accordion. Aby zapobiec wydłużaniu czasu wczytywania strony, można także pobierać dane na żądanie po pierwszym otwarciu danego panelu. Jest kilka technik umożliwiających utworzenie tego złożonego efektu, jednak każda z nich ma pewne ograniczenia. Jeśli programista chce dodać obsługę zdarzeń do „gotowego” efektu, takiego jak accordion, użycie istniejącej biblioteki może się okazać niemożliwe. Dlaczego? Ponieważ autorzy wielu bibliotek nie udostępniają kodu przechwytywanych zdarzeń ani używanych obiektów, który można by wstawić do własnego kodu i scalić z nim. W przypadku kontrolki accordion biblioteki Rico można wprowadzić pewne zmiany w czasie tworzenia obiektu. Na przykład możliwe jest dostosowanie kolorów i innych właściwości wizualnych. Jednak w dokumentacji nie ma nic o tym, które zdarzenia należy przechwycić, aby zgłosić żądanie Ajaksa, ani jak Rico zarządza obiektami w celu dodania wyników do obiektu panelu. Jedyny sposób, aby się tego dowiedzieć, polega na dogłębnej analizie kodu lub odrębnej implementacji efektów — wywołań Ajaksa na żądanie i rozwijania kontrolki accordion — i sprawdzeniu, jak samodzielnie utworzone, niestandardowe efekty współdziałają z gotowymi efektami z biblioteki Rico. Przestrzeń w poziomie — accordion
|
153
Aplikacja przedstawiona w tym punkcie używa takiego mieszanego podejścia. Funkcjonalność obejmująca pobieranie danych z serwera w momencie rozwijania bloku oparta jest na lokalnym kodzie, podczas gdy rozwijaniem paneli zarządza biblioteka Rico. Przykład zastosowania kilku bibliotek łączy aplikację Drinki z poprzednich rozdziałów z opartą na Rico aplikacją przedstawioną we wcześniejszej części tego rozdziału. Ponieważ Rico zależy od biblioteki Prototype, przykładowa aplikacja używa bezpośrednio także tej biblioteki. Po wprowadzeniu zmian kliknięcie etykiety panelu spowoduje wczytanie odpowiedniego przepisu na drinka. Jeśli przepis nie został już wcześniej pobrany, aplikacja skieruje żądanie na serwer, a następnie wyświetli zwrócony przepis na panelu. Jeśli przepis jest już dostępny, wystarczy rozwinąć panel. Inna zmiana w aplikacji polega na zarządzaniu funkcjami dającymi dostęp do przepisów na drinki. Zamiast używać globalnych funkcji i zmiennych, można wykorzystać funkcjonalność biblioteki Prototype, używając jej do pomocy w zarządzaniu obiektem Drink. W szczególności w aplikacji tej używane są dwa mechanizmy biblioteki Prototype: metoda Class.create, która wywołuje metodę initialize obiektu po jego utworzeniu, oraz metoda bindAsEventListener, wiążąca kontekst z każdą metodą. W rozdziale 4. utworzyłam niestandardową metodę do wiązania kontekstu obiektu z metodą setTimeout, aby zagwarantować dostępność danego obiektu w każdym zdarzeniu zegara. W tym punkcie użyję metody bindAsEventListener biblioteki Prototype, ponieważ udostępnia ona podobne działanie w przypadku różnych metod obiektu Drink. Listing 5.5 przedstawia kod strony internetowej. Warto zauważyć, że panele nie mają zawartości innej niż „w toku…”, która jest wyświetlana do czasu pobrania przepisu. Listing 5.5. Strona internetowa aplikacji łączącej różne biblioteki, kontrolkę accordion i Ajaksa Accordion i drinki
Inne dane lub informacje.
Warto zwrócić uwagę na odnośniki do wszystkich plików ze skryptami bibliotek: Prototype, Rico, Adding Ajax i nowego pliku specyficznego dla aplikacji — accordionrico.js. To wiele bibliotek. Kod JavaScript aplikacji znajduje się na listingu 5.6. Ten kod obejmuje mechanizm obsługi zdarzeń biblioteki Prototype oparty na obiekcie Event i funkcji powiązanej ze zdarzeniem, kontrolce accordion z biblioteki Rico, obiekcie Ajax z biblioteki Adding Ajax oraz nowym, specyficznym dla aplikacji obiekcie Drink. Dla każdego panelu kontrolki accordion przeznaczonego na przepis na drinka aplikacja tworzy nowy obiekt Drink, zapisuje informacje specyficzne dla danego drinka i tworzy odbiornik zdarzeń po kliknięciu elementu etykiety z biblioteki Rico. W momencie kliknięcia danego elementu nazwa drinka jest używana w wywołaniu Ajaksa w celu pobrania odpowiedniego przepisu, który następnie służy do zastąpienia treści panelu. Listing 5.6. Połączenie wywołań Ajaksa i efektu accordion var xmlhttp; // Użycie obsługi zdarzeń z biblioteki Prototype Event.observe(window,"load", function() { new Rico.Accordion( $('accordionDiv'), {panelHeight: 200} ); setUp(); }); // Konfigurowanie sposobu wyświetlania strony function setUp() { var divs = document.getElementsByTagName('div'); for (var i = 0; i < divs.length; i++) { if (divs[i].className == 'name') { var drink = new Drink(divs[i]); if (drink.drink.id == 'tea') drink.getRecipe(); } } } // Obiekt Class biblioteki Prototype służy do // utworzenia obiektu Drink Drink = Class.create(); // Metody obiektu Drink są dodawane za pomocą // właściwości prototype Drink.prototype = { // Inicjowanie jest wywoływane automatycznie przy tworzeniu // obiektu za pomocą instrukcji Class.create biblioteki Prototype initialize: function(drink) { this.drink = drink; this._attachBehaviors(); this.state = 'notloaded'; }, _attachBehaviors: function() { Event.observe(this.drink,"click",this.getRecipe.bindAsEventListener(this)); },
156
|
Rozdział 5. Przestrzeń — ostateczna granica
getRecipe : function() { if (this.state=='loaded') return; if (!xmlhttp) xmlhttp = aaGetXmlHttpRequest(); var drink = this.drink.id; var qry = "drink=" + drink; var url = 'recipe5.php?' + qry; xmlhttp.open('GET', url, true); xmlhttp.onreadystatechange = this.printRecipe.bindAsEventListener(this); xmlhttp.send(null); }, printRecipe : function () { if(xmlhttp.readyState == 4 && xmlhttp.status == 200) { // Ustawienie stanu this.state = 'loaded'; // Dostęp do elementu recipe i wyczyszczenie go var recipe = document.getElementById(this.drink.id + "-area"); recipe.innerHTML = ""; // Dodawanie nagłówka var title = xmlhttp.responseXML.getElementsByTagName('title')[0].firstChild. nodeValue; var titleNode = document.createElement('div'); titleNode.className='title'; titleNode.appendChild(document.createTextNode(title)); recipe.appendChild(titleNode); // Dodawanie składników var ingredients = xmlhttp.responseXML.getElementsByTagName('ingredient'); for (var i = 0; i < ingredients.length; i++) { var x = document.createElement('div'); x.className = 'ingredient'; x.appendChild(document.createTextNode(ingredients[i].firstChild.nodeValue)); recipe.appendChild(x); } // Dodawanie instrukcji var instr = xmlhttp.responseXML.getElementsByTagName('instruction')[0]. firstChild.nodeValue; var instrNode = document.createElement('div'); instrNode.className='instruction'; instrNode.appendChild(document.createTextNode(instr)); recipe.appendChild(instrNode); } }};
Metoda Class.create służy do tworzenia klasy Drink. Obiekt Drink ma cztery metody: initialize, _attachBehaviors, getRecipe i printRecipe. Aby zagwarantować, że wszystkie metody mają dostęp do kontekstu obiektu nadrzędnego, dla każdej z nich wywoływana jest metoda bindAsEventListener z parametrem this reprezentującym ten obiekt. W kodzie biblioteki Prototype istotny wiersz to: return __method.call(obiekt, zdarzenie || window.event);
Metoda call języka JavaScript umożliwia współdzielenie obiektów między wieloma metodami; w tym przypadku używany jest kontekst obiektu (dostępny w metodzie poprzez słowo kluczowe this). Pozwala to na dostęp każdej metody obiektu Drink do wszystkich pozostałych, a także do właściwości drink i state. Pierwsza z tych właściwości to nazwa drinka powiązana z obiektem, a druga to wskaźnik informujący o tym, czy przepis został już wczytany. Przestrzeń w poziomie — accordion
|
157
Po wczytaniu strony poszczególne obiekty reprezentujące drinki są tworzone na podstawie tego, czy klasa elementu to name, czy nie. Ponieważ każdy nazwany element ma identyfikator w postaci nazwy drinka, można pobrać tę nazwę i użyć jej w żądaniu kierowanym do usługi sieciowej. Samo żądanie jest zgłaszane w metodzie getRecipe. Po utworzeniu obiektu Drink reprezentującego pierwszego drinka, herbatę, przepis na niego jest natychmiast wczytywany, ponieważ wyświetlający go panel kontrolki accordion jest już widoczny. Rysunek 5.2 przedstawia aplikację z wyświetlonym jednym z paneli z drinkiem.
Rysunek 5.2. Mieszanka łącząca efekt accordion i aplikację wyświetlającą przepisy na drinki
Choć ten przykład jest prosty, jego zastosowanie łatwo można rozszerzyć i wyświetlać na przykład listę osób lub firm i ich adresów, przykładowy kod w witrynie z samouczkiem, lokalizacje sklepów czy informacje o produkcji. To już prawie wszystko na temat kontrolki accordion, umieszczania kodu w bibliotekach i korzystania z różnych bibliotek. Pora wypróbować inne ciekawe sposoby zarządzania przestrzenią stron, włączając w to bardzo popularne strony z zakładkami. Najpierw jednak uwaga dotycząca braków przykładu przedstawionego w tym punkcie: wczytywanie zawartości strony na żądanie nigdy nie będzie w pełni dostępną techniką. Kontrolka accordion może być dostępna, ponieważ możliwe jest otwarcie wszystkich paneli w momencie wczytywania strony. Jednak dane nie są wtedy jeszcze dostępne, a w przypadku
158
|
Rozdział 5. Przestrzeń — ostateczna granica
wyłączonej obsługi skryptów jedyny sposób na pobranie zawartości polega na użyciu aplikacji działającej po stronie serwera lub udostępnieniu odnośników do stron, na których otwarte są poszczególne panele z danymi pobranymi z serwera. To ostatnie rozwiązanie przedstawia rozdział 9.
Strony z zakładkami W czasie pisania tej książki firma Yahoo! udostępniła nową wersję witryny internetowej popularnego serwisu Yahoo! TV. Zmiana projektu nie spotkała się z ciepłym przyjęciem ze strony stałych użytkowników. Jednym z najczęstszych zarzutów dotyczących nowej witryny było to, że używa ona ajaksowego stronicowania, które pokrótce opiszę w dalszej części rozdziału. Ponadto zastosowanie stron z zakładkami przy wyświetlaniu pełnego programu telewizyjnego spotkało się z powszechną krytyką, ponieważ ten efekt Ajaksa działał wolno i był niepotrzebny. Na przykład po kliknięciu danej daty w celu wyświetlenia programu na ten dzień wskaźnik zakładki powoli przechodził do odpowiedniego dnia, a aplikacja wyświetlała listę programów. Nie jest to typowe działanie stron z zakładkami; moim zdaniem niestandardowe zastosowanie animacji zrodziło przynajmniej część krytyki. Strony z zakładkami działają w bardzo określony sposób: dane lub zawartość należy rozbić między różne „strony”, z których każda jest wyświetlana po kliknięciu powiązanej z nią zakładki. Zawartość danej strony może być różna; mogą to być fragmenty długiego formularza, różne rodzaje zawartości portali, program telewizyjny na poszczególne dni, a nawet uporządkowane dane — a etykieta każdej strony powinna odzwierciedlać jej zawartość. Wspólnym elementem wszystkich takich rozwiązań jest wzorzec: kliknięcie zakładki — wyświetlenie strony. W porównaniu z kontrolką accordion ten efekt jest nawet łatwiejszy w implementacji.
Proste spojrzenie na zawartość z zakładkami „Strony z zakładkami” to nieco myląca nazwa, ponieważ takie kontrolki nie muszą mieć rozmiaru strony. Może to być małe okno umieszczone w rogu strony lub, w większej skali, cała strona z zakładkami, przy czym każda podstrona dotyczy innego zagadnienia lub tematu podrzędnego. Zawartość stron z zakładkami może być statyczna lub dynamiczna. Jeśli jest dynamiczna, może ją generować serwer lub żądanie Ajaksa zgłaszane w wyniku wybrania danej zakładki. Zakładki mogą zawierać elementy graficzne, style CSS lub połączenie obu tych technik. Potrzebny jest sposób na określenie, która zakładka powiązana jest z otwartym panelem. W tym celu można na przykład usunąć dolną krawędź zakładki po jej kliknięciu, dzięki czemu zakładka będzie stanowić całość z zawartością strony. W przeciwnym razie użytkownicy mogą nie wiedzieć, która zakładka jest otwarta. W celu zademonstrowania tej techniki wykorzystam formularz użyty we wcześniejszej części rozdziału, jednak zamiast umieszczać jego części jedna nad drugą, rozbiję formularz na strony z zakładkami. Zawartość strony jest stosunkowo prosta. Każda podstrona ma etykietę na zakładce określoną przez klasę name, a do identyfikacji zawartości strony służy klasa elements: Strony z zakładkami
|
159
Imię i nazwisko:
Obiekty na stronie z zakładkami są umieszczone w innym elemencie div — klasy content. Służy to jedynie do nadania stylów i nie ma wpływu na działanie kodu. Ponadto wszystkie strony z zakładkami znajdują się w elemencie „kontenerowym”, ponownie ze względu na style. Zauważyłeś podobieństwa między znacznikami stron z zakładkami i kontrolki accordion? Tak naprawdę są to tylko wybrane sposoby zarządzania zawartością strony. Po poznaniu jednej techniki dość łatwo jest wypróbować inne.
Ponadto poszczególne strony z zakładkami znajdują się wewnątrz elementu div klasy tab. Zamiast używać identyfikatorów do uzyskiwania dostępu do poszczególnych elementów, co miało miejsce w poprzednich przykładach, tu struktura elementów posłuży do utworzenia powiązania między klikniętą zakładką a wyświetlaną stroną. W ten sposób tworzą efekty autorzy wielu bibliotek Ajaksa, ponieważ ułatwia to konfigurację przykładów (nie trzeba martwić się o zastosowanie nieprawidłowych identyfikatorów). Łatwiej jest także dynamicznie wygenerować kod w aplikacji po stronie serwera. Na etapie projektowania widoczne są wszystkie strony, co ułatwia sprawdzenie układu. W tym momencie warto powstrzymać się od dodawania jakiejkolwiek zawartości lub czegokolwiek więcej niż prostego akapitu do czasu zakończenia tworzenia układu i mechanizmów działania. Rysunek 5.3 przedstawia stronę na takim etapie. Jeden element zakładki jest wyróżniony za pomocą narzędzia Inspect rozszerzenia Firebug. Po wczytaniu strony kod JavaScript aplikacji sprawdza wszystkie elementy div klasy tab. Po ich znalezieniu uzyskuje dostęp do elementów podrzędnych poszczególnych węzłów. Pierwszy element podrzędny to sama zakładka, a drugi to powiązana z nią strona. Aplikacja wiąże funkcję obsługi zdarzenia click zakładki ze stroną wyświetlaną w momencie kliknięcia tej zakładki. Można utworzyć ogólny obiekt do przechowywania tablic zakładek i stron oraz bieżącej strony. W czasie wczytywania aplikacji każda para zakładka-panel otrzymuje „indeks” obowiązujący w aplikacji. Ten indeks służy do pobierania każdego elementu do tablicy asocjacyjnej i umieszczania go na odpowiedniej pozycji, co gwarantuje, że na przykład zakładka na pozycji 3 w tablicy zakładek będzie powiązana ze stroną 3 z tablicy stron: function setUp() { var divs = document.getElementsByTagName('div'); var tabCount = 0; for (var i = 0; i < divs.length; i++) { if (divs[i].className == 'name') { tabs.addTab(divs[i]); } else if (divs[i].className == 'content') {
160
|
Rozdział 5. Przestrzeń — ostateczna granica
Rysunek 5.3. Strona z zakładkami w trakcie tworzenia. Jeden element jest wyróżniony za pomocą rozszerzenia Firebug tabs.addPanel(divs[i]); } } tabs.showPanel(0); }
Po dodaniu pary zakładka-panel aplikacja przypisuje do uchwytu zdarzenia click metodę showPanel, która przyjmuje indeks klikniętej zakładki. Kliknięcie zakładki wywołuje kilka zdarzeń: • Kolor tła klikniętej zakładki zmienia się na odcień informujący o tym, że jest aktywna. • Kolor tła poprzednio wybranej zakładki zmienia się na nieaktywny. • Bieżący panel zostaje ukryty. • Aplikacja wyświetla panel powiązany z aktywną zakładką.
Listing 5.7 przedstawia zawartość strony internetowej powiązanej z arkuszem stylów, kodem JavaScript specyficznym dla aplikacji i biblioteką addingajax.js. Listing 5.7. Prosta aplikacja ze stroną z zakładkami Strona z zakładkami
Strony z zakładkami
|
161
162
|
Rozdział 5. Przestrzeń — ostateczna granica
id="opt1" checked="checked" /> id="opt2"
/>
id="opt3"
/>
id="opt4" checked="checked" />
Kod JavaScript do zarządzania stronami z zakładkami nie jest zbyt skomplikowany. Przedstawia go listing 5.8. Po wczytaniu strony kod uzyskuje dostęp do wszystkich zakładek i paneli poprzez nazwy ich klas, a następnie dodaje elementy do odpowiednich tablic: panel i tab. Ponieważ kolejność zakładek i paneli jest taka sama, to choć fizycznie są one rozdzielone na stronie, ich uporządkowanie przy dostępie za pomocą modelu DOM będzie identyczne. Kliknięcie zakładki o określonym indeksie w tablicy tab spowoduje przesłanie tego indeksu do odpowiedniej funkcji, która wyświetli panel o tym indeksie. Listing 5.8. Kod JavaScript strony z zakładkami aaManageEvent(window,"load",setUp); function Tabs () { current=0; tab = new Array(); panels = new Array(); this.addTab = function (tabItem) { var index = tab.length; tab[tab.length] = tabItem; aaManageEvent(tabItem,"click",function() { tabs.showPanel(index);}); }; this.addPanel = function (panel) { panels[panels.length] = panel; }; this.showPanel = function (index) { panels[current].style.display="none"; tab[current].style.backgroundColor="#fff"; tab[index].style.backgroundColor="#ffc"; panels[index].style.display="block"; current = index; }; } tabs = new Tabs(); function setUp() { var divs = document.getElementsByTagName('div'); var tabCount = 0; for (var i = 0; i < divs.length; i++) { if (divs[i].className == 'name') { tabs.addTab(divs[i]); } else if (divs[i].className == 'content') { tabs.addPanel(divs[i]); } } tabs.showPanel(0); }
Arkusz stylów powoduje ułożenie paneli w warstwach jeden na drugim, a po pierwszym wczytaniu strony wszystkie panele są niewidoczne. Kolor tła zakładek zmienia się, aby odzwierciedlić, która z nich jest aktywna: .container { margin: 30px; padding: 10px; } .name
Rysunek 5.4 przedstawia nowy efekt — stronę z zakładkami. Aby zapewnić integrację rozwiązania z serwerem, funkcja obsługi zdarzenia click może także pobierać dane potrzebne do tworzenia zawartości paneli w sposób bardzo zbliżony do techniki z hybrydowego przykładu wykorzystania kontrolki accordion z poprzedniego punktu (listing 5.7). W rozdziale 9. strona z zakładkami służy do sterowania aplikacją typu mashup. Rozdział ten demonstruje także, jak połączyć efekty wizualne i żądania Ajaksa. Aby dodać więcej zakładek do aplikacji, wystarczy dołączyć nowe grupy elementów do strony; kod nie wymaga zmian i można go umieścić w bibliotece w jego obecnej postaci oraz dołączyć do drugiej biblioteki efektów, tabs.js, omówionej w następnym punkcie.
164
|
Rozdział 5. Przestrzeń — ostateczna granica
Rysunek 5.4. Zawartość strony z zakładkami utworzonej za pomocą języka JavaScript
Umieszczanie kodu w bibliotekach po raz wtóry — tworzenie uniwersalnych zakładek Podobnie jak w przypadku kontrolki accordion fragment aplikacji odpowiadający za zakładki jest wystarczająco uniwersalny, aby móc umieścić go w odrębnej bibliotece języka JavaScript. Samego kodu, przedstawionego na listingu 5.9, można użyć w jego obecnej formie w dowolnej aplikacji. Jedyne wymaganie polega na tym, że struktura strony musi odpowiadać tej z listingu 5.7. Wraz z biblioteką należy zachować style CSS i umieścić je w nowym pliku CSS, jednak chwilowo pozostawię rozwiązanie bez zmian w tym zakresie. W skrypcie nie trzeba także zapisywać bezpośrednio kolorów tła. W zamian można po utworzeniu obiektu Tabs przekazać kolory tła aktywnych i nieaktywnych elementów, a następnie zapisać je jako zmienne prywatne. Listing 5.9 przedstawia całą bibliotekę tabs.js. Listing 5.9. Kompletny kod biblioteki tabs.js function Tabs (active,inactive) { current=0; tab = new Array(); panels = new Array(); activeColor = active; inactiveColor = inactive; this.addTab = function (tabItem) { var index = tab.length; tab[index] = tabItem; aaManageEvent(tabItem,"click",function() { tabs.showPanel(index);}); }; this.addPanel = function (panel) { panels[panels.length] = panel; }; this.showPanel = function (index) { panels[current].style.display='none'; tab[current].style.backgroundColor=inactiveColor; tab[index].style.backgroundColor=activeColor; panels[index].style.display='block'; current = index; }; }
Strony z zakładkami
|
165
function setUpTabs() { var divs = document.getElementsByTagName('div'); var tabCount = 0; for (var i = 0; i < divs.length; i++) { if (divs[i].className == 'name') { tabs.addTab(divs[i]); } else if (divs[i].className == 'content') { tabs.addPanel(divs[i]); } } tabs.showPanel(0); }
W języku JavaScript prywatne zmienne składowe to właściwości dostępne jedynie dla metod danego obiektu, a nie dla zewnętrznego kodu. Obecność lub brak słowa kluczowego this określa, czy zmienna jest publiczna czy prywatna.
Aby użyć biblioteki Tabs, należy dodać element script wskazujący tę nową bibliotekę, przygotować odpowiednie znaczniki na stronie, używając wcześniej przedstawionej struktury, a następnie utworzyć obiekt Tabs: var tabs = new Tabs("#ccf","#fff");
I to już wszystko. Można dostosować kolory, określić styl czcionki, zmienić rozmiar zawartości bloków i tak dalej, a także utworzyć dowolną liczbę zakładek. Aby używać biblioteki Tabs poprzez bibliotekę Prototype, można zmodyfikować kod i użyć instrukcji Class.create, aby utworzyć obiekt, oraz wykorzystać funkcję bindAsEventListener w celu zagwarantowania, że kontekst obiektu zostanie przekazany do każdego zdarzenia. W celu zastosowania tego efektu można też użyć gotowej biblioteki, takiej jak Yahoo! UI, zamiast tworzyć własną.
Używanie mechanizmu TabView biblioteki YUI Nie przedstawiłam jeszcze działania biblioteki Yahoo! UI, a mechanizm TabView wydaje się dobrym wprowadzeniem jako jeden z wielu efektów wyższego poziomu. Na stronie http://developer.yahoo.com/yui można pobrać biblioteki YUI, zobaczyć przykłady i zapoznać się z dokumentacją.
Podobnie jak inne biblioteki TabView YUI używa elementów listy (li) dla zakładek i zwykle elementów div do przechowywania treści. Jedną z zalet takiego rozwiązania jest to, że nie trzeba pobierać wszystkich elementów div strony, aby wykryć zakładki:
W dalszej części kodu znajduje się element div z poszczególnymi panelami, a każdemu z nich odpowiada odrębny element div. Ważne jest, aby zachować zgodność paneli z zakładkami, ponieważ to rozwiązanie nie używa identyfikatorów. Elementy są fizycznie oddzielone od siebie i nie mają wspólnego unikalnego elementu nadrzędnego. 166
|
Rozdział 5. Przestrzeń — ostateczna granica
Specjalne klasy wyróżniają kontener ogólny (yui-navset), listę do nawigacji (yui-nav) i elementy (yui-content). Ponadto element kontenera ogólnego ma identyfikator. W czasie tworzenia strony z zakładkami ten identyfikator jest przekazywany do wywołania funkcji w celu utworzenia efektu: var myTabs = new YAHOO.widget.TabView("tabview");
Listing 5.10 przedstawia całą stronę. Jak widać, także w tym przypadku zastosowanie gotowego efektu wyższego poziomu pozwala znacznie zmniejszyć ilość kodu. Biblioteka YUI ma także stosunkowo dobrą dokumentację, włączając w to opis tego, gdzie dodać funkcjonalność w celu zintegrowania efektu z żądaniami kierowanymi do usług sieciowych i innymi operacjami. Listing 5.10. Strona z zakładkami oparta na TabView YUI Strona z zakładkami