Kolejny temat, który zapewne nie brzmi ekscytująco, ale należy go przerobić i być może wynieść coś z T5 do codziennej pracy.

Zacznijmy od ogólnego opisu powiązań pomiędzy klasą a templatem. Wiemy już, że strona składa się z 2 nierozłącznych elementów: .java i .tml. Plik java musi znajdować się w pakiecie pages, bądź w jednym z jego podpakiecie. Plik tml to inna historia, ale przyjąć należy, że ma znajdować się w tym samym pakiecie, wtedy najłatwiej je znaleźć (jeśli korzystamy z mavena to nie wrzucamy tmli do src/java, chyba że modyfikujemy delikatnie pom.xml by maven wiedział, że ma nie tylko kompilować źródła, ale też kopiować tmle). Dzięki temu szybko możemy odnaleźć interesującą nas stronę, jest to przejrzysty sposób oznaczenia przez jaki 'kontroler' (w uszach bo tapestry jest Page Oriented i nie wiem czy można uprościć to aż tak, i strony nazwać kontrolerami). W projekcie, nad którym obecnie pracuję, a który odziedziczyłem po kolegach, pierwszą czynnością przed każdą zmianą, jest wycieczka do servlet-MVC.xml i sprawdzenie na jakiej jestem stronie i jaki kontroler ją obsługuje. Z poprzednich projektów w springu/faceach/strutsie mam podobne doświadczenia więc to nie jest jakiś odosobniony przypadek. W T5 tego nie uświadczymy, na szczęście.

No, ale teraz wypadało by się jakoś zacząć poruszać po aplikacji i przechodzić pomiędzy stronami, bo jedna strona to będzie trochę za mało dla większości projektów ;)

Zaczynamy od otwarcia Index.tml gdzie wpisujemy na początek <a href="http://localhost/aboUt">anchor</a> Zakładając, że serwer nasłuchuje na porcie 80, ten link przeniesie nas do strony about. Alternatywnie podajemy <a href="/about">anchor</a> co uczyni link tyć sprytniejszym. Jak zgaduję, to nie robi wrażenia. No więc na stronie dodać możemy też <t:pagelink page="About">page link</t:pagelink>. Page link to jeden z podstawowych komponentów tapestry (o samych komponentach i czym są będzie za kilka wpisów). Wszystko co znajduje się w przestrzeni nazw tapestry (w headerze strony definiujemy przestrzeń nazw jako xmlns:t="http://tapestry.apache.org/schema/tapestry_5_1_0.xsd") jest obrabiane przez Tapestry i na tych elementach działa T5 magic ;)

Wracając do Pagelinka. Nie zagłębiając się w jego dokumentację za bardzo, uznajmy, że posiada on jedynie atrybut page. Podajemy w nim nazwę stronę do której chcemy przejść a tapestry zajmie się całą resztą. Można tworzyć linki dynamicznie w takie sposób <t:pagelink page="${nextPage}">page link</t:pagelink> i w klasie javowej dostarczyć metodę getNextPage. Pozwoli nam to do woli manipulować celem odnośnika, ale tak wiem, dalej nie jest to nic wybitnego, w końcu tyle można zrobić choćby scripletami czy JSTLami, tyle że w brzydszy sposób.

Przejdźmy więc do kolejnego  przypadku, gdy chcemy wykonać jakąś czynność przed przejściem na kolejną stronę (np zapisać coś do DB). Wtedy idealnym rozwiązaniem okazuje się ActionLink bądź EventLink. Ten drugi działa podobnie jak pierwszy i póki nie zagłębimy się mocno w  tapestry to będzie nam zbędny, teraz zwracam tylko uwagę, że takie cóś istnieje. Zajmiemy się za to ActionLinkiem. W kodzie dodajemy kilka następujących linijek.


About.tml
<t:actionLink t:id="calculateTax">Do something</t:actionLink>

About.java
	Object onActionFromCalculateTax() {
		// complex calculations
		return Index.class;
	}

Jest to najprostszy możliwy przykład. To co się stanie na stronie, to po kliknięciu w link zostanie wygenerowany Event o nazwie Action z komponentu o nazwie takiej samej jak id tego komponentu. Actionlink zawsze generuje event typu action, czym się różni od EventLinka każdy się już chyba domyśla. Po wygenerowaniu eventu, coś musi go przechwycić i to coś wypada oznaczyć. Posłużyłęm się tu jednym z dwóch znanych mi sposobów: konwencją nazw. Wspominałem już, że tapestry 5 to convention over configuration, a to kolejny przykład. Nazwy w tapestry mogą mieć wielkie znaczenie, przeanalizujmy ogólną składnię. on{NazwaEventu}From{NazwaKomponentuBranaZJegoId} Wszystkie części nazwy metody są obowiązkowe, nawiasy dodałem by łatwiej było zauważyć poszczególne części. Dla mnie jest to wyjątkowo czytelne: na określoną akcję od  komponentu wykonaj ciało metod. W naszym przypadku jest to na akcje od calculateTax, przeprowadź złożone obliczenia i przejdź na kolejną stronę, gdzie calculateTax jest brane z atrybutu t:id.

Zwracane wartości też mają znaczenie. Tu posługuję się silnym dowiązaniem, poprzez klasę. Jest to jedna z preferowanych przeze mnie metod nawigowania, druga to zwracanie obiektu na którym chcę pracować, taki sposób uznaję za najlepszy gdy chcę przekazać do kolejnej strony wartości (to w następnej części nawigowania). Inne możliwości to zwrócenie Stringa zawierającego nazwę strony do której chcemy przejść, obiektu URL, specjalnej klasy Tapestry link bądź  StreamResponse gdy chcemy na przykład wysłać plik do użytkownika. Metody zwracające nic (void czy null), same siebie (this bądź .class samej siebie) oraz chyba bollowskie false, oznaczają, że zostajemy na tej samej stronie i jeśli żądanie nie było Ajaxowe to przeładowujemy stronę.

Dlaczego preferuję pierwsze dwa sposoby? Miałem w swojej, stosunkowo krótkiej, karierze przypadek gdy klient poprosił o drobną zmianę w aplikacji. Wszystkie linki zawierające słowo user miały zwać się client. Refaktoryzacja tego i upewnienie się w sporym środowisku, że żadna strona nie nazywa się jak nie powinna, było piekłem. Pewnie dałoby się jakoś sprytniej to filtrować, ale postanowione zostało, że wszystko trzeba renamować. Gdybym miał w projekcie Tapestry i zwracane obiekty .class bądź konkretne instancje, to cała procedura skończyła by się na alt+f6 (bo pracuję w Intellij) bądź alt+shift+r (gdy w domu stukam na eclipsie) zmianie nazwy/pakietu, a framework nie widziałby żadnej różnicy. Wprawdzie można zmieniać po regexp bądź wyszukując danego stringa, ale przy popularnych frazach, gdy występuje dane słowo z 10* na każdej stronie, staje się to upierdliwe. Należy również uważać, by nie popsuć aplikacji. No i ostatnia sprawa: wspominana przejrzystość kodu. Jest on transparentny i czytelny na pierwszy rzut oka. Jeśli zwracam instancję danej strony to wiem gdzie muszę jej szukać i gdzie powinienem się dalej udać. Nie przeglądam bzdurnego XMLa w poszukiwaniu mapowań, nie sprawdzam navigation rules, etc, etc. Wszystko jest zebrane w jednym miejscu, tak bym ja nie musiał tracić czasu i nerwów na bzdury, typu co oznacza, że strona zwraca stringa "success"...

Do nawigacji na pewno jeszcze powrócę, wtedy też sprawdzimy Tapestrowe URLe, ale to za minimum 1 wpis.