środa, 19 maja 2010

Spring Framework 3.0 Tutorial – cz 4 – sitemesh, menu, atrybutykontekstu

Jak zapowiedziałem w poprzednim poście tym razem pobawimy się Sitemesh-em i atrybutami kontekstowymi w JSP, więc będzie to wpis raczej krótki. Skupimy się na rozdzieleniu szablonów (layoutu) aplikacji na 2 części. Pierwszy szblon zastosujemy do panelu administracyjnego, wyświetlać będzie listy, formularze i dane, a także umieści na każdej stronie menu administracyjne. Drugi szablon będzie wykorzystywany jedynie podczas logowania i prócz formularza  nie będzie zawierał nic więcej. Oczywiście jeśli ktoś chce może śmiało dodać więcej szablonów, np. specjalna strona dla obsługi zamówień która zawiera oddzielne menu, czy też


W pierwszej części tutorialu wspomniałem iż Sitemesh służy do składania stron w całość, dzięki niemu możemy stworzyć szablon strony, do którego będziemy doklejać wyniki stron co skutecznie  ułatwia tworzenie widoków. Co prawda biblioteka ta potrafi więcej (w wersji 2.x) ale z racji, że korzystamy z wersji alpha lepiej używać jedynie podstawowych funkcjonalności (przynajmniej w tej chwili).

Rozdzielamy szablony


Sitemesh-a skonfigurowaliśmy w pierwszej części tuorialu, miał on za zadanie składać główny szablon (default.jsp) z widokami wyników wygenerowanymi przez aplikację, teraz zajmiemy się rozdzieleniem szablonów dla widoku logowania oraz dla pozostałej części aplikacji. Jak wiadomo, konfigurację Sitemesh-a trzymamy w klasie com.darekzon.bookstore.filter.Sitemesh, i aktualnie zawiera wpis mówiący, że każdy wyniki ma być "wklejony" do szablony default.jsp. Aby utworzyć dodatkowy szablon wystarczy dopisać regułę która dla żądania /login.html będzie stosować inny szablon. Całość wygląda następująco:

package com.darekzon.bookstore.filter;
import org.sitemesh.builder.SiteMeshFilterBuilder;
import org.sitemesh.config.ConfigurableSiteMeshFilter;
public class Sitemesh extends ConfigurableSiteMeshFilter {
protected void applyCustomConfiguration(SiteMeshFilterBuilder builder) {
builder.addDecoratorPath("/login.html", "/views/layout/login.jsp");
builder.addDecoratorPath("/*", "/views/layout/default.jsp");
}
}

W powyższym zapisie ważna jest kolejność wpisów (odwrotny zapis przysłaniałby nam szablon login.jsp dla wywołania strony logowania.

Teraz wystarczy stworzyć odpowiedni plik (np. bazując na wcześniejszym szablonie) i gotowe, zapytanie o stronę /login.html otrzyma własną stronę wynikową.

Tworzymy menu administracyjne


Teraz zajmiemy się tworzeniem menu, czynność z pozoru prosta, wystarczy wpisać kilka tagów i gotowe, jak jednak sprawić by nasze menu rozpoznawało gdzie aktualnie się znajdujemy i na bazie tego zaznaczało w menu naszą pozycję? W poprzednim projekcie używałem Sitemesh 2.x który w łatwy sposób odczytywał metatagi stron dzięki czemu łatwo można było określić naszą pozycję, niestety w wersji 3 nie działa to jak trzeba, a na pewno nie jest to w żaden sposób udokumentowane, więc trzeba sobie poradzić w inny sposób. Rozwiązaniem jakie wymyśliłem, jest ustawianie atrybutów strony w zależności od tego gdzie jesteśmy. Jak to działa?

Jak wszyscy wiemy, każda akcja posiada własny plik za pomącą którego generowany jest widok, w każdym z tych plików wstawiamy zapis:

<% pageContext.setAttribute("moduleName","content", PageContext.REQUEST_SCOPE); pageContext.setAttribute("pageName","index", PageContext.REQUEST_SCOPE); %>

Powyższy kod, ustawił nam w pliku widoku 2 atrybuty, moduleName która oznaczać będzie w jakiej grupie znajduje się strona oraz pageName która oznacza jaka strona jest generowana, PageContext.REQUEST_SCOPE mówi aplikacji, że atrybuty będą widoczne tylko w zakresie aktualnego żądania (więcej nam nie trzeba).

Zabieramy się za tworzenie menu, ponieważ menu. Możemy je stworzyć w pliku szablonu albo w oddzielnym pliku który dołączymy do naszego szablonu, ponieważ nie lubię chaosu, wybieram rozwiązanie drugie.
Tworzymy plik navigation.jsp (w folderze includes) oraz dołączamy go do naszego szablonu default.jsp dyrektywą:

<%@ include file="../includes/navigation.jsp" %>

Dołączony plik ma za zadanie odczytać atrybuty ustawione w widokach (patrz wyżej) oraz oznaczenie grupy i podstron klasą active gdy są one aktywne, dzięki czemu będziemy mogli za pomącą CSS-a odpowiednio pokazywać i ukrywać nasze menu. Nasze menu zbudowane będzie z zagnieżdżonych list nieuporządkowanych według struktury:

<ul>
<li>GRUPA
<ul>
<li>PODSTRONA</li>
</ul>
</li>
</ul>

Plik ten prezentuje się następująco:

<%@ taglib prefix="tag" uri="http://www.springframework.org/tags" %>
<% String moduleName = (String) pageContext.getAttribute("moduleName",PageContext.REQUEST_SCOPE);  String pageName = (String) pageContext.getAttribute("pageName",PageContext.REQUEST_SCOPE); %>
<ul>
<li class="<% if(moduleName==">">
<a href="<%= request.getContextPath() %>/content.html">

</a></li>
<li class="<% if(moduleName==">">
<a href="<%= request.getContextPath() %>/catalog.html">

</a>
<ul>
<li class="<% if(moduleName==">">
<a href="<%= request.getContextPath() %>/catalog/categories.html">

</a></li>
<li>
<a href="<%= request.getContextPath() %>/catalog/books.html">

</a></li>
</ul>
</li>
<li class="<% if(moduleName==">">
<a href="<%= request.getContextPath() %>/orders.html">

</a></li>
<li class="<% if(moduleName==">">
<a href="<%= request.getContextPath() %>/clients.html">

</a></li>
<li>
<a href="<%= request.getContextPath() %>/settings.html">

</a>
<ul>
<li>
<a href="<%= request.getContextPath() %>/settings/administrators.html">

</a></li>
</ul>
</li>
<li class="logout">
<a href="<%= request.getContextPath() %>/wyloguj.html">

</a></li>
</ul>

Plik dołącza bibliotekę tagów wykorzystywaną do obsługi wiadomości, oraz odczytuje wcześniej ustawione atrybuty zapisując je do zmiennych. W kolejnych pozycjach menu następuje sprawdzenie aktualnej pozycji.
Gdy moduleName jest równe wymaganej wartości zostaje ustawiona klasa active, to samo tyczy się zmiennej pageName. Dzięki takiemu rozwiązaniu mamy nasze "dynamiczne" menu.

Jeśli ktoś zna lepsze rozwiązanie z chęcią je poznam.

PS. Jeśli chodzi o zapis request.getContextPath() to zwraca nam on ścieżkę wywołania aplikacji, tzn. jeśli nasza aplikacja działała by w folderze bookstore a jej uruchomienie wiązałoby się z wpisanie adresu http://example.com/bookstore/ to getContextPath() zwróci nam ciąg /bookstore, dzięki czemu wszystkie linki będą poprawnie obsłużone bez względu na to, czy nasza aplikacja jest uruchomiona na własnej domenie, czy też działa "w folderze".

Jak pobrać projekt


Projekt został umieszczony na serwerach GitHub.com. Adres bezpośredni do projektu: https://github.com/darek/bookstore.
Dodatkowo zostało uruchomione repozytorium GIT dla wszystkich chętnych i dostępny jest pod adresem: git://github.com/darek/bookstore.git

5 komentarzy:

  1. Wygląda na to, że nie będzie kolejnych części... szkoda :(

    OdpowiedzUsuń
  2. Pocieszę Cię, właśnie kończę pisać kolejną (może w weekend się uporam), będzie zawierać integrację z Lucene oraz Envers.

    OdpowiedzUsuń
  3. Ja mam kilka pytań:
    1) Czy są gdzieś udostępnione skrypty tworzące BD?
    2) Nie używałem nigdy maven2 - i tak z ciekawości chciałem się zapytać, jak robicie build i osadzacie aplikację na serwerze aplikacyjnym?

    OdpowiedzUsuń
  4. 1. Nie, aplikacja sama tworzy bazę danych (przy pierwszym uruchomieniu), zrzut bazy postaram się dostarczyć wkrótce
    2. Build robi eclipse + maven (niestety z racji braku serwera CI), liczę, że wkrótce się to zmieni i wszelkie buildy będzie robił Hudson bądź TeamCity

    OdpowiedzUsuń
  5. Witam,
    A jak ma wyglądać końcowy war osadzany na serwerze?
    Czy dla kazdego modułu tworzysz oddzielnego war-a?

    Dla bookstore
    [INFO] Exploding webapp...
    [INFO] Assembling webapp bookstore in E:\Firma\ABC\bookstore\target\bookstore-0.0.2
    [INFO] Copy webapp webResources to E:\Firma\ABC\bookstore\target\bookstore-0.0.2
    [INFO] Generating war E:\Firma\ABC\bookstore\target\bookstore-0.0.2.war
    [INFO] Building war: E:\Firma\ABC\bookstore\target\bookstore-0.0.2.war
    [INFO]
    [INFO] --- maven-install-plugin:2.3.1:install (default-install) @ bookstore ---
    [INFO] Installing E:\Firma\ABC\bookstore\target\bookstore-0.0.2.war to C:\Documents and Settings\Właściciel\.m2\repository\com\darekzon\bookstore.0.2\bookstore-0.0.2.war
    [INFO] Installing E:\Firma\ABC\bookstore\pom.xml to C:\XZY\.m2\repository\com\darekzon\bookstore.0.2\bookstore-0.0.2.pom
    [INFO] ------------------------------------------------------------------------
    [INFO] BUILD SUCCESS
    [INFO] ------------------------------------------------------------------------
    [INFO] Total time: 8.625s
    [INFO] Finished at: Sat Jan 08 01:45:28 CET 2011
    [INFO] Final Memory: 5M/15M
    [INFO] ------------------------------------------------------------------------

    Dla bookstore-admin
    [INFO]
    [INFO] --- maven-war-plugin:2.0.2:war (default-war) @ bookstore ---
    [INFO] Exploding webapp...
    [INFO] Assembling webapp bookstore in E:\Firma\ABC\bookstore-admin\target.0.2
    [INFO] Copy webapp webResources to E:\Firma\ABC\bookstore-admin\target.0.2
    [INFO] Generating war E:\Firma\ABC\bookstore-admin\target.0.2.war
    [INFO] Building war: E:\Firma\ABC\bookstore-admin\target.0.2.war
    [INFO]
    [INFO] --- maven-install-plugin:2.3.1:install (default-install) @ bookstore ---
    [INFO] Installing E:\Firma\ABC\bookstore-admin\target.0.2.war to C:\Documents and Settings\Właściciel\.m2\repository\com\darekzon\bookstore.0.2\bookstore-0.0.2.war
    [INFO] Installing E:\Firma\Projekty\eclipse_wloczykij\bookstore-admin\pom.xml to C:\XZY\.m2\repository\com\darekzon\bookstore.0.2\bookstore-0.0.2.pom
    [INFO] ------------------------------------------------------------------------
    [INFO] BUILD SUCCESS
    [INFO] ------------------------------------------------------------------------
    [INFO] Total time: 30.172s
    [INFO] Finished at: Sat Jan 08 01:58:44 CET 2011
    [INFO] Final Memory: 4M/15M
    [INFO] ------------------------------------------------------------------------


    Dla bookstore-lib
    [INFO] --- maven-install-plugin:2.3.1:install (default-install) @ bookstore-lib ---
    [INFO] Installing E:\Firma\ABC\bookstore-lib\target\bookstore-lib-0.0.1-SNAPSHOT.jar to C:\XYZ\.m2\repository\bookstore-lib\bookstore-lib.0.1-SNAPSHOT\bookstore-lib-0.0.1-SNAPSHOT.jar
    [INFO] Installing E:\Firma\ABC\bookstore-lib\pom.xml to C:\XYZ\.m2\repository\bookstore-lib\bookstore-lib.0.1-SNAPSHOT\bookstore-lib-0.0.1-SNAPSHOT.pom
    [INFO] ------------------------------------------------------------------------
    [INFO] BUILD SUCCESS
    [INFO] ------------------------------------------------------------------------
    [INFO] Total time: 4.015s
    [INFO] Finished at: Sat Jan 08 02:04:06 CET 2011
    [INFO] Final Memory: 4M/15M
    [INFO] ------------------------------------------------------------------------


    Kiedy chcę osadzić aplikację na tomcat wówczas pojawia się mi informacja: "FAIL - Application at context path /bookstore-0.0.2 could not be started"

    Udzielicie jakiejś podpowiedzi?
    Czy możesz zamieścić gdzieś swojego wara po to bym mógł zobaczyć jak ma finalnie wyglądać?

    Pozdrawiam

    OdpowiedzUsuń