AJAX - ZGM Szczecinek
Transkrypt
AJAX - ZGM Szczecinek
www.phpsolmag.org/pl Artykuł w formie elektronicznej pochodzi z magazynu PHP Solutions. Wszelkie prawa zastrzeżone. Rozpowszechnianie artykułu bez zgody Software Wydawnictwo Sp. z o.o. Zabronione. Software Wydawnictwo Sp. z o.o., ul. Piaskowa 3, 01-067 Warszawa, POLSKA. Kontakt: [email protected] Techniki AJAX – wyjątkowo interaktywne i wydajne aplikacje WWW Joshua Eichorn, Werner M. Krauß Aplikacje tworzone w PHP pozwalają osiągnąć bardzo wiele przy ograniczonym oprogramowaniu klienckim, co oznacza łatwe wdrażanie i aktualizacje, a tym samym szybkie efekty pracy. Architektura ta ma też dotkliwe wady, jak opóźnienia między wyświetlaniem kolejnych stron lub brak możliwości pobierania nowych danych bez wysyłania formularza. Na szczęście istnieje mechanizm AJAX. A W SIECI 1. http://sourceforge.net/ projects/jpspan/ – strona główna projektu JPSpan 2. http://pear.php.net/package/ HTML_AJAX/ – strona główna pakietu HTML_AJAX 3. http://www.google.com/ webhp?complete=1&hl=en – Google Suggest 4. http://www.ajaxpatterns.org/ – serwis poświęcony wzorcom aplikacji AJAX 5. http://www.ajaxpatterns.org/ Suggestion – opis wzorca w stylu Google Suggest 6. http://blog.joshuaeichorn. com/archives/category/php/ ajax – blog Joshuy Eichorna poświęcony AJAX-owi 48 JAX pozwala stworzyć dodatkowy kanał komunikacji między klientem a serwerem PHP, a tym samym wysyłać i odbierać dane bez przeładowywania strony. Otwiera to zupełnie nowe możliwości, a w połączeniu z operacjami na modelu DOM z poziomu JavaScriptu, oznacza nadejście ery bogato wyposażonych, interaktywnych aplikacji PHP, wolnych od irytującego klikania i czekania. W tym artykule przedstawimy praktyczne wprowadzenie do techniki AJAX na przykładzie dwóch bibliotek PHP i niewielkiej aplikacji o działaniu podobnym do Google Suggest. Czym jest AJAX? AJAX (skrót od Asynchronous JavaScript And XML) jest nazwą nowej metody programowania, łączącej kilka różnych technik: (X)HTML i CSS do tworzenia interfejsu użytkownika, DOM (Document Object Model) do obsługi elementów dynamicznych i interakcji oraz XMLHttpRequest do www.phpsolmag.org asynchronicznej wymiany danych. Techniki te są łączone w jedną całość za pomocą JavaScriptu, odpowiedzialnego za logikę aplikacji i dynamiczną aktualizację interfejsu użytkownika stosownie do potrzeb. Pomimo XML w nazwie, AJAX niekoniecznie wymaga używania formatu XML do wymiany danych. Poza XML-em obsługiwane są między innymi zwykły tekst, sformatowany HTML (dodawany do bieżącej strony poprzez właściwość innerHTML) oraz format JSON (JavaScript Powinieneś wiedzieć... Powinieneś się dobrze orientować w zasadach programowania obiektowego w PHP4 lub PHP5. Przyda się też pewna znajomość JavaScriptu. Obiecujemy... Po przeczytaniu artykułu będziesz znał zasadę działania i stosowania techniki AJAX oraz śledzenia kodu. Pokażemy też możliwości bibliotek implementujących tę technikę. PHP Solutions Nr 1/2006 AJAX ������������������������ ���� Object Notation), który można przepuścić przez eval() w celu uzyskania typów JavaScript. Można też korzystać z dowolnego innego formatu danych dającego się obsłużyć w JavaScripcie i PHP. AJAX-a można najprościej zdefiniować jako metodę wykorzystania JavaScriptu do komunikacji z serwerem niezależnie od tradycyjnych żądań POST i GET. Strona techniczna jest tu jednak mniej istotna – najważniejsze są zupełnie nowe możliwości tworzenia aplikacji internetowych. Podstawą pracy AJAX-a jest obiekt XMLHttpRequest, stanowiący standardowy element wielu przeglądarek. Jeśli postanowisz dodać obsługę AJAX-a do swojej aplikacji za pomocą biblioteki, to nie musisz wiele wiedzieć o samym XMLHttpRequest, gdyż wszystkim zajmie się biblioteka. Fizyczna implementacja obiektu XMLHttpRequest zależy od konkretnej przeglądarki – w Internet Explorerze jest to wbudowany obiekt ActiveX, natomiast w Firefoksie, Safari i większości innych przeglądarek jest on wewnętrznym obiektem JavaScriptu. XMLHttpRequest udostępnia proste API, pozwalające wysyłać do serwera żądania HTTP metodami GET i POST. Dla serwera są to zwyczajne żądania przeglądarki, zawierające nawet wszystkie pliki cookie dla bieżącej domeny oraz autoryzację HTTP (jeśli oczywiście jest włączona). Od strony JavaScriptu, XMLHttpRequest daje dostęp do treści i nagłówków podczas wysyłania i odbioru żądań. Możliwość ta jest często używana do poinformowania serwera, że żądanie nie zostało zgłoszo- ������������������ �������������� ������������������������ ������������ �������������� ������������������������ ������������� ���� ������������������ ������ ���� ������������������ ������ ������������������� ��������������� Rysunek 2. Przepływ danych w aplikacji AJAX ne bezpośrednio przez użytkownika, lecz poprzez XMLHttpRequest. Odebraną treść można traktować jako zwykły tekst, ale w przypadku treści typu text/xml można też stworzyć obiekt XML DOM. Obsługa modelu DOM przyczyniła się do popularności XML-a jako formatu wymiany danych między klientem a serwerem, natomiast utrzymanie obsługi zwykłego tekstu pozwala korzystać z dowolnego formatu dającego się przetworzyć na poziomie JavaScriptu. Dlaczego AJAX? Najważniejszym argumentem przemawiającym za korzystaniem z AJAX-a jest ������ ���� ��� ��������������������� ���� �������������������� ������������������� ����������������������� Rysunek 1. Przepływ danych w tradycyjnej aplikacji internetowej PHP Solutions Nr 1/2006 ��� ������������������� ��������������� ����������������� ������������������ ��������������������� ������ ���������������� ���������������� ������������������ Techniki www.phpsolmag.org możliwość uzyskania znacznie wyższego poziomu interaktywności w aplikacjach internetowych. Reakcje programu na działania użytkownika są dużo szybsze, bez nużącego klikania i czekania, przez co obsługa całego programu znacznie bardziej przypomina pracę z tradycyjną aplikacją stacjonarną. Rysunek 1 przedstawia przepływ danych w typowej aplikacji internetowej. Użytkownik wypełnia formularz i wysyła go na serwer WWW, który przetwarza formularz i odsyła dane do czekającego użytkownika. Zwracanym wynikiem jest pełna strona HTML, którą przeglądarka klienta musi załadować w całości (treść i strukturę). Marnuje się w ten sposób czas i pasmo, gdyż kod strony wynikowej najczęściej niewiele się różni od kodu poprzedniej strony. Aplikacja AJAX wysyła do serwera wyłącznie żądania pobierające nowe, potrzebne dane, a odpowiedź serwera jest przetwarzana przez JavaScript po stronie klienta. Dzięki wprowadzeniu tej dodatkowej warstwy JavaScriptu, przetwarzanie danych nie spowalnia działania interfejsu użytkownika. Cała aplikacja działa znacznie szybciej, gdyż między serwerem, a klientem przesyłanych jest nieporównanie mniej danych, a spora część przetwarzania odbywa się po stronie klienta (Rysunek 2). Praktycznie rzecz ujmując, stworzenie aplikacji AJAX wymaga zatem dwóch 49 Techniki elementów: odpowiednich skryptów po stronie klienta i specjalnego kanału komunikacji z serwerem. Zalety techniki AJAX AJAX ma wiele zalet, z których najbardziej zauważalną jest znaczące rozszerzenie zakresu możliwości interfejsu użytkownika. Jednak samo w sobie to nie wystarczy – w końcu istnieje też wiele innych technologii o zbliżonych możliwościach. O wyjątkowości AJAX-a stanowi przede wszystkim to, że bazuje on na uznanych standardach, więc w przeciwieństwie do innych narzędzi do tworzenia interaktywnych aplikacji internetowych (na przykład Flasha) można go z łatwością wpasować w istniejące procesy deweloperskie. Można więc dalej korzystać ze swojego ulubionego edytora czy środowiska programistycznego, bez konieczności poznawania nowych narzędzi. Istnieje też wiele darmowych zestawów narzędzi Open Source ułatwiających tworzenie i rozwijanie aplikacji AJAX, a przy okazji redukujących objętość kodu JavaScriptu, jaki trzeba wpisywać ręcznie. W dalszej części artykułu zobaczymy, jak dołączać obsługę AJAX-a do własnych aplikacji z pomocą popularnych bibliotek. Wady AJAX-a Opisywana metoda interakcji z klientem ma też swoje wady. Nie można przewidzieć, z jakiej przeglądarki korzysta użytkownik, więc aplikacja może nie działać na niekompatybilnych przeglądarkach lub przy wyłączonej obsłudze JavaScriptu. Oznacza to, że dobrą praktyką jest uwzględnienie awaryjnej metody obsługi, na przykład poprzez stworzenie bazowej aplikacji z wykorzystaniem tradycyjnych technik, a następnie rozbudowanie jej o opcjonalne usprawnienia używające AJAX-a. Trzeba też pamiętać, że aplikacje z AJAX-em nie będą działać w Internet JPSpan i PEAR Wersja biblioteki JPSpan dla repozytorium PEAR jest dostępna w serwisie http://www.pearified.com. Do instalacji będzie potrzebny PEAR w nowej wersji 1.4, obsługujący inne kanały niż tylko http://pear.php.net. Poleceniem pear channel-discover pearified.com należy dodać kanał do repozytorium, po czym można już zainstalować JPSpan poleceniem pear install pearified/ JavaScript_JPSpan. 50 Explorerze z wyłączoną obsługą ActiveX, co często dotyczy na przykład kafejek internetowych. Może się także zdarzyć, że aplikacja będzie działać nieco inaczej w różnych przeglądarkach i na różnych platformach, choć to samo dotyczy tworzenia tradycyjnych aplikacji internetowych. AJAX oferuje spore możliwości interakcji, ale do wielu zadań po prostu się nie nadaje, na przykład do dynamicznego rysowania elementów czy obsługi animacji – w takich sytuacjach lepiej korzystać z Flasha. Warto w tym miejscu zaznaczyć, że pomimo teoretycznej możliwości połączenia zalet AJAX-a i Flasha w ramach jednej aplikacji, złożoność takiego rozwiązania jest na tyle duża, że lepiej używać tych technik osobno. Wykorzystanie bibliotek Istnieje wiele bibliotek narzędziowych mających na celu ułatwienie integracji JavaScriptu i PHP. Wszystkie uwzględniają jakąś metodę przesyłania danych, ale większość oferuje dodatkowe możliwości, Listing 1. Serwer JPSpan <?php session_start(); // Klasa powitania class HelloWorld { function HelloWorld() { if (!isset($_SESSION['strings'])) { $_SESSION['strings'] = array('Hello','World','Hello World'); } } function addString($string) { $_SESSION['strings'][] = $string; return $this->stringCount(); } function randomString() { $index = rand(0,count($_SESSION['strings'])-1); return $_SESSION['strings'][$index]; } function stringCount() { return count($_SESSION['strings']); } } // Ustawienie stałej JPSPAN require_once 'jpspan-0.4.3/JPSpan.php'; // Załadowanie serwera PostOffice require_once JPSPAN . 'Server/PostOffice.php'; // Utworzenie serwera PostOffice $S = & new JPSpan_Server_PostOffice(); // Rejestracja klasy w serwerze $S->addHandler(new HelloWorld()); // Obsługa wyświetlania JavaScriptu po dodaniu // ciągu ?client do URL-a serwera if (isset($_SERVER['QUERY_STRING']) && strcasecmp($_SERVER['QUERY_STRING'], 'client')==0) { // Wyłączenie kompresji wynikowego JavaScriptu // (m.in. usuwania białych znaków) z powodu // problemów wydajnościowych define('JPSPAN_INCLUDE_COMPRESS',false); // Wyświetlenie klienckiego JavaScriptu $S->displayClient(); }else { // Początek faktycznej obsługi żądań // Dołączenie obsługi błędów // błędy, ostrzeżenia i komunikaty serializowane do JS // Obsługiwanie żądań $S->serve(); } ?> www.phpsolmag.org PHP Solutions Nr 1/2006 AJAX od bezpośredniego odwzorowania metod klas PHP na pośrednika JavaScriptu, po środowisko tworzenia elementów interfejsu użytkownika. Przyjrzyjmy się bliżej dwóm popularnym pakietom: bibliotekom JPSpan i HTML_AJAX. JPSpan Najpierw zajmiemy się pakietem JPSpan – jedną z bardziej dojrzałych bibliotek AJAX dla PHP, dostępną od listopada 2004 r. Podstawową funkcją biblioteki jest niezależna od przeglądarki obsługa mechanizmu AJAX bazująca na XMLHttpRequest, z możliwością wyboru pracy synchronicznej lub asynchronicznej. Dostępne jest wspólne, obiektowe API dla JavaScriptu i PHP. JPSpan obsługuje też wiele innych funkcji, na przykład przezroczyste odwzorowania obiektów z bardzo dobrą serializacją danych, po- zwalającą odwzorowywać tablice PHP na obiekty JavaScriptu. Strona z serwera jest dołączana do wynikowych stron HTML ze znacznikiem client, generując pośrednie klasy JavaScript o takim samym API, jak klasy PHP. Ze względu na ograniczenia PHP4, wszystkie nazwy klas i metod są zapisywane małymi literami. Domyślny serwer JPSpan nosi nazwę JPSpan_ Server_PostOffice i może służyć do odwzorowywania na JavaScript zarówno całych klas, jak i ich części. Korzystając z serwera w dużym serwisie można rozważyć dodanie znacznika class, co pozwoli ograniczyć liczbę klas dołączanych i rejestrowanych na serwerze, a tym samym zmniejszy koszt przetwarzania. Osobiście nie doświadczyłem jednak żadnych problemów wydajnościowych nawet przy pięciu stale zarejestrowanych klasach integracyjnych. Listing 2. Klient JPSpan <html> <head> <title>Hello World w JPSpan</title> <script type='text/javascript' src='jpspan_server.php?client'></script> <script> // Klasa JavaScript zawierająca metody zwrotne var hwCallback = { randomstring: function(result) { document.getElementById('canvas').innerHTML += '<p>'+result+'</p>'; }, stringcount: function(result) { document.getElementById('count').innerHTML = result; }, addstring: function(result) { document.getElementById('count').innerHTML = result; } } // Utworzenie obiektu zdalnego. Jego nazwa jest odwzorowana małymi literami, // gdyż w nazwach klas i funkcji PHP4 wielkość liter nie jest rozpoznawana. // Rejestrując każdą klasę na serwerze można przywrócić rozróżnianie // wielkości liter. var remoteHW = new helloworld(hwCallback); function do_addString() { remoteHW.addstring(document.getElementById('string').value); document.getElementById('string').value = ''; } </script> </head> <body onLoad="remoteHW.stringcount()"> <input type="button" name="check" value="Pokaż losowy napis" onclick= "remoteHW.randomstring(); return false;"> <div>Liczba losowych napisów: <span id="count"></span></div> <div id="canvas" style="border: solid 1px black; margin: 1em; padding: 1em;"></div> <div> Podaj nowy napis: <input type="text" name="string" id="string" size="20"> <input type="button" name="check" value="Dodaj nowy napis" onclick= "do_addString(); return false;"> </div> </body> </html> PHP Solutions Nr 1/2006 www.phpsolmag.org Techniki HTML_AJAX a nazwy klas Nazwy klas zwracane przez PHP4 są zawsze zapisane małymi literami. Jeśli koniecznie chcesz zachować rozróżnienie wielkości liter lub potrzebujesz zgodności między PHP4 i PHP5, musisz skorzystać z dodatkowych parametrów metody registerClass(), określających nazwę klasy rejestrowanej w JavaScripcie oraz tablicę eksportowanych metod: $server = new HTML_AJAX_Server(); $hello = new HelloWorld(); $methods = array('foo','bar'); $server->registerClass($hello, 'Example', $methods); § Wywołania polegają na utworzeniu instancji odpowiedniej klasy JavaScriptu, a następnie wywoływaniu jej metod. W chwili utworzenia instancji w trybie asynchronicznym, określana jest klasa zwrotna, po czym wyniki są przesyłane jej metodom o takich samych nazwach, jak metody pierwotnie wywoływane. JPSpan dodatkowo obsługuje złożone typy danych, w tym wielowymiarowe tablice i obiekty, jak również serializację i przekazywanie błędów PHP do JavaScriptu z możliwością konfiguracji obsługi błędów po stronie klienta. Strona serwera Przyjrzyjmy się działaniu JPSpan nieco bliżej na prostym przykładzie typu Hello World, wyświetlającym losowy napis w reakcji na kliknięcie i pozwalającym dodawać nowe napisy. Zaczynamy od utworzenia klasy helloworld, zawierającej proste metody PHP do obsługi dodawania napisów do tablicy sesyjnej, zwracania długości tablicy i wyświetlania losowego napisu z tablicy. Są to odpowiednio metody addString(), stringCount() i randomString(), a ich kod przedstawia Listing 1. Praca z klasami JPSpan nie różni się niczym od obsługi zwykłych klas. Trzeba tylko pamiętać, że klasa ta jest odtwarzana przy każdym wywołaniu ze strony JavaScriptu, więc utrzymywanie danych składowych między wywołaniami wymaga pamiętania instancji klasy w ramach sesji. Musimy jeszcze dołączyć JPSpan i odpowiedni plik serwera PostOffice, po czym możemy stworzyć nową instancję serwera i zarejestrować w niej naszą klasę wywołując $S->addHandler(new 51 Techniki HelloWorld()). Pozostaje jeszcze tylko określić, czy chcemy wysłać kliencki kod JavaScript, czy też obsługiwać żądania. Jak widać na Listingu 1, obiektowe API JPSpan bardzo ułatwia przygotowania po stronie serwera. Strona klienta Teraz zajmiemy się stroną kliencką naszej prostej aplikacji – Listing 2 przedstawia kod klienta. Od razu zwraca uwagę wyraźne rozdzielenie kodu HTML i PHP podczas pracy z JPSpan. Wystarczy tylko umieścić w ramach strony HTML poniższy, automatycznie generowany kod, odpowiedzialny za faktyczne połączenie HTML i PHP: <script type='text/javascript' src='jpspan_server.php?client'> </script> Przy odrobinie pracy nagłówkami pozwala to wygodnie obsłużyć składowanie znanych informacji po stronie klienta, co bardzo przydaje się na przykład przy dodawaniu do istniejącego serwisu pola autouzupełniania. Następnie tworzymy klasę JavaScriptu o nazwie hwCallback, zawierającą metody zwrotne zastępujące treść odpowiednich elementów <div> wartościami podanymi przez serwer z wykorzystaniem właściwości innerHTML. Pozostaje już tylko utworzyć zdalny obiekt: var remoteHW=new helloworld(hwCallback); Klasa helloworld jest wyeksportowaną klasą PHP, którą wcześniej utworzyliśmy po stronie serwera. Nazwa klasy zawiera wyłącznie małe litery, gdyż PHP4 nie rozróżnia wielkości liter w nazwach klas i funkcji. Reszta kodu z Listingu 2. to już tylko dodanie formularza HTML z odpowiednimi metodami obsługi – i już możemy się pobawić naszą pierwszą aplikacją stworzoną w technologii AJAX. HTML_AJAX Biblioteka HTML_AJAX daje znacznie większe możliwości niż JPSpan, ale dla uproszczenia przykładu skorzystamy z podobnej konfiguracji, co w przypadku poprzedniego przykładu: zewnętrzna strona serwera generuje kod pośredni JavaScriptu, który jest dołączany i faktycznie wykonywany na stronie HTML. HTML_ AJAX potrafi też generować cały kod pośrednika i serwera w jednym skrypcie, ale 52 Listing 3. HTML_AJAX może posłużyć do pobierania treści z innej strony na tym samym serwerze <html> <head> <script type='text/javascript' src="server.php?client=main"></script> <script type='text/javascript' src="server.php?client=dispatcher"></script> <script type='text/javascript' src="server.php?client=HttpClient"></script> <script type='text/javascript' src="server.php?client=Request"></script> <script type='text/javascript' src="server.php?client=json"></script> </head> <body> <script type="text/javascript"> function clearTarget() { document.getElementById('target').innerHTML = 'clear'; } // Operacja 'grab' jest najprostszym zastosowaniem HTML_AJAX, polegającym na // wysłaniu żądania do strony i pobraniu wyników. Można jej używać w trybie // synchronicznym lub asynchronicznym (z wywołaniem zwrotnym). var url = 'README'; function grabSync() { document.getElementById('target').innerHTML = HTML_AJAX.grab(url); } function grabAsync() { HTML_AJAX.grab(url,grabCallback); } function grabCallback(result) { document.getElementById('target').innerHTML = result; } // Operacja 'replace' może działać albo na adresie (tak jak grab), albo na // zdalnej metodzie. W tym drugim przypadku trzeba ustawić defaultServerUrl na adres // eksportujący wywoływaną metodę. Obecnie replace używa wyłącznie synchronicznych // wywołań AJAX - wywołania asynchroniczne mogą się pojawić w przyszłości. function replaceUrl() { HTML_AJAX.replace('target',url); } </script> <ul> <li><a href="javascript:clearTarget()">Czyszczenie pola docelowego</a></li> <li><a href="javascript:grabSync()">Przykład pobrania synchronicznego</a></li> <li><a href="javascript:grabAsync()">Przykład pobrania asynchronicznego </a></li> <li><a href="javascript:replaceUrl()">Zastąpienie zawartości treścią pobraną spod wskazanego adresu</a></li> </ul> <div style="white-space: pre; padding: 1em; margin: 1em; width: 600px; height: 300px; border: solid 2px black; overflow: auto;" id="target">Pole docelowe</div> </body> </html> Listing 4. Klasa implementująca serwer podpowiedzi (plik suggest.class.php) class suggest { function suggest() { require_once 'pear_array.php'; $this->strings = $aPear; } function getString($input='') { if ($input == '') return ''; $input = strtolower($input); $suggestStrings=array(); foreach ($this->strings as $string) { if (strpos(strtolower($string),$input) === 0) { $suggestStrings[] = $string; } } return $suggestStrings; } } www.phpsolmag.org PHP Solutions Nr 1/2006 AJAX Techniki in, Dispatcher, HttpClient, Request klienckie, zawierające w ciągu zapytania. Zamiast all można też podać jedną z części składowych klienta. Obecnie obsługiwanych jest pięć części ( Ma- Pierwsze dwa rodzaje żądań można łączyć w jednym żądaniu, lecz trzeba pamiętać, że wiąże się to z pewnym kompromisem: mniej żądań to mniej połączeń z serwerem, ale z drugiej strony generowane namiastki zmieniają się częściej od danych klienckich, co może negatywnie wpłynąć na efektywność lokalnego składowania kodu. Ostrożnie należy też korzystać z żądań stub=all, gdyż namiastka dla, na przykład, dziesięciu klas może już być spora. W kolejnej wersji biblioteki HTML_AJAX pojawi się możliwość podawania wielu klas w ramach jednego żądania namiastki w postaci listy rozdzielanej przecinkami, a więc stub=test,test2. E A Rysunek 3. Serwer podpowiedzi w akcji nie polecałbym tej metody, gdyż tracimy wtedy możliwość lokalnego składowania wcześniej wygenerowanego kodu JavaScriptu. Instalacja pakietu HTML_AJAX jest bardzo prosta – wystarczy wykonać polecenie pear install HTML_AJAX-alpha. Jeśli na serwerze nie masz narzędzia PEAR, możesz po prostu pobrać pakiet http://pear.php.net/package/HTML_AJAX, rozpakować go i ręcznie umieścić w wybranym katalogu, wymienionym oczywiście w ramach parametru include_path w php.ini. R PHP Solutions Nr 1/2006 Serwer Strona HTML_AJAX działająca na serwerze jest bardzo prosta – jej działanie polega na utworzeniu instancji serwera HTML_AJAX_ Server, zarejestrowaniu wszystkich eksportowanych klas (zwanych tu stubs, czyli namiastkami) i obsługiwaniu nadchodzących żądań. Istnieją trzy możliwe rodzaje żądań: i JSON), a w przyszłości zostanie dodana obsługa dodatkowych, opcjonalnych części. Żądanie generowanej namiastki, zawierające ?stub=nazwaklasy w ciągu zapytania (można też podać wartość all). Żądanie AJAX, zawierające w ciągu zapytania ?c=nazwaklasy&m=nazwame tody. Żądanie ?client=all K L A www.phpsolmag.org M 53 Techniki Tryb synchroniczny i asynchroniczny Biblioteki JPSpan i HTML_AJAX obsługują pracę zarówno w trybie asynchronicznym (z wywołaniami zwrotnymi), jak i synchronicznym (z bezpośrednim zwracaniem wartości). Ogólnie lepiej jest stosować operacje asynchroniczne, gdyż wywołania synchroniczne mogą wstrzymywać pracę interfejsu użytkownika w oczekiwaniu na odpowiedź drugiej strony. Oczywiście niekiedy jest to zachowanie pożądane, ale przesyłając większe ilości danych trzeba wtedy zawsze pamiętać o wyświetleniu komunikatu typu proszę czekać. Wywołania synchroniczne są znacznie łatwiejsze do oprogramowania, ale mimo to lepiej oprzeć się pokusie bezkrytycznego ich stosowania. Żądania AJAX w sieci lokalnej najczęściej trwają nie dłużej niż 50 ms, ale w przypadku przesyłania danych przez Internet czas ten najczęściej wzrasta do ponad 250 ms. Oznacza to, że użytkownik nie będzie w stanie skorzystać z żadnego elementu strony czy nawet przełączyć się na inną zakładkę przeglądarki przez ćwierć, a często i pół sekundy. Łatwa aktualizacja treści bez AJAX-a HTML_AJAX pozwala też korzystać z podstawowych możliwości AJAX-a wyłącznie po stronie klienckiego JavaScriptu, dzięki czemu można bardzo szybko dodać do strony proste elementy używające AJAX-a lub włączyć HTML_AJAX do istniejącego środowiska. Typowym zastosowaniem jest aktualizacja zawartości elementu HTML za pomocą treści wygenerowanej przez inną stronę PHP, co daje elastyczność ramek <iframe> bez ich wad (patrz Listing 3). Po dołączeniu do strony niezbędnego kodu JavaScriptu można pobierać treść ze wskazanego adresu na serwerze w trybie synchronicznym za pomocą HTML_ AJAX.grab(url) lub w trybie asynchronicznym za pomocą HTML_AJAX.grab(url,grab Callback), gdzie argument grabCallback wskazuje funkcję zwrotną automatycznie wywoływaną przez HTML_AJAX po pobraniu treści. Można też wywołać HTML_A JAX.replace('target',url), by zastąpić zawartość elementu o identyfikatorze podanym jako target treścią pobraną z adresu url. Ze względów bezpieczeństwa można w ten sposób pobierać treści wyłącznie z adresów na tym samym serwerze. Nie jest to jednak ograniczenie biblioteki HTML_AJAX, lecz ograniczenie przeglądarki, mające na celu zapobieganie atakom cross site scripting (XSS). Przykład w stylu Google Suggest z HTML_AJAX Pora na nieco bardziej zaawansowany przykład: pole podpowiedzi podobne do mechanizmu Google Suggest (http:// www.google.com/webhp?complete=1&hl= en), ale służące do wyszukiwania pakietów PEAR (patrz też Ramka Wzorzec AJAX Suggest). HTML_AJAX pozwala zarządzać interakcją klienta z serwerem w kilku prostych wierszach kodu. Jak widać na Listingu 4., klasa obsługująca stronę PHP jest dość prosta. Dla potrzeb przykładu korzystamy z tablicy zawierającej możliwe hasła wyszukiwania, choć w rzeczywistej aplikacji byłyby one prawdopodobnie pobierane z bazy danych. Lista wyszukiwania wymaga tylko jednej metody getString(), która porównuje przekazany ciąg znaków z kolejnymi pozycjami tablicy. Pasujące elementy są następnie kopiowane do tablicy wyników, która jest ostatecznie zwracana. Teraz uruchamiamy serwer usługi (Listing 5). W tym przykładzie skorzystamy z klasy AutoServer, która rozszerza podstawową klasę serwera i dodaje metodę inicjalizacyjną dla każdej klasy. Pozwala to zarządzać eksportem kilku klas PHP za pomocą jednego serwera – wystarczy ustawić wartość zmiennej $initMethods na true i nadać metodom inicjalizacyjnym nazwy w postaci initNazwaKlasy, co dla naszej klasy oznacza utworzenie metody initSuggest(). Wykorzystanie klasy AutoServer w tak prostym przykładzie to oczywiście strzelanie z armaty do muchy, ale pokazuje ciekawe możliwości biblioteki HTML_AJAX, które mogą się bardzo przydać w większych projektach. I to by było na tyle po stronie PHP. Jeśli nasza metoda działa poprawnie i nie generuje żadnych błędów, to nie musimy jej więcej zmieniać i możemy się zająć implementacją po stronie klienta. Rzut oka na Listing 6 pokazuje, że zaczynamy od dołączenia JavaScriptu dla serwera: <script type='text/javascript' src='auto_server.php?client= all&stub=suggest'></script> Wzorzec AJAX Suggest Podpowiadanie użytkownikom możliwych sposobów uzupełnienia tekstu wprowadzanego w polu tekstowym uatrakcyjnia stronę i ułatwia korzystanie z niej – wystarczy zaproponować kilka słów lub wyrażeń, które mogą pasować do danych wprowadzanych przez użytkownika. Do implementacji mechanizmu uzupełniania służy najczęściej połączenie pola tekstowego z listą rozwijaną wraz z synchronizacją tych elementów. Użytkownik może podać dowolny tekst, a bieżąca pozycja na liście będzie odpowiadać dotychczas wprowadzonemu ciągowi. Możliwe jest również wybranie elementu z listy, co spowoduje zastąpienie zawartości pola tekstowego wybranym hasłem. Implementacja tego wzorca wiąże się z reguły z wykorzystaniem zwykłego pola tekstowego i stworzeniem niewidocznej z początku warstwy (elementu <div>), w której będą umieszczane kolejne podpowiedzi. Do pola tekstowego trzeba dołączyć funkcję obsługi zdarzenia, kontrolującą zawartość pola w celu zapewnienia poprawnego wyświetlania pasujących podpowiedzi na liście. Nie będziemy oczywiście wysyłać żądania po każdym naciśnięciu klawisza – lepiej skorzystać z techniki dławienia zgłoszeń. W tym przypadku przeglądarka sprawdza co (na przykład) 350 milisekund, czy zawartość pola uległa zmianie – jeśli tak, to do serwera wysyłane jest odpowiednie żądanie. Pozwala to ograniczyć liczbę żądań (i tym samym zaoszczędzić nieco pasma), a przy okazji nie będzie przeszkadzać szybko piszącemu użytkownikowi. Serwer odpowiada na żądanie wysyłając uporządkowaną listę pasujących podpowiedzi, którą po stronie klienta odbiera funkcja zwrotna. Funkcja ta wprowadza w modelu dokumentu odpowiednie zmiany, by użytkownik mógł przejrzeć nowe podpowiedzi i ewentualnie wybrać jedną z nich. Z każdą pozycją listy skojarzona jest funkcja obsługi kliknięcia, odpowiadająca za aktualizację pola tekstowego treścią wybranej przez użytkownika pozycji. 54 www.phpsolmag.org Następnie tworzymy kod obsługi żądania w postaci metody do_suggest() oraz funkcję zwrotną do wyświetlania wyników, a na koniec tworzymy nową instancję zdalnej wyszukiwarki AJAX. Reszta kodu to po prostu formularz z jednym polem tekstowym i elementem <div> do wyświetlania wyników. Dodanie do pola tekstowego obsługi zdarzenia onkeyup="do_suggest(); return false;" powoduje wywołanie funkcji do_suggest() po każdym zwolnieniu klawisza (zdarzenie onkeypress byłoby obsługiwane zbyt wcześnie). Jak to działa? Każda zmiana wartości pola tekstowego powoduje wywołanie funkcji do_suggest(), która z kolei wywołuje metodę remoteSu ggest.getstring() javascriptowej klasy PHP Solutions Nr 1/2006 AJAX Listing 5. Serwer podpowiedzi w HTML_AJAX (plik auto_server.php) session_start(); require_once 'HTML/AJAX/Server.php'; class AutoServer extends HTML_AJAX_Server { // Ustawienie tego znacznika jest konieczne, by korzystać z metod inicjalizacyjnych var $initMethods = true; // Metody inicjalizacyjne dla klasy podpowiedzi function initSuggest() { require_once 'suggest.class.php'; $suggest = new suggest(); $this->registerClass($suggest); } } $server = new AutoServer(); $server->handleRequest(); Listing 6. Prosty klient podpowiedzi <html> <head> <title>HTML_AJAX Suggest</title> <script type='text/javascript' src='auto_server.php?client=all&stub=suggest'> </script> <script> function do_suggest() { remoteSuggest.getstring(document.getElementById('string').value); } // Stworzenie tablicy asocjacyjnej do składowania metod zwrotnych var suggestCallback = { getstring: function(result) { document.getElementById('suggestions').innerHTML = result; } } // Utworzenie obiektu zdalnego. Jego nazwa jest odwzorowana małymi literami, // gdyż w nazwach klas i funkcji PHP4 wielkość liter nie jest rozpoznawana. // Rejestrując każdą klasę na serwerze można przywrócić rozróżnianie // wielkości liter. var remoteSuggest = new suggest(suggestCallback); </script> </head> <body> <div> Podaj nazwę pakietu PEAR: <input type="text" name="string" id="string" size="20" onkeyup=" do_suggest(); return false;"> <input type="button" name="check" value="Podpowiedz..." onclick="do_suggest(); return false;"> </div> <div id="suggestions"> </div> </body> </html> Listing 7. Ulepszona metoda zwrotna var suggestCallback = { getstring: function(resultSet) { var resultDiv = document.getElementById('suggestions'); resultDiv.innerHTML = ''; for(var f=0; f<resultSet.length; ++f){ var result=document.createElement("span"); result.innerHTML = resultSet[f]; resultDiv.appendChild(result); } } } PHP Solutions Nr 1/2006 www.phpsolmag.org Techniki HTML_AJAX. Ta komunikuje się z serwerem, który odsyła tablicę pasujących podpowiedzi, przekazywaną następnie funkcji zwrotnej, która kończy cały proces dokonując niezbędnych zmian w strukturze dokumentu i wyświetlając podpowiedzi w ramach elementu <div>. W ten sposób mamy już działający, choć nie najpiękniejszy przykład. Po pierwsze, funkcja autouzupełniania przeglądarki w tym przypadku tylko przeszkadza. Możemy ją jednak łatwo wyłączyć dodając do pola tekstowego atrybut autocomplete="off". Po drugie, sposób wyświetlania podpowiedzi pozostawia bardzo wiele do życzenia. Spróbujmy więc ulepszyć funkcję zwrotną – Listing 7. przedstawia poprawiony kod. Po usunięciu poprzedniej zawartości elementu resultDiv, wyświetlającego wyniki, opakowujemy każdy wynik w osobny znacznik <span> dla uzyskania lepszej kontroli nad formatowaniem, po czym w pętli for dodajemy kolejne wyniki do warstwy resultDiv. Etap opakowania wykonujemy wywołując metody JavaScriptu document. createElement("span") i appendChild(). Dla zwiększenia czytelności można popracować nad stylem (Listing 8). Najważniejszy jest tu wpis powodujący wyświetlanie podpowiedzi jedna pod drugą zamiast w jednym wierszu: #suggestions span { display: block; } Listing 8. Style CSS dla klienta podpowiedzi * { padding: 0; margin: 0; font-family : Arial, sans-serif; } #suggestions { max-height: 200px; width : 306px; border: 1px solid #000; overflow : auto; margin-top : -1px; float : left; } #string { width : 300px; font-size : 13px; padding-left : 4px; } #suggestions span { display: block; } 55 Techniki Listing 9. Ostateczna wersja klienta podpowiedzi <html> <head> <title>Podpowiedzi HTML_AJAX</title> <link rel="StyleSheet" type="text/css" href="suggest3.css" /> <script type='text/javascript' src='auto_server.php?client=all&stub=suggest'> </script><script> var string = ''; var oldstring = ''; var timeout= 1000; /* czas w ms między sprawdzeniami - dobrą wartością jest 250*/ function do_suggest() { string = document.getElementById('string').value; if (string != oldstring) { /* Przy pustym polu nie wysyłaj żądania... */ if (string) { remoteSuggest.getstring(string); } /* ... tylko ukryj warstwę */ else { document.getElementById('suggestions').style.display = 'none'; } oldstring = string; } window.setTimeout('do_suggest()', timeout); } // Stworzenie tablicy asocjacyjnej do składowania metod zwrotnych var suggestCallback = { getstring: function(resultSet) { var resultDiv = document.getElementById('suggestions'); resultDiv.innerHTML = ''; resultDiv.style.display = 'block'; if (!resultSet) resultDiv.style.display = 'none'; else{ for(var f=0; f<resultSet.length; ++f){ var result=document.createElement("span"); result.innerHTML = resultSet[f]; result.onmouseover = highlight; result.onmouseout = unHighlight; result.onmousedown = selectEntry; resultDiv.appendChild(result); } } } } // Utworzenie obiektu zdalnego var remoteSuggest = new suggest(suggestCallback); // Funkcje obsługi interakcji function highlight () { this.className = 'highlight'; } function unHighlight () { this.className = ''; } function selectEntry () { document.getElementById('string').value =this.innerHTML; } </script> </head> <body onload="do_suggest()"> <h1>HTML_AJAX Example: Suggest</h1> <p>Uwaga: czas między sprawdzeniami jest ustawiony na 1000ms dla celów demonstracyjnych. W praktyce lepiej korzystać z wartości rzędu 350ms.</p> <div id="error"></div> Podaj nazwę pakietu PEAR: <form method="get" id="suggest"> <input type="text" name="string" id="string" size="20" autocomplete="off"> <input type="button" name="check" value="Podpowiedz..." onkeyup="do_suggest(); return false;"> <div id="suggestions"> </div> </form> </body> </html> 56 www.phpsolmag.org Warstwa wyników powinna początkowo być ukryta, więc w pliku CSS podajemy dla niej atrybut display: none, który po otrzymaniu wyników przełączamy na wartość block w ramach metody zwrotnej: resultDiv.style.display='block'; if (!resultSet) resultDiv.style.display='none'; Dodatkowe sprawdzenie zapobiega wyświetleniu warstwy, gdy serwer nie zwróci żadnych podpowiedzi. Pora dodać do wyników nieco interakcji – na razie tylko widzimy podpowiedzi, ale nie możemy wybierać pozycji z listy. Efekt wybierania osiągniemy dodając obsługę zdarzeń do elementu <span> każdego wyniku: result.onmouseover = highlight; result.onmouseout = unHighlight; result.onmousedown = selectEntry; Spowoduje to dodanie funkcji JavaScript do każdego zdefiniowanego zdarzenia. Działanie funkcji highlight() i unHighlight() polega po prostu na zmianie klasy CSS elementu <span>: function highlight (){ this.className='highlight'; } Klasa CSS highlight wygląda tak: .highlight { background-color: 0000ff; color: fff; } Minimalna wersja naszej wyszukiwarki powinna obsługiwać zastąpienie zawartości pola tekstowego podpowiedzią klikniętą przez użytkownika. Pole ma identyfikator string, więc podmiana jego treści jest prosta: function selectEntry () { document.getElementById('string') .value = this.innerHTML; } Wartość pola tekstowego jest zastępowana zawartością danego elementu <span>, czyli jednym z wyników zwróconych przez serwer AJAX. Całość wygląda już znacznie lepiej (Rysunek 3). Przykład działa poprawnie, PHP Solutions Nr 1/2006 AJAX ale do serwera wysyłanych jest zbyt wiele żądań – jeśli użytkownik wpisze coś bardzo szybko, a korzysta z powolnego łącza, to może dochodzić do wysyłania kolejnego żądania podpowiedzi przed otrzymaniem odpowiedzi na żądanie poprzednie. Spróbujmy jakoś temu zapobiec. Skorzystajmy z techniki zwanej dławieniem zgłoszeń (ang. submission throttling). W tym przypadku kliencki JavaScript będzie co jakiś czas (na przykład co 350 milisekund) sprawdzał, czy wartość pola tekstowego uległa zmianie – jeśli tak, to zostanie wysłane żądanie do serwera (patrz Listing 9). Dodatkowo sprawdzimy też, czy pole nie jest przypadkiem puste – w takiej sytuacji nie wysyłamy żądania i ukrywamy warstwę wyników. Jak widać, dodanie imponującej interakcji do formularzy i aplikacji nie jest wcale trudne. Nasz prosty przykład można oczywiście rozbudować, dodając na przykład możliwość przechodzenia po liście wyników klawiszami kursora czy też obsługę lokalnego składowania danych, pozwalającego oszczędzić sporo pasma. Komunikat ładowania Uruchamiając przykłady z HTML_AJAX zauważysz, że przy każdym wywołaniu AJAX pojawia się czerwone okienko z komunikatem loading. Powiadomienie to jest automatycznie wyświetlane przez HTML_AJAX i jest po prostu warstwą o określonym identyfikatorze, tworzoną jeśli wcześniej nie istniała. Jeśli więc chcesz zmienić ten komunikat, wystarczy gdzieś w kodzie HTML umieścić na przykład taki fragment: <div id="HTML_AJAX_LOADING" style= "background-color : blue; color : white; display : none; position : absolute; right : 50px; top : 50px;"> Ładowanie nowego napisu...</div> Wyświetlania komunikatu nie da się w obecnej wersji wyłączyć po stronie serwera, ale w przyszłych wersjach HTML_AJAX taka możliwość już będzie. Aby zapobiec wyświetlaniu komunikatu trzeba nadpisać generującą go funkcję JavaScriptu: HTML_AJAX.onOpen = function(){ // nic } JavaScriptu, co wynika z tego, że JPSpan przechwytuje również te błędy i zgłasza je jako ostrzeżenia. Podczas pracy z HTML_AJAX można dodać własną funkcję obsługi błędu, która będzie podmieniać zawartość elementu <div> o identyfikatorze error: HTML_AJAX.onError = function(e) { msg = "\nn"; for(var i in e) { Śledzenie kodu AJAX Podczas eksperymentów z AJAX-em zauważysz zapewne, że technika ta wymaga nowego podejścia do śledzenia kodu. Nie wystarczy już śledzić kodu PHP – trzeba jeszcze pilnować JavaScriptu i obsługiwanej przez AJAX-a komunikacji między klientem i serwerem. Na szczęście nie jest to trudne. Przede wszystkim, każdy moduł kodu należy testować osobno. Pracując w JavaScripcie dobrze jest stworzyć funkcję pomocniczą, na przykład prościutki odpowiednik print_r() z PHP: function print_r(input) { var ret; ..for(var i in input) { ....ret += "["+i+"] = "+input[i]+"\n"; ..} ..alert(ret); } Możliwości obserwacji w bibliotece JPSpan pozwalają też rejestrować między innymi błędy i udane wywołania funkcji AJAX. W domyślnej konfiguracji serwera błędy PHP są przekazywane jako ostrzeżenia JavaScript. Niekiedy można też natrafić na ostrzeżenia wynikające z błędów PHP Solutions Nr 1/2006 Techniki msg += i + ':' + e[i] +"\n"; } document.getElementById('error'). innerHTML += msg; } Pozwoli to przechwytywać wszystkie błędy AJAX – najczęściej będą to zwyczajne błędy PHP, ale mogą się też pojawiać błędy 404, błędy wygaśnięcia i inne. Na koniec zalecałbym tworzenie aplikacji dla przeglądarki Firefox, a dopiero potem testowanie ich w Internet Explorerze. Firefox ma nieporównanie lepsze narzędzia programistyczne od IE, a w dodatku oferuje bardzo wiele przydatnych rozszerzeń. Podsumowanie Wiedząc już czym jest AJAX i jak z niego korzystać, możesz rozważyć zastosowanie tej techniki w swoich stronach. Prawidłowe używanie AJAX-a pozwala często osiągnąć imponujące wyniki, ale nie znaczy to, że należy bezkrytycznie stosować tę technikę we wszystkich witrynach. Zawsze trzeba mieć na uwadze podstawowe przeznaczenie danego serwisu – być może w konkretnym przypadku lepiej byłoby dopracować nawigację lub prezentację www.phpsolmag.org treści, niż dodawać interakcję korzystającą z AJAX-a. Koniecznie trzeba też uwzględniać docelowych odbiorców – jeśli większość użytkowników z różnych względów ma wyłączoną obsługę JavaScriptu, to wprowadzenie AJAX-a raczej nie będzie dobrym pomysłem. Podobną rolę odgrywa skala – aplikacja wyposażona w AJAX-a znacznie lepiej sprawdzi się w przypadku niewielkich serwisów intranetowych (gdzie łatwo rozwiązać problemy konfiguracyjne i ujednolicić przeglądarki) niż w przypadku rozbudowanych, publicznie dostępnych witryn. Krótko mówiąc, wprowadzenie AJAX-a może dać dobre wyniki pod warunkiem, że nadrzędnym celem pozostanie uzyskanie jak najlepszych walorów użytkowych. n O autorze Joshua Eichorn tworzy serwisy PHP od siedmiu lat. Jest autorem phpDocumentor – wielokrotnie nagradzanego i szeroko używanego narzędzia dokumentującego kod PHP. Jest też szefem projektu HTML_AJAX, dostarczającego implementację techniki AJAX dla repozytorium PEAR. Obecnie pracuje jako starszy architekt oprogramowania w firmie Uversa Inc., gdzie tworzy dla klientów unikatowe rozwiązania. Technikę AJAX stosował jeszcze przed jej oficjalnym opracowaniem i popularyzacją. Mieszka w Phoenix w stanie Arizona (USA). Kontakt: [email protected] Werner M. Krauß programuje w PHP od 1999 r. Gdy nie gra na gitarze, zajmuje się tworzeniem dokumentacji dla frameworka Seagull. Kontakt: [email protected] 57