Kontrolka Drzewka w Tagu JSP

31 sierpnia 2009 | 0 komentarze

Począwszy od JSP 2.0 do dyspozycji programistów dostępne są tagi użytkownika. Tagi posiadają kilka nowych, bardzo ważnych funkcjonalności, których brakowało w starym JSP. Wśród nich są możliwość przekazania referencji obiektów jako atrybuty (nie przez kontekst) i wykonywanie przekazanych w atrybutach fragmentów (fragments).


Napisanie rozsądnej kontrolki drzewka w HTML'u w tagu JSP nie jest banalne ze względu na rekurencję - podstawowym założeniem jest brak ograniczenia liczby poziomów. Dla uproszczenia kodu nie będzie możliwości rozwijania i zwijania gałęzi. Po stronie przeglądarki wykorzystam elementy listy <ul> i <li>. Wnętrze elementu listy będzie przekazywane jako fragment, co umożliwi wykonanie dowolnego zdarzenia javascript lub podpięcie linka w zależności od sposobu wykorzystania drzewka. Model to zwykły POJO:

package pl.publicmethods.model;

public class Group {

private Set<Group> children;

public Set<Group> getChildren() {
return children;
}
public void setChildren(Set<Group> children) {
this.children = children;
}
}


Kod tagu /WEB-INF/tags/widgets/tree.tag:

<%@ tag display-name="" description="" import="pl.publicmethods.model.*"%>
<%@ attribute name="groups" required="true" type="java.util.Collection" %>
<%@ attribute name="level" required="false" type="java.lang.Integer" %>
<%@ attribute name="name" required="true" fragment="true" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<%@ taglib prefix="w" tagdir="/WEB-INF/tags/widgets"%>

<c:if test="${empty level}">
<c:set value="0" var="level" />
</c:if>

<c:if test="${level eq 0}">
<c:set value="class=\"tree\"" var="ulclass" />
</c:if>

<ul ${ulclass}>
<%
for(Object o : groups){
Group group = (Group)o;
request.setAttribute("group",group);
%>
<li>
<jsp:invoke fragment="name"/>
</li>
<w:tree groups="${group.children}" level="${level+1}" name="${name}" />
<%
}
%>
</ul>

Konkretny przypadek użycia na stronie JSP - atrybut name zawiera fragment który zostanie wykonany i wyświetlony dla każdego elementu w kolekcji ${groups}:

<w:tree groups="${groups}">
<jsp:attribute name="name">
<c:url value="/groupform.do?id=${group.id}" var="url" />
<a href="${url}">
<c:out value="${group.name}" />
</a>
</jsp:attribute>
</w:tree>

Czy można przekazać fragment JSP rekurencyjnie w postaci atrybutu? Okazuje się że można. Tag zawiera pętlę rekurencyjną w formie Scriptletu. Alternatywnie można ją napisać w JSTL, pamiętając o przekazaniu zmiennej group do zakresu request - musi być widoczna dla fragmentu.

Atrybut level to nic innego jak piętro drzewka - na zerowym element <ul> dostaje oddzielną klasę stylu. Łatwo można wprowadzić sprawdzanie zapętlonych powiązań - wystarczy wstawić warunek dla level < 1000.

I jeszcze style CSS:

ul.tree {
margin: 0;
padding: 0;
background-color: #f8f8f8;
border-top: 1px solid #888;
border-bottom: 1px solid #444;
}
ul.tree ul {
padding: 0;
margin: 0;
margin-left: 24px;
}
ul.tree li {
list-style-type: none;
border-bottom: 1px dotted #888;
padding: 4px;
}
ul.tree li:hover {
background-color: #f0f0f0;
}

Crosstab i Dataset użytkownika w Jasper Reports

23 sierpnia 2009 | 1 komentarze

Natrafiłem na banalny z pozoru problem w iReport: jak w prosty sposób poza głównym zapytaniem odczytać coś z DB i wyświetlić w raporcie, np. w sekcji nagłówka? Założenie: id rekordu jest stałe albo przekazywane jako parametr zewnętrzny.

Najłatwiej byłoby oczywiście dodać podzapytanie w głównym query, ale komplikowanie mocno rozbudowanego sql'a to zły pomysł - wydajnościowo i (nie)higienicznie. Raport podrzędny też odpada - armata na komara. Pozostaje użycie dedykowanego Dataset przez wstawienie tabelki krzyżowej.

Crosstabs to niewdzięczne struktury. Moj iReport (3.5.3) kompletnie sobie z nimi nie radzi - próby wstawienia tabeli krzyżowej z palety kończą się szybkim pożegnaniem z GUI NetBeans bez zapisania zmian i komunikatu o błędzie. Pozostaje edycja źródeł raportu w XML'u, ale po kolei.

Poniżej załączam przepis na wstawienie do dowolnej sekcji raportu pola odczytanego dedykowanym zapytaniem sql. Ważne jest takie zgrupowanie kolumn i wierszy w Crosstab, żeby została widoczna tylko jedna komórka sekcji Detail, bez nagłówków i sum. Oczywiście podany kod trzeba dopasowac do swoich potrzeb (zapytanie, wielkość elementów, itd).

Krok 1. Do raportu dodać nowy Dataset - "Add Dataset" z menu kontekstowego po kliknięciu na nazwę raportu. Dodać parametr. Typ parametru musi być zgodny z id rekordu i parametrem zewnętrznym raportu.


Krok 2. Wpisać sparametryzowane zapytanie do Dataset.


Krok 3. Wstawić Crosstab do raportu, np. do sekcji nagłówka (z palety). Jeśli iReport nie chce współpracować - przejść do następnego punktu (wkleić XML).


Krok 4. Wyciąć nagłówki i stopki kolumn i wierszy tabeli. W edytorze tabeli powinna zostać widoczna tylko jedna komórka sekcji Detail typu String (w sekcji Measures pole ma Value Expression=$F{number}). Jako bucket expression (wyrażenie grupujące) w Row Groups i Column Groups wpisać stały tekst, np. "a".


Jeśli przy edycji tabeli iReport odmawia współpracy, wkleić gotowy szablon kodu do żródła XML raportu.

<crosstab isRepeatColumnHeaders="false" isRepeatRowHeaders="false" columnBreakOffset="0">
    <reportElement x="60" y="14" width="200" height="14"/>
    <crosstabParameter name="orderId" class="java.lang.Long"/>
    <rowGroup name="row" width="0">
        <bucket>
            <bucketExpression class="java.lang.String"><![CDATA["a"]]></bucketExpression>
        </bucket>
        <crosstabRowHeader>
            <cellContents/>
        </crosstabRowHeader>
        <crosstabTotalRowHeader>
            <cellContents/>
        </crosstabTotalRowHeader>
    </rowGroup>
    <columnGroup name="col" height="0">
        <bucket>
            <bucketExpression class="java.lang.String"><![CDATA["a"]]></bucketExpression>
        </bucket>
        <crosstabColumnHeader>
            <cellContents/>
        </crosstabColumnHeader>
        <crosstabTotalColumnHeader>
            <cellContents/>
        </crosstabTotalColumnHeader>
    </columnGroup>
    <measure name="orderNumber" class="java.lang.Object">
        <measureExpression><![CDATA[$F{number}]]></measureExpression>
    </measure>
    <crosstabCell width="237" height="25">
        <cellContents>
            <textField>
                <reportElement style="Crosstab Data Text" x="0" y="0" width="237" height="14"/>
                <textElement textAlignment="Left">
                    <font isBold="true"/>
                </textElement>
                <textFieldExpression class="java.lang.String"><![CDATA[""+$V{orderNumber}]]></textFieldExpression>
            </textField>
        </cellContents>
    </crosstabCell>
</crosstab>

Krok 5. Przypisać nazwę Dataset do tabeli. Zrobić mapowanie wartości parametru raportu na Dataset.


Krok 6. Jeśli nie istnieje, wstawić pole tekstowe do sekcji Detail tabeli i przypisać zmienną typu String (Text Field Expression=$V{orderNumber}).



I to byłoby na tyle. Wszelkie uwagi są mile widziane. :)

Bloki deklaracyjne w JSP

21 sierpnia 2009 | 0 komentarze

Bloki deklaracyjne nie należą do często używanych elementów JSP. Wynika to po pierwsze z wąskiego zakresu zastosowania, po drugie ze znanych zaleceń i tendencji używania tagów/EL a unikania Scriptlets (czytelny kod, łatwe utrzymanie, mniej błędów, itd.). Mimo tego, deweloper JSP powinien mieć przynajmniej świadomość istnienia tego mechanizmu. ;-)

Kod JSP każdej strony/tagu użytkownika jest w pierwszej fazie tłumaczony na kod źródłowy (servlet) a w następnej kompilowany do bytecode. Deklaracje pozwalają na tworzenie metod i pól będących członkami klasy wynikowej strony JSP (class members). W fazie tłumaczenia treść tagów JSP i Scriptlets zostaną wrzucone do jednej metody. Ale nie deklaracje.

Podstawowym ograniczeniem bloków deklaracyjnych jest dostępność pól i metod tylko w Scriptlets - nie są widoczne w Expression Language. EL operuje przecież na elementach zawartych w kontekście.

Przykład deklaracji JSP - tabliczka mnożenia w tabeli HTML. Mnożenie wykonywane w metodzie multiply(), zdefiniowanej w bloku deklaracyjnym. Metodę można wywoływać wielokrotnie z dowolnego miejsca na stronie.

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<body>
  <h1>Test</h1>
  <%!
    int multiply(int a, int b){
    return a*b;
  }
  %>
  <p>
    <table>
    <% for(int x=1; x<=10; x++){%>
    <tr>
      <% for(int y=1; y<=10; y++){%>
        <td><%= multiply(x, y) %></td>
      <% } %>
    </tr>
    <% } %>
    </table>
  </p>
</body>

W taki sam sposób można deklarować pola. Trzeba tylko pamiętać, że przechowywanie w ten sposób stanu w JSP nie jest bezpieczne - dostęp do pól ma wiele wątków jednocześnie i w zasadzie nie ma gwarancji operowania na konkretnej instancji klasy. Z kolei użycie słowa kluczowego synchronized na pewno zabije wydajność.

Istnieją dwie metody specjalne, uwzględnione w cyklu życia strony JSP: jspInit wywoływana podczas inicjalizacji, jspDestroy przy niszczeniu servlet'a. Deklaracje oferują możliwość przeciążenia obu metod.

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<%! public void jspInit() { System.out.println("* witaj!"); } %>
<%! public void jspDestroy() { System.out.println("* bye bye!"); } %>

W blokach deklaracyjnych działają słowa kluczowe i kwalifikatory dostępu, pola i metody mogą być oznaczone jako static, final, synchronized. Deklaracje pozwalają na tworzenie bloków inicjacyjnych, w tym statycznych. Więcej - choć specyfikacja o tym milczy, dopuszczone jest deklarowanie klas zagnieżdżonych, np.

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<body>
  <h1>Test 2</h1>
  <%!
  public static class InnerClass {
    public static void testIt(String str){
      System.out.println("testIt:"+str);
    }
  }
  %>
  <p>
  <%
    InnerClass.testIt("ok!");
  %>
  </p>
</body>

Na koniec ciekawostka, a raczej wpis z działu Tips & Tricks. ;-) Na stronie JSP, mimo że Eclipse informuje o błędzie, można napisać działający konstruktor. :-)


<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<%! public myjsp_jsp() { System.out.println("* Dzień Dobry!"); } %>

Linki:
The Java EE 5 Tutorial, JSP Declarations
JavaServer Pages (JSP) v2.0 Syntax Reference