People and technology
RSS Feed

Articles

  • Nawigacja w T5 part II

    Umiemy już przemieszczać się pomiędzy stronami oraz zatrzymywać ich stan. Czas więc pomyśleć o przekazywaniu wartości pomiędzy różnymi stronami. Przykład? W każdej aplikacji się znajdzie, od wypełnienia przelewu po nasz: stronę z wynikiem naszej gry (BTW sam pomysł na guess game wziąłem z oficjalnego tutoriala Tapestry).

    No to zdefiniujmy nasze nowe wymaganie. Grający po kliknięciu w link z liczbą ma zostać przeniesiony na stronę z wynikiem. Jeśli poprawnie wytypował liczbę to system ma go o tym powiadomić oraz umożliwić powrót na stronę z grą, by mógł zacząć grać od nowa. Jeśli wytypował niepoprawnie, to ma zostać o tym poinformowany oraz ma być mu wyświetlony link pozwalający grać dalej. Use case prosty. Zaczynamy kodzić :) Na początek stwórzmy stronę z wynikiem (kod tml-owy pomijam, jako że wszystko do javy wsadzę, a  jak linki się tworzy i wypisuje wartości to każdy już wie). Na stronie wyniku wstawiamy trzy property: int playerGuess, boolean correctAnswer; Pierwsza własność będzie nam potrzebna do wyświetlenia na ekranie wyboru osoby grającej, druga oznaczać będzie tylko czy to co user podał jest prawidłowe czy nie. Do tego mam 2 stringi do wyświetlenia graczowi odpowiedniego komunikatu i odpowiedniej wartości linka. Na pierwotnej stornie dodajemy następujące pole: @InjectPage private GuessResult resultPage;
    Zacznijmy od tego najmniej użytecznego sposobu ;) GuessGame.java modyfikujemy w następujący sposób.

    [sourcecode language="java"]
    Object onActionFromGuess(int guess) {
    final boolean isCorrectAnswer = numberToGuess == guess;
    resultPage.setCorrectAnswer(isCorrectAnswer);
    resultPage.setPlayerGuess(guess);
    return resultPage;
    }
    void reset() {
    numberToGuess = new Random().nextInt(10) + 1; //Przeniesiony z setupRender który teraz wywołuje tą metodę
    }
    [/sourcecode]

    Na stronie wyników umieszczamy poza getterami i setterami następujący fragment:

    [sourcecode language="java"]
    @InjectPage private GuessGame gamePage;

    void onActivate(int playerGuess, boolean correctAnswer) {
    this.playerGuess = playerGuess;
    this.correctAnswer = correctAnswer;
    resultMessage = correctAnswer ? "That is correct answer" : "That is not correct answer";
    linkMessage = correctAnswer ? "Want to play again?" : "Please try again";
    }

    Object[] onPassivate() {
    return new Object[] { playerGuess, correctAnswer };
    }

    Object onActionFromBackToGuessGame() {
    if (correctAnswer) {
    gamePage.reset();
    }
    return gamePage;
    }
    [/sourcecode]

    W ten sposób mamy prostą i czystą nawigację, która jednak trzyma wartości w URLu, co nie bardzo mi pasuję do tej strony, jednak znakomicie mogło by się sprawdzić w wszelakiego rodzaju wizardach, które pozwalają bookmarkować kolejne kroki. Modyfikujemy więc stronę rezultatu dodając adnotację @Persist nad poszczególnymi polami, usuwamy metody onActivate i onPassivate i już mamy czysto w URLu. W zasadzie to prawie. Brak nam jeszcze miejsca, w którym będziemy nasze 2 stringi ustalać i proponuję je umieścić w znanym nam już setupRender(); Przy moich zastosowaniach jest to zawsze wygodne miejsce. Przyjrzyjmy się obu przypadkom trochę bliżej.
    Pierwszy oznacza pełną transparentność przekazywanych parametrów, pozwala bookmarkować stronę oraz odwoływać się do niej w prosty sposób z zewnętrznych systemów. Poprzez metodę activate można przyjąć wartości, które połączymy z odpowiednimi polami. Pola te mogą być adnotowane poprzez @Persist co pozwoli połączyć oba sposoby. W ten sposób wykonałem kiedyś dla klienta, prostą integrację z zewnętrznym systemem CRM.
    Drugi sposób jest bardzo wygody do przekazywania wartości pomiędzy stronami i określił bym go jako 'obiektowy'. Nie ma możliwości wstrzyknięcia z zewnątrz danych (przynajmniej ja nie kojarzę takiego sposobu) a Tapestry zajmie się za nas zarządzaniem pamięcią i nie pozwoli by dane przeciekały do kolejnych stron.

    Read More »

  • Page pooling w T5

    W poprzednim wpisie wspomniałem, że do wersji 5.2, wszystkie strony w Tapestry były przetrzymywane w puli. Niestety poprzedni wpis się rozrósł i postanowiłem stworzyć krótszy wpis poświęcony temu zagadnieniu.

    W poprzednim wpisie pokazywałem jak można zarządzać stanem strony. Jedną z zalet programowania w T5 jest wrażenie, że strony dają poczucie bycia świadomymi swojego stanu. Wystarczy mała adnotacja, bądź metoda i programujemy  nie przejmując się skąd pobrać wartości dla kolejnego żądania od klienta. Osobiście miałem ten problem, gdy korzystałem z innych szkieletów, wiem że inni też go miewali. Każda osoba pisząca ukryte pola, bądź kombinująca nad sesją ręka w górę ^^

    Niestety zachowywanie stanu ma swoje wady. Dane trzeba gdzieś trzymać i jakoś je ładować. Dla developera i klienta, strona może i wygląda jakby cały czas miała zapisane wartości, ale tak nie jest. Tapestry podczas każdego redirecta, zapisuje dane do wyznaczonego przez nas miejsca (przeważnie do sesji), po czym wysyła stronę do puli a następnie pobiera stronę o którą prosi użytkownik (może być ta sama) i ustawia property z nią związane (adnotowane np persistem, bądź będące w cyklu aktywacji/deaktywacji). Dłuższe i sensowniejsze wyjaśnienie cyklu życia i stanowość opisane są tu, tu i tu.

    Podejście to ma jednak wady. Trzymanie wielu instancji strony jest kosztowne. Każda z nich zajmuje pewną ilość pamięci co przy wielu stronach i wielu klientach doprowadzi do zużycia dużej ilości pamięci. Alternatywnie można by tworzyć dla każdego klienta instancję strony i niszczyć tuż po użyciu, niestety strony w Tapestry wymagają dużo zasobów do ich stworzenia, a to nie pomaga gdy użytkownik czeka na swoją stronę. Pula jest więc dobrym pomysłem, jednak stwarza problem dla dużych serwisów, gdzie zużywana pamięć bywa już problemem. By wyjść naprzeciw oczekiwaniom developerów, od wersji 5.2 pulę zastąpiono poprzez singletony stron. Dzięki sprytnemu zabiegowi, dla każdej strony tworzona jest tylko jedna instancja, co jak łatwo się domyślić przekłada się pozytywnie na ilość zużywanej pamięci. Obecnie mechanizm wygląda tak, że Tapestry pakuje w mapę wszystkie wartości, związane z stroną, i w przypadku żądania od klienta wykorzystuje je do zasilenia obiektu strony. Więcej o nowym mechanizmie (i bardziej szczegółowo) można przeczytać na blogu Howarda Lewisa Shipa. Wpis ten tłumaczy koncepcję i implementację nowego mechanizmu. Polecam zainteresowanym.

    Ostatnia sprawa. Jednym z czynników, dla których poruszam ten temat, jest to, że jest to spora zmiana. Kto czytał o Tapestry więcej, wie że większe zmiany powodowały duże problemy z wsteczną kompatybilnością. Tu jednak nie tylko został zachowany stary mechanizm (można aktywować pulę i zrezygnować z singletona), ale ponadto pula została rozszerzona. Od ver 5.2 da się zarządzać nią zdalnie i dostosowywać jej wielkość do aktualnych potrzeb (wcześniej było to możliwe tylko podczas kompilacji, ewentualnie przez properties i reload aplikacji). Mechanizm ten opisał na swoim blogu Igor Drobiazko. Podobne zachowanie kompatybilności wstecz, przy rozwijaniu frameworku, można zaobserwować w sprawie bibliotek javascriptowych. Na liście non stop powraca temat zastąpienia obecnych bibliotek poprzez jquery i non stop jest odrzucany wraz z przekierowaniem do projektów, które pozwalają na ową integrację. Nie bójcie się więc T5, ono nie planuje się zmieniać, przynajmniej nie rewolucyjnie :)

    Read More »

  • Zarządzanie stanem w Tapestry5

    Zacznę od małej uwagi: staram się by wpisy układały się w jeden spójny tutorial. Jednym z moich podstawowych założeń jest nie robienie zbyt dużych kroków i stopniowe dorzucanie kolejnych koncepcji. Może teraz to będzie działało na niekorzyść wpisów, ale liczę, że gdy będzie ich odpowiednio więcej, to ta metoda będzie pomagać kolejnym osobom je czytającym. Piszę o tym dlatego, że część z tego co dziś zrobię może się wydawać mocno idiotyczna :P

    Skoro poznaliśmy już jak się nawiguje i wywołuje akcje, to czas pomyśleć o czymś co będzie cokolwiek robiło. Zacznijmy może od czegoś prostego, jako że nie jestem najlepszym programistą, to bardziej skomplikowane rzeczy mogą mnie przerosnąć. Zaimplementujemy grę w zgadywankę. Scenariusz jest prosty: losujemy liczbę z przedziału 1-10, po czym użytkownik musi zgadnąć jaką liczbę wylosowaliśmy. Nawet ja sobie z tym powinienem dać radę ;)

    Zacznijmy od stworzenia nowej strony. W pakiecie pages dodajemy klasę GuessGame, będzie ona dostępna pod tą nazwą w URLu czyli np localhost:8080/guessGame. Definiujemy w niej następujące wartości

    [sourcecode language="java"]
    private int numberToGuess;
    private int item;
    private String resultMessage;

    public int getNumberToGuess() {
    return numberToGuess;
    }

    public String getResultMessage() {
    return resultMessage;
    }

    public void setItem(int item) {
    this.item = item;
    }

    public int getItem() {
    return item;
    }
    [/sourcecode]

    oraz uzupełniamy template mniej więcej następująco

    
    		Number to guess: ${numberToGuess}<br/>
    		Result: ${resultMessage}<br/>
    		Choose a number:<br/>
    		<t:loop source="1..10" value="item">
          		<t:actionlink t:id="guess" context="item" style="padding-right: 2px;">${item}</t:actionlink>
        	</t:loop>
    

    Mając już cel możemy przystąpić do gry. No ale jak grać? Poznaliśmy ostatnio komponent ActionLink i z niego korzystamy. Dodałem też komponent loop, który służy do iterowania po kolekcjach, bądź potrafi policzyć od 1 do 10. Tym się jednak dziś nie zajmujemy, jest on tu tylko dlatego, że okazałem się zbyt leniwy by napisać 10 osobnych action linków ;) Zmienna item jest potrzebna tylko dla loopa, o działaniu którego innym razem.

    Stronę możemy już podejrzeć i poklikać, choć dostaniemy teraz błędy :/ i numberToGuess wynosi zawsze 0. Hmm, zainicjalizujmy więc tę wartość i dodajmy obsługę actionLinków.

    [sourcecode language="java"]
    void onActionFromGuess(int guess){
    if(numberToGuess = guess){
    resultMessage = "Success";
    }
    else {
    resultMessage = "Try Again";
    }
    }

    void setupRender(){
    if(numberToGuess==0){
    numberToGuess = new Random().nextInt(10) + 1;
    }
    if(resultMessage==null&amp;&amp;resultMessage.isEmpty()){
    resultMessage = "Try U'r luck! ^^";
    }
    }
    [/sourcecode]

    Kolejny raz widzimy konwencję nazw Tapestry. Tym razem jest ona związana z cyklem życia strony... zaraz, zaraz. Cykl życia strony? Ano tak, przed wersją 5.2, wszystkie strony w Tapestry żyły sobie w pool-u, do którego po użyciu zostawały zwrócone. Same wartości były przypisywane w momencie aktywacji strony, każdy kto pracował z stateless EJB powinien wiedzieć jak to działa. Reszcie spieszę wyjaśnić, iż wprowadzone to zostało, by ograniczyć ilość pamięci potrzebną dla aplikacji. Dzięki ponownemu wykorzystaniu obiektów stron, framework nie musi tworzyć dla każdego klienta nowej instancji strony. To tak w dużym uproszczeniu, ale musi teraz wystarczyć. W związku z owym cyklem aktywacji i deaktywacji (strona posiada cykl życia i cykl aktywacji i deaktywacji. Użyłem na początku akapitu niewłaściwej, ale bardziej przykuwającej uwagę nazwy), strona musi mieć możliwość zainicjowania wartości oraz wykonania operacji by być gotową do użycia poprzez różnych klientów, pomimo jej nie zmieniającej się struktury. Pierwszą z metod w tym cyklu jest setup render i użyjemy jej, a co nam. Druga metoda (onAction...) obsługuje wszystkie eventy generowane przez nasze action linki. Warto zauważyć, że w tapestry możemy przekazywać do metod parametry. Nie trzeba kombinować nad jakimiś ukrytymi checkboxami, hidden fieldami czy innymi paskudztwami, chcecie przekazać parametr do metody, to po prostu to zróbcie. Więcej o cyklu życia i cyklu wyświetlania strony znajdziemy na stronach tapestry, a i ja może coś kiedyś skrobnę.

    Wracając do naszej gry. To co możemy już zaobserwować, to to, że najzwyczajniej w świecie nie działa :( Dlaczego dostajemy cały czas wartość 0 i brakuje nam wiadomości na stronie? Związane jest to znów z cyklem aktywacji strony. Tapestry dba o to by nie trzymać w pamięci obiektów nam zbędnych, więc to na nas spada wskazanie, które properties strony mają być zapamiętane. Można to zrobić na kilka sposobów:

    1. Dodanie metod aktywujących
    [sourcecode language="java"]
    void onActivate(int numberToGuess, String message) {
    this.numberToGuess = numberToGuess;
    this.resultMessage = message;
    }

    Object[] onPassivate() {
    return new Object[] { numberToGuess, resultMessage };
    }
    [/sourcecode]
    Obie metody bazują na konwencji nazw, a więc nazwa ma znaczenie ;) onActivate (liczba parametrów jest dowolna) jest wywoływana za każdym razem gdy strona ma zostać załadowana i należy ustalić wartości pól strony. Można napisać kilka takich metod, bardzo przydatne dla stron, które przyjmują różne ilości parametrów (osobiście korzystałem z tego w przypadku stron zawierających filtry). Metoda onPassivate jest wywoływana w dokładnie przeciwnym przypadku, tj gdy strona zwracana jest do page poola. Zwrócić może jeden parametr, bądź tablicę. Zwrócić? Ale dokąd, i jak przechowywane są te wartości? Obie metody korzystają z URLa. Wpisane przez nas wartości znajdą się w adresie, co uczyni stronę prostą do bookmarkowania (idealne dla wspomnianych filtrów). Niestety nie jest to dobry sposób na przechowywanie danych w grze w zgadywanie (jakby wypisanie na ekranie wyniku to było mało!). Sprawdźmy co tam jeszcze można zrobić by nasze properties przeżywały poszczególne requesty.

    2. Oznaczenie pola adnotacją @Persist wskazuje, że jego wartość ma być zachowywana pomiędzy kolejnymi requestami. Znów większość osób zastanowi się jak to się dzieje i gdzie Tapestry trzyma nasze wartości. Odpowiedź jest tym razem trudniejsza i brzmi: to zależy. Pokrótce postaram się to objaśnić.
    Jest kilka możliwości, domyślnie stosowana jest strategia trzymania wartości w sesji, zwana Session (adnotacja przyjmuje Stringa jako parametr określający, która strategia ma być użyta. Stałe je reprezentujące znajdziemy  w obiekcie PersistenceConstants)
    Flash  - wartość pola wkładana jest wprawdzie do sesji, ale usuwana jest z niej w momencie pierwszego pobrania. Można wykorzystać do np wyświetlenia komunikatu błędu)
    Client - mimo iż jest, to odradzam korzystanie. W tym trybie tapestry wrzuci obiekt w URLa w sobie tylko znany sposób, bądź (w przypadku formularzy), skorzysta z ukrytych pól. Prosty sposób by narobić sobie problemów, ale jeśli będzie potrzeba, to taki mechanizm jest)

    Z jednym z powyższych możemy zapisywać wartości na stronach, co jest nam niezbędne do tego by gra działała. Przetestować proszę kod, powinien działać. Jako, że długie się to zrobiło to kończę wpis :)

    Przypomnę jeszcze tylko, że całość kodów źródłowych można znaleźć w moim repo bit bucketa

    -= Edit =-

    Długo szukałem nazwy dla tego posta, niestety właściwa przyszła dopiero po publikacji. Ta zmiana wyjdzie na zdrowie tej notce, bo o utrwalaniu danych będzie więcej w przyszłości

    Read More »

  • Nawigacja pomiędzy stronami w Tapestry5

    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.

    Read More »

  • Przykładowa aplikacja Tapestry5

    Przeglądać staram się regularnie strony związane z T5 i jakiś czas temu na blogu projektu woookicentral.com, znalazłem informację, że ekipa zaczyna montować przykładową aplikację w Tapestry. Założenia były wzięte z Seama i ich Hotel Booking. Projekt powstaje na githubie, skąd można go pobrać i eksperymentować do woli. Dostępne są 2 branche. Pierwszy, upubliczniona i posiadająca własne demo wersja, zbudowana jest z podstawowych komponentów dostępnych w Tapestry + biblioteki tapx, twórcy Tapestry: Howarda Lewisa Shipa.

    Drugi branch, jeszcze w budowie, powstaje z wykorzystaniem innych dostępnych rozszerzeń, jeśli dobrze się doczytałem to głównie  korzystają z projektu Tynamo. Powinno to dodać duży model security i uprościć np. konwersacje.

    Projekt należy sprawdzić. Jako drugą ciekawą referencję (którą osobiście przeglądałem i z czystym sercem polecam) jest Wooki. O wooki więcej można poczytać na ich stronie, przejrzeć komponenty i moduły które wytworzyli. No i za kilka tygodni ten blog mam nadzieję, że będzie też dobrą referencją dla Polaków (i wszystkich translatujących) ;)

    A w weekend prawdziwa notka, tj taka z kodem i mówiąca coś więcej o T5 :)

    Read More »

Michal Gruca
Michal Gruca

Manager by title, developer at heart

Twitter Email

TOC