Java

Własne Komponenty GWT



Wstęp


Tworząc aplikacje webowe zazwyczaj stajemy w pewnym momencie przed zadaniem stworzenia bardziej skomplikowanego elementu interfejsu użytkownika. Zazwyczaj gdy element taki powtarza się w kilku miejscach staramy się stworzyć moduł, który będzie abstrakcyjnym i elastycznym rozwiązaniem.
GWT dostarcza nam bardzo dobrych narzędzi do tworzenia własnych komponentów na strony WWW. Za pomocą dostarczonych z biblioteką standardową klas w stosunkowo prosty sposób możemy stworzyć własne bardzo rozbudowane elementy strony. W tym artykule zaprezentuję jak stworzyć własny komponent - okno informacyjne.

Coś łatwego na początek


Zanim weźmiemy się za właściwe zadanie należy pokrótce opisać zasady tworzenia własnych komponentów w GWT. Zakładam, że wiesz jak stworzyć projekt GWT czy to za pomocą ulubionego IDE, czy też mavena. Jeżeli nie, najpierw zapoznaj się z artykułem podstawy GWT. Zaczynamy!

Stwórz projekt GWT. Następnie stwórz klasę w pakiecie twój_pakiet.client. Ja nazwałem ją CustomComposite. Klasa musi rozszerzać klasę Composite.
package pl.koziolekweb.programmers.gwt.customcomposite.client;
 
import com.google.gwt.user.client.ui.Composite;
 
public class CustomComposite extends Composite {
//...        
}


Komponenty GWT są rodzajem elementu, który służy do grupowania prostszych elementów w bardziej złożone struktury. Naturalnym sposobem projektowania tego typu klas jest delegowanie funkcjonalności do prostszych elementów. W naszym przypadku komponent będzie zachowywał się tak samo jak element klasy Label.
package pl.koziolekweb.programmers.gwt.customcomposite.client;
 
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.Label;
 
public class CustomComposite extends Composite {
 
        private Label mainPanel = new Label();
        private String comunicate;
 
        public CustomComposite(String comunicate) {
                this.comunicate = comunicate;
                initWidget(this.mainPanel);
                this.mainPanel.setText(this.comunicate);
        }
        //...
}


Klasa ta jest bardzo prosta. Najważniejszym elementem dla własnych komponentów jest wywołanie metody initWidget, której jako argument przekazujemy główny kontener, w którym są poszczególne elementy naszego komponentu. Wynika to z tego, że obiekt klasy Composite opakowuje (wraps) inny element. Metoda ta musi być wywołana zaraz po zainicjowaniu osłanianego elementu.

Dodajmy nasz nowy element do głównej klasy aplikacji...
package pl.koziolekweb.programmers.gwt.customcomposite.client;
 
import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.user.client.ui.RootPanel;
 
public class Application implements EntryPoint {
 
        public void onModuleLoad() {
                CustomComposite customComposite = new CustomComposite("My comopsite");
                RootPanel.get().add(customComposite);
        }
}

...i uruchommy ją w trybie testowym.

Jak widać efekt nie jest imponujący, ale w ten sposób możemy "załapać" jak tworzyć własne komponenty.

Własny komponent - Okno informacyjne


Przejdźmy zatem do naszego głównego zadania. Okno informacyjne będzie elementem osadzonym na stronie www. Będzie pojawiać się we wskazanym miejscu np. komórce tabeli w wyniku jakiegoś zdarzenia. Przyjmijmy, że okno może zawierać trzy rodzaje informacji. Informacje o błędach, informacje o udanych operacjach i informacje neutralne. Dodatkowo po kliknięciu okno będzie znikać.

Klasa AbstractInfoComposite
------------------------------------------------

Nasz komponent posiada trzy różne sposoby zachowania zależne od tego jaki rodzaj komunikatu będzie zawierał.
Możemy zatem w najprostszym podejściu za pomocą serii instrukcji warunkowych stworzyć odpowiedni obiekt i mieć tylko jedną klasę.
Możemy stworzyć trzy różne komponenty i na użytkownika przerzucić tworzenie odpowiedniego obiektu. W takim przypadku mamy trzy niezależne klasy.
Możemy w końcu stworzyć klasę abstrakcyjną, co i zaraz zrobimy, w której zbudujemy odpowiedni obiekt, a klasy ją rozszerzające będą tylko dostarczać pewnych elementów implementacji. Podejście takie nazywane jest [[Inżynieria_oprogramowania/Wzorce_projektowe/Strategia|Wzorcem Strategii]].

Od strony wizualnej każdy z komunikatów będzie składać się z tytułu, ikony oraz tekstu. Tytuł umieścimy na środku ramki przy jej górnej krawędzi, ikona będzie umieszczona poniżej po lewej stronie. Komunikaty będą występować na tle różnego koloru. Stwierdzamy zatem, że elementami, które muszą być dostarczane przez każdy z komponentów z osobna są:
# Obrazek, czyli obiekt klasy Image.
  1. Tytuł, który będzie zależał od rodzaju komunikatu.
  2. Własny styl CSS, za pomocą którego będą definiowane kolory, krój czcionek itp.

Mamy zatem jasno określone elementy abstrakcyjne, które będą dostarczane przez poszczególne implementacje. W oparciu o te informacje możemy przystąpić do pisania kodu. Tworząc własny komponent należy pamiętać, że większość pracy wykonywana jest w konstruktorze. To właśnie tam budowane są zależności pomiędzy poszczególnymi elementami interfejsu. Tam też dodawane są listenery i ładowane dane.
package pl.koziolekweb.programmers.gwt.customcomposite.client;
 
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Image;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.VerticalPanel;
 
public abstract class AbstractInfoComposite extends Composite {
 
        private VerticalPanel mainPnl;
        private HorizontalPanel messagePnl;
 
        public AbstractInfoComposite(String comunicate) {
                mainPnl = new VerticalPanel();
                initWidget(mainPnl);
                messagePnl = new HorizontalPanel();
                Label titleLbl = new Label(getTitle());
                mainPnl.add(titleLbl);
                Label messageLbl = new Label(comunicate);
                messagePnl.add(getImage());
                messagePnl.add(messageLbl);
                mainPnl.add(messagePnl);
 
                mainPnl.setStylePrimaryName(getStyleName() + " info-composite");
                titleLbl.setStylePrimaryName("info-composite-title");
                messageLbl.setStylePrimaryName("info-composite-msg");
        }
 
        public abstract String getTitle();
 
        public abstract Image getImage();
 
        public abstract String getStyleName();
}



Klasa ErrorInfoComposite
------------------------------------------------

Mając kod naszego komponentu stwórzmy element zawierający informację o błędzie. Jest to bardzo prosty kod, który ogranicza się do implementacji trzech metod abstrakcyjnych. Nie musimy nic więcej robić.
package pl.koziolekweb.programmers.gwt.customcomposite.client;
 
import com.google.gwt.user.client.ui.Image;
 
public class ErrorInfoComposite extends AbstractInfoComposite {
 
        public ErrorInfoComposite(String comunicate) {
                super(comunicate);
        }
 
        @Override
        public Image getImage() {
                return new Image("msg-error.gif");
        }
 
        @Override
        public String getStyleName() {
                return "info-error";
        }
 
        @Override
        public String getTitle() {
                return "BŁĄD!";
        }
 
}


Najważniejszym elementem nie jest jednak kod Java, ale kod css:
.info-composite {
}
.info-composite img {
        margin: 15px;
}
 
.info-composite-title {
        font-weight: bold;
        text-align: center;
        padding: 3px 0;
}
.info-composite-msg{
        padding: 5px 0;
}
.info-error {
        border-top: 3px solid #ff5566;
        border-right: 3px solid #ff5566;
        border-bottom: 3px solid #ff3355;
        border-left: 3px solid #ff3355;
        width: 250px;
}
 
.info-error .info-composite-title {
        color: #FFF;
        background-color: #ff5566;
}

Dlaczego? Ponieważ to właśnie kod CSS pozwala na określenie najwyraźniejszych różnic pomiędzy poszczególnymi elementami. Końcowy efekt jest uzależniony w dużej mierze od tego właśnie kodu.
{{Image:gwt_custcom3.png}}
Czytelnikowi jako zadanie samodzielne pozostawiam implementację pozostałych klas.

Dodawanie zdarzeń
--------------------------------------

Ze specyfikacji została nam jeszcze jeden element do zaimplementowania. Jest to obsługa zdarzeń. Dokładnie chodzi nam o to by komponent zniknął po kliknięciu na niego. Zadanie to pomimo pozornej trudności jest bardzo proste. Ogranicza się do implementacji interfejsu HasClickHandlers oraz niewielkiej zmiany w kodzie konstruktora klasy abstrakcyjnej - dodaniu handlera.
package pl.koziolekweb.programmers.gwt.customcomposite.client;
 
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.HasClickHandlers;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Image;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.VerticalPanel;
 
public abstract class AbstractInfoComposite extends Composite implements HasClickHandlers {
 
        private VerticalPanel mainPnl;
        private HorizontalPanel messagePnl;
 
        public AbstractInfoComposite(String comunicate) {
                mainPnl = new VerticalPanel();
                initWidget(mainPnl);
                messagePnl = new HorizontalPanel();
                Label titleLbl = new Label(getTitle());
                mainPnl.add(titleLbl);
                Label messageLbl = new Label(comunicate);
                messagePnl.add(getImage());
                messagePnl.add(messageLbl);
                mainPnl.add(messagePnl);
 
                this.addClickHandler(new ClickHandler() {
 
                        public void onClick(ClickEvent arg0) {
                                removeFromParent();
                        }
                });
 
                mainPnl.setStylePrimaryName(getStyleName() + " info-composite");
                titleLbl.setStylePrimaryName("info-composite-title");
                messageLbl.setStylePrimaryName("info-composite-msg");
        }
 
        public HandlerRegistration addClickHandler(ClickHandler handler) {
                return addDomHandler(handler, ClickEvent.getType());
        }
 
        public abstract Image getImage();
 
        public abstract String getStyleName();
 
        public abstract String getTitle();
 
}


Podsumowanie


Implementacja komponentu interfejsu użytkownika z wykorzystaniem klasy Composite jest bardzo prosta. Pamiętać trzeba tylko o wywołaniu metody initWidget. Reszta robi się sama.