GWT, czyli AJAX bez bólu
Google Web Toolkit jest to framework umożliwiający łatwe i bezbolesne tworzenie aplikacji AJAX. Aby użyć GWT wystarcza znajomość Javy i podstaw HTMLa, nie jest natomiast potrzebna znajomość JavaScriptu. Poniższy artykuł ma na celu przybliżyć podstawy GWT oraz krok po kroku pokazać jak szybko stworzyć prostą aplikację opartą na tym frameworku.
Budowa GWT
Na początek przyjrzymy się, co tak naprawdę kryje się nazwą GWT. GWT składa się z czterech podstawowych komponentów.
Pierwszy z nich, kompilator Java-to-JavaScript, tłumaczy kod źródłowy na JavaScript i HTML. Oczywiście, aby kod mógł zostać poprawnie przetłumaczony, musi spełniać szereg ograniczeń. Przede wszystkim należy pamiętać, że GWT w obecnej wersji (1.4) jest zgodny z Javą 1.4.2, więc użycie jakichkolwiek rozszerzeń Javy 1.5 skończy się błędem kompilacji. Poza tym należy zapomnieć o takich przyjemnościach jak wielowątkowość czy refleksja.
Drugi komponent to dedykowana przeglądarka WWW. Umożliwia ona uruchomienie aplikacji bez translacji do JavaScriptu, w tzw. Hosted Mode. Szczegóły – za chwilę.
Trzeci komponent to biblioteka emulująca podstawowe klasy biblioteki standardowej Javy w JavaScripcie. Zawiera ona implementację większości klas z java.lang, oraz część java.util.
Czwarty komponent, czyli biblioteka Web UI, służy do tworzenia interfejsu użytkownika który zostanie następnie przetłumaczony na stronę WWW.
Hosted Mode vs. Web Mode
GWT oferuje nam dwa tryby uruchomienia stworzonej aplikacji. Pierwszy z nich, tzw. Hosted Mode, wykorzystując wspomnianą już dedykowaną przeglądarkę WWW uruchamia aplikację bezpośrednio w wirtualnej maszynie Javy, bez tłumaczenia kodu do JavaScriptu. To pozwala programiście korzystać z takich dobrodziejstw Javy, jak chociażby wygodne debugowanie kodu. Z tego względu Hosted Mode jest używany w trakcie procesu tworzenia aplikacji.
Z kolei Web Mode polega na skompilowaniu kodu do HTMLa i JavaScriptu i uruchomieniu tak przygotowanej aplikacji w dowolnej przeglądarce WWW. Ten tryb używany jest przy wdrażaniu gotowej aplikacji.
GWT w praktyce
Po krótkim wprowadzeniu czas przejść do konkretów, czyli przykładowej aplikacji. Będzie to prosty kalendarz zamieszczony na stronie WWW.
Aby użyć GWT wystarczy pobrać wersję odpowiednią dla używanego systemu ze strony http://code.google.com/webtoolkit i rozpakować ją do dowolnego katalogu. Dla wygody warto dopisać go do zmiennej środowiskowej PATH.
Do stworzenia projektu skorzystamy z dwóch skryptów dostarczonych wraz z GWT: projectCreator oraz applicationCreator. Pierwszy z nich posłuży do stworzenia plików projektu Eclipse’a, drugi do wygenerowania podstawowych plików aplikacji.
C:\\projects\\gwtcalendar>projectCreator.cmd -eclipse gwtcalendar Created directory C:\\projects\\gwtcalendar\\src Created directory C:\\projects\\gwtcalendar\\test Created file C:\\projects\\gwtcalendar\\.project Created file C:\\projects\\gwtcalendar\\.classpath C:\\projects\\gwtcalendar>applicationCreator.cmd -eclipse gwtcalendar pl.jcommerce.example.gwtcalendar.client.Calendar Created directory C:\\projects\\gwtcalendar\\src\\pl\\jcommerce\\example\\gwtcalendar Created directory C:\\projects\\gwtcalendar\\src\\pl\\jcommerce\\example\\gwtcalendar\\client Created directory C:\\projects\\gwtcalendar\\src\\pl\\jcommerce\\example\\gwtcalendar\\public Created file C:\\projects\\gwtcalendar\\src\\pl\\jcommerce\\example\\gwtcalendar\\Calendar.gwt.xml Created file C:\\projects\\gwtcalendar\\src\\pl\\jcommerce\\example\\gwtcalendar\\public\\Calendar.html Created file C:\\projects\\gwtcalendar\\src\\pl\\jcommerce\\example\\gwtcalendar\\client\\Calendar.java Created file C:\\projects\\gwtcalendar\\Calendar.launch Created file C:\\projects\\gwtcalendar\\Calendar-shell.cmd Created file C:\\projects\\gwtcalendar\\Calendar-compile.cmd
Skrypt applicationCreator tworzy strukturę pakietów zgodną z zaleceniami GWT:
.../gwtcalendar – główny pakiet zawierający wszystkie pliki projektu. Bezpośrednio w tym pakiecie tworzony jest plik konfiguracyjny XML.
.../gwtcalendar/client – pakiet zawierający pliki źródłowe warstwy klienta. To właśnie te pliki będą później tłumaczone na JavaScript.
.../gwtcalendar/public – w tym pakiecie zawarte są statyczne zasoby takie jak gotowe pliki html, css czy grafika.
W typowych aplikacjach występuje dodatkowo pakiet .../gwtcalendar/server, zawierający źródła warstwy serwerowej. Ta część kodu nie podlega kompilacji do JavaScriptu, więc nie obowiązują tu żadne ograniczenia nałożone przez GWT. W naszej przykładowej aplikacji zajmiemy się tylko warstwą klienta, więc ten pakiet jest zbędny.
Dodatkowo stworzone zostały trzy pliki w katalogu głównym projektu:
Calendar.launch – plik projektu Eclipse’a pozwalający uruchomić projekt w trybie debug
Calendar-shell.cmd – skrypt uruchamiający aplikację w Hosted Mode
Calendar-compile.cmd – skrypt kompilujący aplikację do postaci HTMLa i JavaScriptu w celu uruchomienia jej w trybie Web Mode
Po wykonaniu powyższych skryptów importujemy projekt do Eclipse’a i przystępujemy do dokładniejszych oględzin plików w pakiecie .../gwtcalendar.
Najpierw przyjrzyjmy się plikowi konfiguracyjnemu modułu Calendar.gwt.xml:
<module> <!-- Inherit the core Web Toolkit stuff. --> <inherits name='com.google.gwt.user.User'/> <!-- Specify the app entry point class. --> <entry-point class='pl.jcommerce.example.gwtcalendar.client.Calendar'/> </module>
Jak widać nie jest on zbyt skomplikowany. Pierwszy element, inherits, wskazuje na dziedziczenie ze standardowego modułu interfejsu użytkownika GWT. Drugi element, entry-point, wskazuje na główną klasę modułu. W momencie wczytania strony WWW, wykonana zostanie metoda onModuleLoad() wskazanej klasy. Główna klasa modułu musi implementować interfejs com.google.gwt.core.client.EntryPoint.
Teraz przejdźmy do naszej statycznej strony WWW, czyli pliku Calendar.html. Wygenerowana, przykładowa zawartość pokazuje, jak można użyć modułu GWT jako część istniejącej strony WWW. W naszym przypadku w zupełności wystarczy mocno okrojona wersja:
<html>
<head>
<title>Calendar</title>
</head>
<body>
<script language='javascript' src='pl.jcommerce.example.gwtcalendar.Calendar.nocache.js'></script>
<!-- OPTIONAL: include this if you want history support -->
<iframe src="javascript:''" id="__gwt_historyFrame" style="width:0;height:0;border:0"></iframe>
</body>
</html>
Nie ma tu za wiele do wyjaśniania. W elemencie script podajemy nazwę modułu z końcówką .nocache.js. Z kolei umieszczenie elementu iframe pozwala na wykorzystanie wsparcia historii przeglądarki oferowanego przez GWT. W naszej przykładowej aplikacji nie ma to żadnego zastosowania, ale warto pamiętać o tym udogodnieniu.
Na koniec trzeci, najbardziej interesujący plik, czyli Calendar.java. Podobnie jak w przypadku Calendar.html, wygenerowana treść metody onModuleLoad() jest tylko przykładowa i od razu możemy się jej pozbyć.
Przyjrzyjmy się teraz budowie interfejsu użytkownika za pomocą GWT. Podstawową klasą interfejsu jest com.google.gwt.user.client.ui.Widget. Z Widget dziedziczą wszystkie elementy UI takie jak przyciski, pola tekstowe, tabele i wiele innych. Szczególnym elementem jest com.google.gwt.user.client.ui.Panel, abstrakcyjna klasa bazowa dla wszystkich kontenerów. GWT, w odróżnieniu od np. Swinga, nie używa odrębnych klas typu LayoutManager. Układem komponentów zarządzają bezpośrednio klasy dziedziczące z Panel, takie jak HorizontalPanel, VerticalPanel, FlowPanel czy DockPanel. Aby umieścić jakiekolwiek komponenty na stronie musimy uzyskać dostęp do głównego panelu. Służy do tego metoda statyczna RootPanel.get().
Interfejs naszego kalendarza będzie się składał z nagłówka zawierającego przyciski do zmiany wyświetlanego miesiąca, paska z etykietami oznaczającymi kolejne dni tygodnia oraz panelu przedstawiającego dni miesiąca. Na początek kilka zmiennych i stałych wykorzystywanych w naszej klasie, oraz metoda onModuleLoad():
private int currYear;private int currMonth;
private VerticalPanel mainPanel;
private Label monthLabel;
private Widget monthPanel;private static final int DAY = 1000 * 3600 * 24;
private static final int WEEK = DAY * 7;
private static final String[] dayNames = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
/**
* This is the entry point method.
*/
public void onModuleLoad() {
Date date = new Date();
currYear = date.getYear();
currMonth = date.getMonth();
initUI();
}
Zmienne mainPanel, monthLabel oraz monthPanel będą przechowywać komponenty, do których potrzebny będzie dostęp w metodach odpowiedzialnych za „dynamikę” naszej aplikacji. Reszta kodu nie wymaga chyba komentarza. Przyjrzyjmy się wobec tego kolejnej metodzie tworzącej interfejs:
private void initUI() {
mainPanel = new VerticalPanel();
mainPanel.setWidth("250px");
mainPanel.setSpacing(3);
mainPanel.add(createHeaderPanel());
mainPanel.add(createDayNamesPanel());
mainPanel.add(monthPanel = createMonthPanel());
RootPanel.get().add(mainPanel);
}
W tej metodzie tworzymy główny panel, ustawiamy jego parametry i dodajemy podstawowe elementy. Na koniec ustawiamy go jako jedyny element RootPanela. Warto zwrócić uwagę na wywołanie metody setWidth(), która przyjmuje parametr typu String, a nie int. Metoda ta przyjmuje wszystkie poprawne wartości CSS, a więc poprawne będzie zarówno „250px”, jak i „5cm” czy „15%”.
Następne dwie metody tworzą nagłówek naszego kalendarza:
private Widget createHeaderPanel() {
HorizontalPanel headerPanel = new HorizontalPanel();
headerPanel.setWidth("100%");
headerPanel.setSpacing(3);
Button prevButton = new Button("<<");
prevButton.setWidth("100%");
headerPanel.add(prevButton);
headerPanel.setCellWidth(prevButton, "20%");
monthLabel = new Label((currYear + 1900) + "-" + (currMonth + 1));
headerPanel.add(monthLabel);
headerPanel.setCellHorizontalAlignment(monthLabel,
HasHorizontalAlignment.ALIGN_CENTER);
Button nextButton = new Button(">>");
nextButton.setWidth("100%");
headerPanel.add(nextButton);
headerPanel.setCellWidth(nextButton, "20%");
return headerPanel;
}
private Widget createDayNamesPanel() {
HorizontalPanel dayNamesPanel = new HorizontalPanel();
dayNamesPanel.setWidth("100%");
dayNamesPanel.setSpacing(3);
Label lab;
for (int i = 0; i < dayNames.length; i++) {
lab = new Label(dayNames[i]);
lab.setWidth("100%");
dayNamesPanel.add(lab);
dayNamesPanel.setCellWidth(lab, "14%");
dayNamesPanel.setCellHorizontalAlignment(lab, HasHorizontalAlignment.ALIGN_CENTER);
}
return dayNamesPanel;
}
Jak widać uzyskanie żądanego układu komponentów nie powinno sprawić problemów programiście posiadającemu chociaż podstawowe doświadczenia z takimi bibliotekami GUI jak Swing czy SWT. Bardzo pomocne są metody setCellWidth() czy setCellHorizontalAlignment(), pozwalające ustawić rozmiar i ułożenie osobno dla każdego elementu.
Kolejne dwie metody tworzą panel przedstawiający dni miesiąca:
private Widget createMonthPanel() {
VerticalPanel monthPanel = new VerticalPanel();
monthPanel.setWidth("100%");
monthPanel.setSpacing(3);
Date firstDay = new Date(currYear, currMonth, 1);
Date currDay = new Date(firstDay.getTime() - firstDay.getDay() * DAY);
monthPanel.add(getWeekPanel(currDay));
while ((currDay = new Date(currDay.getTime() + WEEK)).getMonth() == firstDay.getMonth()) {
monthPanel.add(getWeekPanel(currDay));
}
return monthPanel;
}
private Widget getWeekPanel(Date date) {
HorizontalPanel weekPanel = new HorizontalPanel();
weekPanel.setWidth("100%");
weekPanel.setSpacing(3);
Date firstDay = new Date(date.getTime() - date.getDay() * DAY);
for (int i = 0; i < 7; i++) {
Date btnDate = new Date(firstDay.getTime() + i * DAY);
Button btn = new Button(String.valueOf(btnDate.getDate()));
if (btnDate.getMonth() != currMonth) {
btn.setEnabled(false);
}
btn.setWidth("100%");
weekPanel.add(btn);
weekPanel.setCellWidth(btn, "14%");
}
return weekPanel;
}
W powyższych metodach nie ma w zasadzie niczego nowego, poza nieco karkołomnym żonglowaniem datami. Jeśli zastanawiasz się, dlaczego konsekwentnie używamy oznaczonych jako deprecated metod klasy Date, zamiast użyć znacznie wygodniejszej klasy Calendar, odpowiedź brzmi: klasa Calendar nie istnieje. A właściwie: klasa Calendar należy do tej znaczącej części pakietu java.util, której GWT nie implementuje. Pozostaje więc zacisnąć zęby i radzić sobie z tym, co mamy.
Ale wróćmy do naszej aplikacji. Mamy już wszystko co potrzeba do stworzenia interfejsu. Efekt możemy obejrzeć uruchamiając nasz projekt (bezpośrednio za pomocą Eclipse’a lub za pomocą skryptu Calendar-shell.cmd).
Teraz, gdy mamy stworzony interfejs, czas tchnąć życie w naszą stronę WWW. Do obsługi zdarzeń GWT wykorzystuje wzorzec Listener, dobrze znany np. ze Swinga. Na początek dodajmy interfejs com.google.gwt.user.client.ui.ClickListener do deklaracji naszej klasy Calendar:
public class Calendar implements EntryPoint, ClickListener {
...
}
Następnie dopiszmy parę metod odpowiedzialnych za obsługę zdarzeń:
public void onClick(Widget sender) {
String text = ((Button) sender).getText();
if ("<<".equals(text)) {
prevMonth();
} else if (">>".equals(text)) {
nextMonth();
} else {
Window.alert(text + "-" + (currMonth + 1) + "-" + (currYear + 1900));
}
}
private void prevMonth() {
if (currMonth > 0) {
currMonth--;
} else {
currYear--;
currMonth = 11;
}
refreshMonthPanel();
}
private void nextMonth() {
if (currMonth < 11) {
currMonth++;
} else {
currYear++;
currMonth = 0;
}
refreshMonthPanel();
}
private void refreshMonthPanel() {
monthLabel.setText((currYear + 1900) + "-" + (currMonth + 1));
mainPanel.remove(monthPanel);
mainPanel.add(monthPanel = createMonthPanel());
}
Na koniec musimy podpiąć obsługę kliknięć do stworzonych przycisków:
private Widget createHeaderPanel() {
...
Button prevButton = new Button("<<");
prevButton.setWidth("100%");
prevButton.addClickListener(this);
...
Button nextButton = new Button(">>");
nextButton.setWidth("100%");
nextButton.addClickListener(this);
...
}
private Widget getWeekPanel(Date date) {
...
for (int i = 0; i < 7; i++) {
...
Button btn = new Button(String.valueOf(btnDate.getDate()));
btn.addClickListener(this);
...
}
W ten sposób dotarliśmy do końca, nasza przykładowa aplikacja jest gotowa. Wystarczy uruchomić skrypt Calendar-compile.cmd, a w katalogu WWW pojawi się nasza aplikacja przetłumaczona na HTML i JavaScript, gotowa do uruchomienia w dowolnej przeglądarce WWW lub umieszczenia na serwerze.
Podsumowanie
Oczywiście zaprezentowana aplikacja pokazuje tylko skromny wycinek możliwości GWT. Nie wspomnieliśmy o prostej komunikacji z serwerem za pomocą RPC, ułatwionej internacjonalizacji czy bezpośrednich wstawkach JavaScript za pomocą JavaScript Native Interface (JSNI). Jednak już tak prosty przykład pokazuje jak łatwo można tworzyć dynamiczne strony WWW używając jedynie Javy. Jeśli dodatkowo wierzyć zapewnieniom autorów o kompatybilności stworzonego kodu ze wszystkimi wiodącymi przeglądarkami WWW oraz o wydajności porównywalnej z ręcznie tworzonymi stronami AJAX, wówczas warto bliżej zainteresować się frameworkiem Google Web Toolkit.








September 30th, 2007 at 15:04
Ja od siebie dodam tylko:
http://mygwt.net/
September 30th, 2007 at 21:40
No proszę, proszę :)
Widzę, że powolutku robi się tu całkiem fajna kolekcja interesujących wpisów.
Tak trzymać !!!
Pozdrawiam,
Radek
October 13th, 2007 at 22:40
Polecam zapoznać się z artykułem dotyczącym Drag&Drop w GWT dostępny na blogu http://coderlife.blogspot.com/2007/10/drag-w-gwt.html
January 11th, 2008 at 14:36
Ja tez cos dorzucę: http://code.google.com/p/gwt-ext/
January 18th, 2008 at 23:04
http://code.google.com/p/tatami/ dojo toolkit + gwt
January 15th, 2009 at 21:18
Witam.
Niestety przykład nie działa. Eclipse wywala błędy obsługi dat. Sprawdziłem i wykorzystywane funkcje zostały już wycofane i trzeba skorzystać z pakietu java.util.Calendar Niestety dopiero poznaje Jave i nie moge sobie poradzić z podmianą starych funkcji na nowe.
Pozdrawia
July 18th, 2010 at 14:01
Tworzenie aplikacji w GWT za pomocą szablonów HTML zamiast tworzenia wszystkich elementów okienek na wzór Java Swing.