Wicket je komponentno-orjentisani web-frejmvork, koji poslednjih meseci beleži primetan rast popularnosti među Java programerima u svetu. Uprkos tome, na internetu i dalje postoji mali broj tutorijala i članaka koji bi pomogli da se širi krug ljudi upozna sa ovim frejmvorkom. Ovaj članak nastoji da promeni tu situaciju, barem što se tiče naše zajednice.
Budući da je reč o mladom frejmvorku, istorija Wicket API-ja je krakta i ne preterano dinamična. Autor Wicket-a (ali ne i jedini programer) je Jonathan Locke, član tima koji je radio Swing, pa nije slučajno što korišćenje API-ja mnoge podseća na Swing programiranje. Prva verzija je objavljena u junu prošle godine, a poslednja stabilna verzija 1.1 je izašla pre samo nekoliko dana.
Iako na ovom polju predstavlja direktnu konkurenciju Tapestry-ju i "industrijskom-standardu-samo-što-nije" - JSF-u, zanimljivo je da programeri Wicketa ističu da ciljna grupa za Wicket nisu korisnici ova dva frejmvorka. Po njihovim rečima to je ona brojnija grupa programera koja i dalje koristi Model-2 arhitekturu i frejmvorke, i koje Tapestry i JSF nikako da "preobrate", pre svega zbog poznate kompleksnosti koje nose sa sobom. Iz ovoga se jasno vidi da je Wicket-ov najjači adut - jednostavnost.
Iako JSP dominira u Model-2 arhitekturi, pokazuje se da je njegova budućnost neizvesna u komponentno-orjentisanim frejmvorcima, koji dolaze. Član expertske grupe zadužene za JSF - Hans Bergsten već je pisao o problemima integracije JSP-a i JSF-a. Manje poznati komponentni frejmvorci poput Echo2 i WebOnSwing, takođe ne koriste JSP. Ni najkorišćeniji komponenti web framework u Javi danas - Tapestry, nije izuzetak. Jonathan Locke, priznaje da je prilikom razvoja Wicket-a bio inspirisan upravo rešenjem iz Tapestry-ja.
U Wicket-u se za prikaz komponenti koristi običan HTML. Veza Java kooda i HTML-a se ostvaruje preko vrednosti obaveznog argumenta id u konstruktoru komponenata, i dodatnog atributa wicket:id koji se dodaje na standardne tagove HTML-a.
Da bi ste napravili web stranicu u Wicketu potrebno je napraviti dva fajla, koji bi po konvenciji trebalo da nose isti naziv(naravno sa različitim ekstenzijama), i nalaze na istoj lokaciji(u odgovarajućim paketima). Jedan je Java fajl u kojem se definiše klasa koja će predstavljati konkretnu stranicu web aplikacije i ona mora biti hijerarhijski podklasa klase WebPage. Drugi fajl je HTML templejt fajl, kojeg je zbog toga moguće razviti bez servlet/jsp kontejnera. Evo primera:
| PollEntryPage.java | PollEntryPage.html |
|---|---|
public class PollEntryPage extends WebPage { |
<html>
...
<body>
...
<form wicket:id="form">
...
</form>
...
</body>
</html>
|
Gore spomenuta veza između Jave i HTML-a u datom primeru je "form" koji predstavlja jedinstveni identifikator komponente na stranici.
Da bi se komponenta "izrenderovala" potrebno je da wicket:id atribut bude u odgovarajućem tagu sa odgovarajućim atributom, u zavisnosti od klase komponente koju predstavlja. Već je prikazano da se komponenta Form "uparuje" sa FORM tagom. U tabeli je prikazana ta zavisnost još nekih komponenti i HTML tagova:
| Java klase komponenata | HTML markup tagovi |
|---|---|
| Label | SPAN, TD |
| ListView | SPAN |
| Link | A |
| TextArea | TEXTAREA |
| TextField | INPUT TYPE="TEXT" |
| CheckBox | INPUT TYPE="CHECKBOX" |
| RadioChoice | INPUT TYPE="RADIO" |
| Button | INPUT TYPE="SUBMIT" |
| DropDownChoice | SELECT |
| ListChoice | SELECT |
Takođe, neophodno je i da na web stranici, hijerarhija komponenti bude ista kao i hijerarhija tagova koji ih predstavljaju. To se najbolje vidi na primeru pravljenja forme:
public class PollEntryPage extends WebPage {
public PollEntryPage() {
// forma se dodaje stranici
add(new PollEntryForm("form", new PollItem()));
}
private class PollEntryForm extends Form {
public PollEntryForm(String s, PollItem pollItem) {
...
TextField income = new TextField("income");
add(income); // polje za unos se dodaje formi,
// i na taj način pravi hijerarhijska
// struktura komponenti<html> ... <form wicket:id="form"> ... <!-- tagovi se "gnezde" u skladu sa hijerarhijom komponenti --> <input type="text" wicket:id="income" /> ...
Wicket ima kvalitetan izveštaj o greškama što je kod web razvoja veoma važno. Ako na primer za id komponente napišete "buisnes" umesto "buisness"(kao što je vrednost wicket:id) dobićete upotrebljiv izveštaj o grešci.
Ipak, neke komponente nemaju ekvivalente tagove u HTML-u i da bi se izbeglo ponavaljanje istog Java kooda, programeri Wicket-a su se odlučili za uvođenje specijalnih tagova za neke specifične komponente. Ti tagovi nemaju wicket:id atribut, što znači da u Java klasama stranica nema njihovog pravljenja i dodavanja na stranicu. To se dešava automatski.
Ova nepricipijelnost se nekima ne dopada, ali će se u narednim primerima nekih takvih tagova videti dobit od ovakvog pristupa:
<wicket:link><a href="PollEntryPage.html">Novo anketiranje...</a></wicket:link>
Zajedno sa wicket:extend tagom, ovaj tag se može iskoristiti za lako pravljenje kostura sajta. Najpre definišemo kostur svih stranica na sajtu:
| BasePage.java | BasePage.html |
|---|---|
public class BasePage extends WebPage {
|
<html>
<head>
<title>JavaSvet</title>
<meta http-equiv="Content-Type"
content="text/html; charset=utf-8">
<link rel="stylesheet" type="text/css" href="styles.css"/>
</head>
<body>
<center>
<img src="javasvet-logo.gif"/>
<img src="wicket-logo.png"/>
<wicket:child/>
</center>
</body >
</html>
|
Na mestu gde je tag wicket:child, nalaziće se sadržaj one strane koja nasledi BasePage.
Sada za svaku stranu na sajtu nasleđujemo BasePage a u HTML templejtu stranice, se navodi samo ono što je specifično u odnosu na kostur svih stranica, i to se uokviruje wicket:extend tagom
| PollResultsPage.java | PollResultsPage.html |
|---|---|
public class PollResultsPage extends BasePage {
|
<wicket:extend> ... ovde ide specifičan sadrzaj stranice ... </wicket:extend> |
Ovaj tag će ukloniti sve ono što okružuje. Koristi se kako bi web dizajner pravio "mock" delove stranica (radi bržeg starta razvoja i lakšeg rada - bez web servera), a koje će u toku rada aplikacije biti beskorisne. Primer za to su strukture koje se dobijaju iteracijama prikazivanja podataka.
List results = dao.findAllPollItems();
ListView resultsView = new ListView("results", results) {
protected void populateItem(ListItem listItem) {
PollItem item = (PollItem) listItem.getModelObject();
listItem.add(new Label("voted", dateFormat.format(item.getVoted())));
listItem.add(new Label("position", item.getPosition()));
listItem.add(new Label("business", item.getBusiness()));
...
}
};
<span wicket:id="results"> <tr> <td wicket:id="voted">11/6/2005</td> <td wicket:id="position">Junior programer</td> <td wicket:id="business">Razvoj softvera</td> ... </tr> <wicket:remove> <tr> <td>11/6/2005</td> <td>Senior programer</td> <td>Banka</td> ... </tr> </wicket:remove> </span>
Kao što se iz primera može videti neke komponente koje se predstavljaju "običnim" tagovima(u primeru Label) zamenjuju "mock" sadržaj koji se nalazi između tih tagova, sa pravim podacima.
Sve Wicket-ove komponente interno koriste jedan isti interfejs(IModel) za čuvanje svog stanja. To je praktično omotački interfejs oko bilo kojeg POJO objekta, koji će komponenta koristiti da bi čuvala svoje stanje.
Jedna od glavnih zamerki koje stižu na račun Wicket-a je čuvanje stanja komponenti u sesiji, što može dovesti do velikih sesija, kao i problema prilikom rada u cluster okruženjima. Međutim, strategijom proglašavanja polja POJO objekta transientnim, kao i korišćenjem AbstractDetachableModel-a, to se može kontrolisati.
Jonathan Lockea objašnjava ovakav pristup činjenicom da većina današnjih frejmvorka funkcioniše po principu "one size fits all" jer daje mogućnost pravljenja Yahoo.com-a, ali primorava korisnika da koristi isti metod i za pravljenje trivijalne intranet aplikacije. Za razliku od toga Wicket korišćenjem jednostavnosti session-based kooda dozvoljava brz start u razvoju aplikacije, a opet ostavlja mogućnost kasnijeg tuniranja po potrebi.
Sledi kratak pregled nekih implementacija IModela, a za njihovo dublje razumevanje pogledajte http://www.wicket-wiki.org.uk/wiki/index.php/Models.
Model je najjednostavanija implementacija IModel interfejsa, koja je samo omotač oko POJO objekta koji mora biti serijabilan, pošto se smešta u sesiju. Evo kako komponenta Label koristi Model klasu
public Label(final String id, String label) {
this(id, new Model(label));
}... new TextField("username", new PropertyModel(person, "username"))CompoundPropertyModel se može dodeliti formi, i podrazumeva da je id komponenti koji je čine, istovremeno i atribut modela. Na taj način model "dele" različite komponente.
public PollEntryForm(String id, final PollItem pollItem) {
super(id);
this.pollItem = pollItem;
CompoundPropertyModel formModel = new CompoundPropertyModel(pollItem);
setModel(formModel);
add(new DropDownChoice("business", dao.findAllBusiness()));
add(new DropDownChoice("position", dao.findAllPositions()));
add(new TextField("income"));
...TextField income = new TextField("income")
income.add(RequiredValidator.getInstance());
income.add(IntegerValidator.POSITIVE_INT);U ovom primeru, validatori nameću ograničenje da se nešto mora uneti u polje za unos, i da to mora biti pozitivan, ceo broj. U tabeli je dat kratak pregled predefinisanih validatora u Wicket-u:
| IntegerValidator | Validator proverava da li je String vrednost komponente ceo broj, i ako jeste da li je u zadatom intervalu. |
| LengthValidator | Validator minimalne i maksimalne dužine ulaza. |
| PatternValidator | Koristi snagu regularnih izraza, a istovremeno pokušava da smanji njihovu kompleksnost korišćenjem MetaPattern-a. |
| EmailAddressPatternValidator | Ovaj validator je podklasa PatternValidator-a. Naziv sve govori. |
| RequiredValidator | String vrednost komponente kojoj se dodeli ovaj validator ne sme biti prazan. |
| TypeValidator | Proverava da li je moguće ulaznu vrednost konvertovati u neki tip koristeći interfejs komponente IConverter. |
| CustomValidator | Koristi se u slučaju kada nijedan od predefinisanih validatora nije adekvatan. Klasa je dizajnirana da bude nasleđena, kako bi se implementirala onValidate() metoda. |
Rezultati validacije automatski ulaze u Feedbacktoc komponentu, koja se na stranicu dodaje na standardan način
| PollFormPage.java | PollFormPage.html |
|---|---|
public class PollFormPage extends WebPage {
|
<html> <body> <span wicket:id="feedback">Feedback stuff</span> ... |
Tekst koji treba da bude ispisan u slučaju da validacija ne prođe, treba da se nalazi(ponovo konvencija) u properties fajlu koji je istog naziva kao i stranica na kojoj se nalazi forma. U slučaju navedenog primera, sadržaj tog fajla je:
form.income.RequiredValidator=Morate uneti primanja form.income.IntegerValidator=Primanja moraju biti ceo, pozitivan broj
public final class UserSession extends WebSession {
public UserSession(WebApplication application) {
super(application);
}
public void login(User user) { ... }
public void logout() { ... }
public boolean isUserLoggedIn() { ... }
...public class RegisteredUsersOnlyPage extends WebPage {
protected UserSession getUserSession() {
return (SignInSession)getSession();
}
protected boolean isUserLoggedIn() {
return getUserSession().isUserLoggedIn();
}
...Wicket za sada ima eksperimentalnu podršku za AJAX, što znači da je ona u fazi razvoja i testiranja. I pored toga, već su počele da se pojavljuju prve komponente koje omogućavaju transparentno korišćenje ove sve popularnije tehnike, što govori da je razvoj podrške za AJAX na pravom putu. Jedna od glavnih najavljivanih unapređenja u sledećoj verziji Wicketa je upravo na ovom planu.
Ostaje da vidimo da li će Wicket sa kvalitetima koje već poseduje i sa dodatnim unapređenjima, uspeti da ostvari cilj sa početka članka - priklanjanje širokog kruga programera komponento-orjentisanoj arhitekturi korisničkog interfejsa web aplikacija.
Naravno, nemoguće je jednim člankom obuhvatiti sve ono što jedan web-frejmvork donosi, ali se nadam da će ovaj tekst i ponuđeni linkovi uspeti da vas dovoljno zainteresuju da istražujete dalje. Možda tako Wicket postane realna opcija za neki manji projekat koji vas čeka.