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">&nbsp;</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">&nbsp;</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

Podobne dokumenty