JSF komponenta

Tento článek popisuje vývoj jednoduché komponenty v Java Server Faces (JSF) verze 1.2. Nejprve si ukážeme, co tvoří JSF komponentu a pak jednoduchou komponentu naimplementujeme. Technologie JSF je postavena nad Java Server Pages (JSP), jejichž znalost dále předpokládám.

Ukázková komponenta bude generovat posloupnost náhodných čísel, která bude sloužit jako nápověda pro sázkaře. Pokud používáte NetBeans 6.5, začněte vytvořením nové webové aplikace. Při vytváření projektu nezapomeňte zatrhnou podporu JSF.

JSF komponenta se skládá z těchto částí:

  • UIComponent class - třída, která obsahuje "logiku" komponenty. Její instance jsou součástí stromu komponent, který se vytváří na serveru po příchodu JSF požadavku.
  • Renderer class - třída zodpovědná za převod (rendering) komponenty do HTML či jiného značkovacího jazyka. Pokud se o tento převod postará sama třída komponenty (UIComponent class), může Renderer chybět.
  • Tag class - slouží k odkazu na komponentu v JSP stránce. Je také zodpovědný za předávání parametrů do třídy komponenty.
  • Tag Library Descriptor (TLD) - popis tagu, tj. jeho jméno, třída a atributy.

Začneme implementací logiky komponenty. Třída LottoComponent bude potomkem třídy UIComponentBase, která je potomkem UIComponent. Protože nebudeme používat Renderer, postará se o převod do HTML sama komponenta. K tomu slouží metody encodeBegin, encodeEnd a příp. i encodeChildren. V našem příkladu vystačíme s encodeBegin.

public class LottoComponent extends UIComponentBase {
private Random rand = new Random();
private int number = 6;
@Override
public void encodeBegin(FacesContext fc) throws IOException {
ResponseWriter w = fc.getResponseWriter();
w.startElement("p", this); // zapíše <p>
for (int i = 0; i < number; i++) { // zapisuje čísla z intervalu <1,40>
w.writeText(1 + rand.nextInt(40) + " ", null);
}
w.endElement("p"); // zapíše </p>
}
@Override
public String getFamily() {
return "LottoFamily";
}
}

Metoda getFamily vrací jméno rodiny komponent, do níž komponenta patří. Rodina komponenty se používá při výběru Rendereru. Protože v našem příkladu Renderer nepoužíváme, metoda může vracet libovolný řetězec.

Dále vložíme do faces-config.xml element component:

<faces-config version="1.2"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_1_2.xsd">
<component>
<component-type>Lotto</component-type>
<component-class>lotto.LottoComponent</component-class>
</component>
</faces-config>

A naimplementujeme Tag:

public class LottoTag extends UIComponentELTag {
@Override
public String getComponentType() {
return "Lotto"; // vrací to, co je uvedeno v elementu component-type ve faces-config.xml
}
@Override
public String getRendererType() {
return null; // Renderer v tomto příkladě nepoužíváme
}
}

Třída LottoTag je potomkem UIComponentELTag, která je určena pro vytváření JSF tagů. JSF tagy jsou "vylepšené" JSP tagy. Umí navíc např. Expression Language.

Tag Library Descriptor obsahuje popis tagu:

<taglib version="2.0" xmlns="http://java.sun.com/xml/ns/j2ee">
<tlib-version>1.0</tlib-version>
<short-name>lotto</short-name>
<uri>/WEB-INF/Lotto</uri>
<tag>
<name>tips</name>
<tag-class>lotto.LottoTag</tag-class>
</tag>
</taglib>

Element short-name obsahuje doporučený prefix pro tagy z této knihovny a element uri je identifikátor knihovny. Pomocí uri se na knihovnu budeme odkazovat z JSP stránky. V elementu name je jméno tagu a v elementu tag-class je jméno třídy tagu.

Teď už můžeme tag použít v JSF stránce:

<%@taglib prefix="lotto" uri="/WEB-INF/Lotto"%>
...
<f:view>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Lotto</title>
</head>
<body>
<h1>Lotto</h1>
<lotto:tips/>
</body>
</html>
</f:view>

Výhody komponentového modelu se naplno projeví tehdy, když použijeme tag opakovaně. Můžeme jej použít na té samé stránce i na jiné.

JSFCompParts

Atributy

Dále přidáme naší komponentě atribut number, kterým řekneme, kolik čísel chceme generovat. Pokud atribut nebude uveden, bude komponenta generovat šest čísel jako dříve.

Do TDL souboru přidáme popis atributu:

<taglib version="2.0" xmlns="http://java.sun.com/xml/ns/j2ee">
<tlib-version>1.0</tlib-version>
<short-name>lotto</short-name>
<uri>/WEB-INF/Lotto</uri>
<tag>
<name>tips</name>
<tag-class>lotto.LottoTag</tag-class>
<attribute>
<name>number</name>
<description>The number of tips</description>
</attribute>
</tag>
</taglib>

Ve třídě LottoTag přibyde property number a metody setProperties a release:

public class LottoTag extends UIComponentELTag {
private String number;
@Override
protected void setProperties(UIComponent component) {
super.setProperties(component);
if (number != null) {
component.getAttributes().put("number", number);
}
}
@Override
public void release() {
super.release();
number = null;
}
@Override
public String getComponentType() {
return "Lotto"; }
@Override
public String getRendererType() {
return null;
}
public String getNumber() {
return number;
}
public void setNumber(String number) {
this.number = number;
}
}

Metoda setProperties je volána po vytvoření komponenty před jejím přidáním do stromu komponent. Má za úkol přenos parametrů z tagu do komponenty. Metoda release je volána po ukončení zpracování tagu. Měla by objekt vrátit do stavu, ve kterém byl těsně po vytvoření. Při opakovaném výskytu stejného tagu se totiž nemusí vytvářet vždy nová instance, ale starší instance mohou být "recyklovány".

Ve třídě LottoComponent upravíme metodu encodeBegin, aby používala atribut number:

@Override
public void encodeBegin(FacesContext fc) throws IOException {
String s = (String) getAttributes().get("number");
if (s != null) {
number = Integer.parseInt(s);
}
ResponseWriter w = fc.getResponseWriter();
for (int i = 0; i < number; i++) {
w.writeText(1 + rand.nextInt(40) + " ", null);
}
}

V JSP stránce pak můžeme použít u tips atribut number:

<lotto:tips number="4"/>

Expression Language

Dále komponentu rozšíříme o podporu Expression Language (EL). Tj. přidáme možnost stanovit hodnotu atributu number pomocí výrazu v EL:

<lotto:tips number="#{testManagedBean.size}"/>

Nejprve upravíme TLD:

<tag>
<name>tips</name>
<tag-class>lotto.LottoTag</tag-class>
<attribute>
<name>number</name>
<deferred-value>
<type>Integer</type>
</deferred-value>
<description>The number of tips</description>
</attribute>
</tag>

Pak drobně změníme třídu LottoTag:

public class LottoTag extends UIComponentELTag {
private ValueExpression number;
@Override
protected void setProperties(UIComponent component) {
super.setProperties(component);
if (number != null) {
component.getAttributes().put("number", number);
}
}
@Override
public void release() {
super.release();
number = null;
}
@Override
public String getComponentType() {
return "Lotto";
}
@Override
public String getRendererType() {
return null;
}
public ValueExpression getNumber() {
return number;
}
public void setNumber(ValueExpression number) {
this.number = number;
}
}

A nakonec ještě malá úprava metody encodeBegin ve třídě LottoComponent:

@Override
public void encodeBegin(FacesContext fc) throws IOException {
ValueExpression ve = (ValueExpression) getAttributes().get("number");
if (ve != null) {
number = (Integer) ve.getValue(fc.getELContext());
}
ResponseWriter w = fc.getResponseWriter();
for (int i = 0; i < number; i++) {
w.writeText(1 + rand.nextInt(40) + " ", null);
}
}

A je hotovo. Na závěr si ještě ukážeme, jak vytvořit knihovnu, kterou lze snadno připojit k webové aplikaci. Vytvoříme jar, který bude mít tuto strukturu:

complib

(MANIFEST.MF byl vložen automaticky programem jar). Takto vytvořenou knihovnu pak k projektu připojíme nakopírováním jaru do adresáře WEB-INF/lib.