Prototype
Prototype jest biblioteką JavaScript, której autorem jest Sam Stephenson. Została ona opracowana po to by uprościć tworzenie skryptów mających wprowadzić dynamikę na stronach stronach www. Dzięki rozszerzeniu modelu DOM oraz wsparciu dla technologii Ajax budowa bogatego interfejsu użytkownika w aplikacjach webowych zajmie nam mniej czasu.
Sama biblioteka to jeden plik, który możemy szybko dodać do projektu. Wystarczy zaimportować jeszcze jeden plik ze skryptami JavaScript i gotowe.
Rozszerzanie modelu DOM
A teraz kilka słów na temat ciekawego pomysłu twórcy opisywanej biblioteki, czyli rozszerzania modelu DOM. Na czym ono polega i co nam właściwie daje? Otóż, chodzi o to byśmy przy odnoszeniu się do elementów strony www wykorzystywali funkcję $(). Funkcja ta przyjmuje jako argument identyfikator danego elementu i zwraca referencję do obiektu ‘Element’, który opakowuje standardowy obiekt zwracany przez document.getElementById(). Owo opakowanie polega na rozszerzeniu zbioru metod, jakie możemy wywoływać o te zawarte w Element.Methods i Element.Methods.Simulated. Od tej pory mamy dostęp do dodatkowych funkcji, które możemy wywołać na pobranym elemencie.
Zacznijmy może od prostego przykładu, w którym zobaczymy jak działa owo rozszerzanie modelu DOM przez Prototype.
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-2"/>
<script src="ścieżka/do/prototype.js" type="text/javascript"></script>
<script type="text/javascript" >
function powitaj() {
alert( $('welcomeSection').innerHTML );
}
function ukryj(obj) {
$(obj).hide();
}
function pokaz(obj) {
$(obj).show();
}
</script>
</head>
<body>
<span id="secretSection">Tę sekcję możemy ukryć</span>
<button onclick="ukryj('secretSection')">Ukryj</button>
<button onclick="pokaz('secretSection')">Pokaż</button>
<div id="welcomeSection">Witaj</div>
<button onclick="powitaj()">Powitaj</button>
</body>
</html>
Powyższy przykład pokazuje w jak prosty sposób możemy uzyskać dostęp do szerokiej gammy dodatkowych funkcji oferowanych przez Prototype. Wykorzystując funkcję $() pobieramy referencję do elementów o identyfikatorach ‘welcomeSection’ i ’secretSection’ i wywołujemy na nich funkcje z biblioteki Prototype. W przypadku funkcji powitaj() odnosimy się jedynie do własności innerHTML. Nazwy zaprezentowanych funkcji mówią same za siebie. Funkcje hide() i show() kolejno ukrywają i pokazują sekcję ’secretSection’. Owo ukrywanie i pokazywanie polega na zmianie stylu pobranego elementu. My jednak nie musimy tego wiedzieć. Po prostu wywołujemy funkcje na danym elemencie i uzyskujemy spodziewany efekt. By osiągnąć to samo bez wykorzystania Prototype musielibyśmy napisać taki oto fragment:
document.getElementById('secretSection').style.display = 'none';
Wykorzystując prezentowaną bibliotekę wystarczy, że napiszemy:
$('secretSection').hide();
Proste. Genialne ;)
Gdy przy pomocy funkcji $() pobierzemy jeden z elementów formularza (input, textarea, select) to wówczas mamy do dyspozycji nieco więcej funkcji, które możemy wywołać. Dzieje się tak dlatego gdyż zwracany obiekt jest dodatkowo rozszerzany o metody z Form.Element.Methods. Natomiast jeśli pobierzemy formularz (form) to rozszerzenie nastąpi o metody z Form.Methods. Są to funkcje umożliwiające np. deaktywowanie pól formularza, czy też czyszczenie ich zawartości. Jednak gdy spróbujemy je wywołać na elemencie nie będącym formularzem, bądź jednym z jego pól to wówczas zostaniemy poinformowani o błędzie.
<script type="text/javascript" >
function deaktywuj(id) {
$(id).disable();
}
function aktywuj(id) {
$(id).enable();
}
</script>
<div id="opis"><h2>Formularz danych osobowych</h2></div>
<form action="ex2.html" id="form1">
<label>Imię: </label><input type="text" id="imie"/><br>
<label>Hobby: </label><textarea rows="5" cols="20" id="hobby"></textarea><br>
<input type="submit" value="Zapisz" id="zapisz"/>
</form>
<button onclick="deaktywuj('imie')">Deaktywuj</button>
<button onclick="aktywuj('imie')">Aktywuj</button>
<button onclick="deaktywuj('form1')">Deaktywuj formularz</button>
<button onclick="deaktywuj('opis')">Test błędu</button>
Przykład przedstawia prosty formularz zawierający dwa pola do wprowadzenia danych oraz przycisk powodujący wysłanie tychże danych do serwera. Ponadto na stronie znajdują się przyciski modyfikujące formularz. Dwa pierwsze, ‘Deaktywuj’ i ‘Aktywuj’ modyfikują stan pola ‘imie’ wykorzystując funkcje ‘aktywuj’ oraz ‘deaktywuj’. Każda z nich otrzymuje identyfikator elementu, który pozwoli na jego “wydobycie” przy pomocy funkcji $(). Pobrany obiekt opakowany jest w metody z Element.Methods, Element.Methods.Simulated oraz Form.Element.Methods. Metoda disable/enable, którą następnie wywołujemy jest funkcją zdefiniowaną w Form.Element.Methods. Nieco inaczej mechanizm rozszerzania modelu DOM zachowuje sie gdy przekazujemy identyfikator formularza ‘form1′. Funkcja $() zwraca obiekt posiadający metody z Element.Methods, Element.Methods.Simulated oraz Form.Methods. Teraz wywołujemy metodę disable/enable zdefiniowaną w Form.Methods, która modyfikuje wszystkie elementy formularza, a nie jak poprzednia tylko jeden.
Funcja $() ma inne, ciekawe właściwości o czym zaraz napiszę. Po pierwsze może ona przyjmować jako argument również obiekt zwrócony przez document.getElementById() i podobnie jak wcześniej zwróci nam obiekt typu ‘Element’. Jeśli natomiast podamy w wywołaniu funkcji kilka identyfikatorów to jako rezultat jej działania otrzymamy tablicę obiektów ‘Element’. Ciekawym pomysłem jest też możliwość potokowego wywoływania metod na obiektach. Jako, że spora liczba funkcji zwraca referencję do obiektu, który modyfikuje nic nie stoi na przeszkodzie by od razu wywołać na nim kolejną metodę. Oto prosty przykład:
<div id="topSecret" style="display:none;">Ten tekst jest niewidoczny.</div>
<script type="text/javascript">
$('topSecret').update('Tajne dane juz nie są tajne.').show();
</script>
Pomimo tego, że po zastosowaniu funkcji $() mamy do naszej dyspozycji całkiem pokaźny zbiór funkcji ułatwiający modyfikację elementów, czy też prostsze poruszanie się po drzewie DOM Prototype daje nam możliwość rozszerzenia tegoż zbioru o nasze własne metody. Posługując się funkcją Element.addMethods() możemy dołączyć nasze, opracowane w pocie czoła, metody. Musimy jedynie pamiętać by jako pierwszy argument przyjmowały obiekt, na którym będą operować. Jeśli dodatkowo będziemy zwracać jego referencję to tym samym zapewnimy możliwość potokowego wywoływania naszych funkcji. Oto przykład:
<script type="text/javascript">
var MyMethods = {
showValue: function(element){
alert( $(element).innerHTML );
return element;
},
setDefaultValue: function(element, value){
return $(element).update(value);
}
}
Element.addMethods(MyMethods);
function testNaszychMetod() {
$('newsContent').setDefaultValue('Nowa treść').showValue();
}
</script>
AJAX
W tej części artykułu zobaczymy w jaki sposób Prototype może nam ułatwić stosowanie technologii Ajax w naszym projekcie.
Zakładam, że osoba czytająca ten artykuł ma zainstalowanego mavena ponieważ wykorzystamy to narzędzie do budowy aplikacji webowej. Tak więc na początek stwórzmy katalog w wybranej przez siebie lokalizacji i wywołajmy w nim komendę
mvn archetype:create -DgroupId=pl.j2ee.prototypejs -DartifactId=prototypejs -DarchetypeArtifactId=maven-archetype-webapp
Powinna się ona zakończyć komunikatem BUILD SUCCESSFUL, a wynikiem jej działania będzie katalog o nazwie prototypejs zawierający strukturę aplikacji webowej odpowiednią dla mavena. Gdy będziemy wywoływać polecenie mvn install maven zbuduje nam wersję aplikacji gotową do uruchomienia na serwerze. Do uruchomienia prezentowanych przykładów wykorzystamy serwer tomcat w wersji 5.5. Wróćmy może do naszego projektu. Poleceniem archetype:create utworzyliśmy szkielet aplikacji, który teraz powinniśmy nieco zmodyfikować. Na początek dodamy kilka wpisów do pom.xml dotyczących zależności, zewnętrznego repozytorium oraz buildu. Nasz plik pom.xml powinien wyglądać tak:
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>pl.j2ee.prototype</groupId>
<artifactId>prototypejs</artifactId>
<packaging>war</packaging>
<version>1.0-SNAPSHOT</version>
<name>prototypejs Maven Webapp</name>
<url>http://maven.apache.org</url>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>tomcat</groupId>
<artifactId>servlet-api</artifactId>
<version>5.5.15</version>
</dependency>
</dependencies>
<build>
<finalName>prototypejs</finalName>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.5</source>
<target>1.5</target>
</configuration>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<name>ibiblio</name>
<id>ibiblio</id>
<url>http://www.ibiblio.org/maven/</url>
</repository>
</repositories>
</project>
Jako że nasza aplikacja ma prezentować wykorzystanie technologii Ajax to musimy przygotować serwlet, który będzie generował odpowiedź dla żądań ajaxowych.
W katalogu prototypejs\src\main\ utwórzymy dodatkowy katalog o nazwie java a w nim w pakiecie pl.j2ee.prototypejs.servlet klasę serwletu DataServlet. Docelowo będzie on obsługiwał żądania typu get i post.
package pl.j2ee.prototypejs.servlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.ServletException;
import java.io.PrintWriter;
import java.io.IOException;
import java.util.Date;
public class DataServlet extends HttpServlet {
public void doGet(HttpServletRequest req,
HttpServletResponse res)
throws ServletException, IOException {
PrintWriter out = res.getWriter();
out.println("Wiadomosc pobrana o " + new Date());
String osoba = (String) req.getParameter("osoba");
if (osoba != null)
out.println( " Osoba: " + osoba);
out.close();
}
public void doPost(HttpServletRequest req,
HttpServletResponse res)
throws ServletException, IOException {
PrintWriter out = res.getWriter();
String text = "Tekst o 7 krasnoludkach i innych takich.";
out.println("Dane pobrane o " + new Date());
for (int i = 0; i < 10; i++)
out.println(text);
out.close();
}
}
Metoda doGet zwraca jako odpowiedź tekst zawierający datę jej wygenerowania. Jeśli w żądaniu ajaxowym podamy parametr o nazwie osoba to zostanie on również dodany do odpowiedzi.
Dobrze. Mamy już potrzebny serwlet wiec teraz dodamy o nim wpis do deskryptora serwera czyli pliku web.xml.
<?xml version="1.0" encoding="ISO-8859-1"?> <web-app xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd" version="2.4"> <display-name>J2ee.pl - Prototype</display-name> <servlet> <servlet-name>dataServlet</servlet-name> <servlet-class>pl.j2ee.prototypejs.servlet.DataServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>dataServlet</servlet-name> <url-pattern>/data</url-pattern> </servlet-mapping> <welcome-file-list> <welcome-file>index.html</welcome-file> </welcome-file-list> </web-app>
Jak widzimy serwlet DataServlet będzie obsługiwał żądania, które zostaną dopasowane do wzorca /data.
I jeszcze strona html. Zawiera ona kilka sekcji, w których będziemy umieszczać dane przychodzące z serwera w kilku następnych przykładach oraz funkcję javascrpit wysyłającą ajaxowe żądanie.
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-2"/>
<script src="js/prototype.js" type="text/javascript"></script>
<style type="text/css">
.section {
margin: 5px 5px 5px 5px;
padding: 2px 2px 2px 2px;
width: 500px;
}
.data {
background-color: #60A7F2;
border: 2px solid #388EF2;
}
.errors {
background-color: #FF9966;
border: 2px solid #ED3D16;
}
</style>
</head>
<body>
<script type="text/javascript">
function testAjax() {
new Ajax.Request('/prototypejs/data',
{
method:'get',
onSuccess: function(transport) {
$('news').innerHTML = transport.responseText || "brak danych";
},
onFailure: function() {
alert('Wystąpił błąd podczas pobierania danych...')
}
});
}
</script>
<button onclick="testAjax()">Pobież dane</button>
<div class="section data">Dane pobrane z serwera: <span id="news"></span></div>
<div id="errors" class="section errors"></div>
<div id="newsContent" class="section data">Dane pobrane z serwera:</div>
<div class="section data">Dane w tej sekcji będą uaktualniane okresowo:
<span id="dynamicContent"></span>
</div>
</body>
</html>
W Prototype funkcjonalność związana z technologią Ajax została zawarta w obiekcie Ajax oraz kilku funkcjach serializujacych dane. Żądania ajaxowe wysyłamy w bardzo prosty sposób. Wystarczy utworzyć obiekt Ajax.Request, wskazać url, do którego ma się odnieść oraz podać kilka parametrów związanych z żądaniem i jego obsługą. Konstruktor obiektu Ajax.Request przyjmuje dwa parametry. Pierwszym z nich jest url, a drugim hashmapa z parametrami. W naszym przykładzie podajemy trzy parametry: typ żądania oraz funkcje obsługującą dane w przypadku poprawnej bądź niepoprawnej obsługi naszego żądania. Żądania ajaxowe są z reguły asynchroniczne, stąd metody, które przekazujemy do ich obsługi są traktowane jako funkcje callback. Oznacza to iż są one wywoływane gdy otrzymamy odpowiedni status obsługi naszego żądania ajaxowego. Tutaj znajdziemy spis parametrów, które możemy podać w żądaniu wraz z ich domyślnymi wartościami.
Dla osób, którym powyższy zapis wydaje się nieco nieprzejrzysty mam dobrą wiadomość. Otóż możemy przepisać powyższy kod javascript do nieco ładniejszej postaci. Drugi argument możemy zapisać w notacji JSON. Teraz nasza funkcja wygląda nieco przejrzyściej.
<script type="text/javascript">
function putText(transport) {
$('news').innerHTML = transport.responseText || "brak danych";
}
function showErrorMsg() {
alert('Wystąpił błąd podczas pobierania danych...');
}
function testAjax() {
var url = "/prototypejs/data";
var params = new Object();
params.method = "get";
params.parameters = "osoba=Leszek Peszek";
params.onComplete = putText;
params.onFailure = showErrorMsg;
new Ajax.Request(url, params);
}
</script>
Ajax często wykorzystywany jest w ten sposób, że pobiera część strony www z serwera i bez modyfikacji wstawia ją do odpowiedniej sekcji strony. Prototype wychodzi na przeciw tym praktykom udostępniając obiekt Ajax.Updater. Działa on na podobnej zasadzie co Ajax.Request, tyle że umożliwia stosowanie łatwiejszego zapisu. W konstruktorze obiektu podajemy tym razem trzy argumenty. Są to: identyfikator sekcji, do której wstawiona zostanie pobrana z serwera treść, url oraz parametry żądania. Oto malutki przykład.
<script type="text/javascript">
function testAjax() {
new Ajax.Updater('newsContent', '/prototypejs/data',
{ method: 'post'}
);
}
</script>
Nie musimy podawać żadnych funkcji obsługujących to żądanie. Jeśli otrzymamy status żądania świadczący o udanym pobraniu danych z serwera to Prototype sam wstawi pobraną treść do wskazanej sekcji. Bardzo zgrabnie obsługiwany jest przypadek wystąpienia jakiegokolwiek błędu. Otóż jeśli zastosujemy zapis jak powyżej to treść błędu nie zostanie wstawiona do podanej sekcji. Po prostu stracimy informację o zaistniałej błędnej sytuacji. Takie podejście jest najczęściej stosowane gdyż zwykle nie chcemy pokazać osobie przeglądającej naszą stronę brzydkiego komunikatu o błędzie. Niemniej jednak jeśli po stronie serwera przechwytujemy wszelkie błędy i opakowujemy je w ładne komunikaty to możemy je również wyświetlić na stronie www.
<script type="text/javascript">
function testAjax() {
new Ajax.Updater({ success:'newsContent', failure:'errors' },
'/prototypejs/data',
{ method: 'post', insertion: Insertion.Top }
);
}
</script>
Tym razem pierwszy argument przekazywany do konstruktora obiektu Ajax.Updater jest hashmapą, w której podajemy dwa identyfikatory sekcji. Jeśli nie wystąpi żaden błąd to pobrane dane zostaną wstawione do sekcji ‘newsContent’, natomiast jeśli pojawi się jakikolwiek błąd to jego treść znajdzie się w sekcji ‘errors’.
W drugim przykładzie pojawiła się jeszcze jedna nowość. Podajemy dodatkowy parametr żądania ‘insertion: Insertion.Top’, który informuje obiekt Ajax.Updater by wstawił pobraną treść “przed” tą, która znajduje się już w danej sekcji. Więc jeśli pojawi się jakiś błąd związany z żądaniem ajaxowym to informacja o błędzie zostanie wstawiona do sekcji gdzie, być może, mamy już inne komunikaty o błędach, na wyeksponowanej, pierwszej pozycji.
Inne dość często wykorzystywane zastosowanie technologii Ajax polega na okresowym uaktualnianiu części strony www, np. co minutę sekcja z najświeższymi informacjami ze świata Javy jest aktualizowana. Również tym razem Prototype dostarcza nam gotowy obiekt, który przy naszym minimalnym wkładzie zajmie się tym trudnym zadaniem ;). obiekt nazywa się Ajax.PeriodicalUpdater i stosuje się go podobnie do tych wcześniej opisanych.
<script type="text/javascript">
function testAjax() {
new Ajax.PeriodicalUpdater('dynamicContent',
'/prototypejs/data',
{method: 'get', frequency: 3, decay: 2}
);
}
</script>
Pierwszym argumentem jest identyfikator sekcji, do której dane będą wstawiane. Dalej mamy url oraz paczkę z parametrami żądania. W przykładzie podajemy parametr ‘frequency’, który określa jak często żądanie będzie przesyłane. W tym przypadku są to trzy sekundy. Kolejny parametr ‘decay’ mówi ile razy obiekt Ajax.PeriodicalUpdater ma zwiększyć parametr ‘frequency’ jeśli treść żądania, którą właśnie otrzymaliśmy jest identyczna z tą wcześniej otrzymaną. Pozwala to zmniejszyć obciążenie serwera na serwisach gdzie niusy nie pojawiają się zbyt często :).
Prototype zostało opracowane po to by ułatwić programowanie w JavaScript. Jego twórca miał kilka ciekawych pomysłów, które zostały udanie zaimplementowane. Zostało to zauważone również przez twórców innych bibliotek JavaScript (jak choćby Prototype Window), które wykorzystują Prototype jako swój szkielet, swoją bazę. W prosty sposób uzyskujemy dostęp do szerokiej gammy funkcji, którą zresztą możemy sami rozszerzać o nasze własne. Poprzez wsparcie dla technologii Ajax jej użycie staje się dziecinnie proste i bardzo wygodne. Za jedyną słabszą stronę Prototype można uznać jej dokumentację, która w niektórych miejscach nie jest zbyt obszerna. Niemniej jednak ubytek ten będzie nam zrekompensowany przez mającą się ukazać w najbliższym czasie książkę pt. ‘Prototype and script.aculo.us’.








August 7th, 2007 at 21:41
No wreszcie po miesiącu jakiś artykuł do poczytania :)
gr8 job ! :)
December 13th, 2007 at 14:59
A ja polecam zapoznanie się z mootools, bardzo fajny framework pozwalający w miarę obiektowy sposóp pisać aplikację w JS.