Intersult Taglib

Achtung: Intersult Taglib ist nur für JSF 1.2. Für die aktuelle Version JSF 2.1 gibt es JSF Ext.

Die Intersult hat die Herstellung von JSF-Components revolutioniert: Die Intersult Taglib ermöglicht erstmals die Erstellung vollwertiger Facelet-Tags in XHTML. Und das ohne eine Zeile Java-Code zu schreiben.

Die effiziente Herstellung von Components hat einen gravierenden Vorteil: Einzelteile werden wiederverwertbar, das Web-System einheitlicher. Die Components können weiterentwickelt werden, während das Gesamtsystem wächst.

Wir beraten Sie gerne über den Einsatz von Components und die effiziente Strukturierung von JEE-Projekten. Wir liefern Ihnen die für Ihr Projekt nützlichen Components.

Inhalt#

Was ist Meta-UI?#

Die Intersult Taglib "meta-ui" ist eine Taglib zur Produktion von Taglibs. Die produzierten Tags sind geeignet für die Entwicklung von JEE-Applikationen unter JSF 1.2, Seam, Facelets, Tomahawk und Richfaces. Damit produzieren Sie alle denkbaren Tags in hoher Geschwindigkeit, auch mit Javascript, AJAX, Styles oder Images.

Tag "meta"#

Der wichtigste Tag der Gruppe "com.intersult", Artifact "meta-ui" ist der meta-Tag. Was ist ein Meta-Tag? Das ist ein Tag zur Konstruktion einer Component mittels einer XHTML-Datei mit vollwertigem Id- und Rendering-Verhalten. Der Bau eigener Components wird damit zum Kinderspiel, da in der Regel keine komplizierten Tag-, Renderer- und Handler-Klassen mehr geschrieben werden brauchen. Eine Java-Klasse bauen Sie nur noch, wenn Ihre Komponente ein Datenmodell braucht, also mit dem value-Attribut an einen Wert gebunden wird.

Dabei sind Features enthalten, wie Component-Id-Generierung, JSF-Client-Id-Access aus Javascript, Javascript- und CSS-Header-Insertions, Resource-Exposition aus dem JAR, Text-Interpolation (Evaluierung von EL-Expressions) in allen Text-Resources etc. Das Gießen eigener Taglibs ist noch nie so einfach gewesen wie heute!

Attribute#

Der meta-Tag unterstützt einige Attribute:

Wie unter dem Attribut var beschrieben wird das Object innerhalb des Component Scope einer Variable mit dem unter var spezifiziertem Namen zugeordnet. In den Beispielen ist das meist comp. Unter diesem Component Object kann auf verschiedene Properties zugegriffen werden:

Beispiel Infobox#

Als Aufgabenstellung soll ein Facelet gebaut werden, das auf einer XHTML-Seite wie folgt eingebaut werden kann:
    <i:info id="info" value="Dies ist die Infobox"/>

Die Implementierung erfolgt als Facelet in der Datei info.xhtml:

<ui:composition>
	<i:meta useId="#{id}" var="comp" rendered="#{empty rendered or rendered}">
		<s:graphicImage id="#{comp.id}" url="#{empty img ? '/images/information.png' : img}"
			onmouseover="$('#{comp.clientId}-text').style.display = '';"
			onmouseout="$('#{comp.clientId}-text').style.display = 'none';"/>
		<s:span id="#{comp.id}-text" style="display: none; position: fixed; background-color: yellow; padding: 3px;">
			<h:outputText value="#{value}"/>
		</s:span>
	</i:meta>
</ui:composition>

Die *.taglib.xml enthält folgenden Eintrag, um das Facelet zu registrieren:

	<tag>
		<tag-name>info</tag-name>
		<source>/tag/info.xhtml</source>
	</tag>

Was kann dieser Tag?

Als Luxus kann noch eine TLD im Verzeichnis META-INF abgelegt werden, sodass z.B. der Content Assist von Eclipse eine Code Completion für die eben gebaute Component vornehmen kann:

	<tag>
		<name>info</name>
		<tag-class/>
		<body-content>empty</body-content>
		<description>Renderes a info icon with popup div.</description>
		<attribute>
			<name>id</name>
			<required>false</required>
			<rtexprvalue>false</rtexprvalue>
			<type>java.lang.String</type>
		</attribute>
		<attribute>
			<name>value</name>
			<required>true</required>
			<rtexprvalue>false</rtexprvalue>
			<type>java.lang.String</type>
			<description>Text for popup.</description>
		</attribute>
		<attribute>
			<name>img</name>
			<required>false</required>
			<rtexprvalue>false</rtexprvalue>
			<type>java.lang.String</type>
			<description>Optional: Image for the info icon.</description>
		</attribute>
                <attribute>
                    <name>rendered</name>
                    <description>
            	        Flag indicating whether this component (and its children) should be rendered. Expressions must evaluate
                        to a boolean.
                    </description>
                </attribute>
        </tag>

Einschränkungen: Der meta-Tag ist nur mit State-Saving auf dem Server getest, d.h. ohne Serialisierung. Wenn es Probleme gibt, in der web.xml folgende Parameter setzen:

	<context-param>
		<param-name>javax.faces.STATE_SAVING_METHOD</param-name>
		<param-value>server</param-value>
	</context-param>

Beispiel Menü#

Eine eigene Menükomponente wird durch die Tage menu und menuItem gebaut. Zunächst wird eine Resource in der components.xml definiert:
	<component name="resource" class="com.intersult.ui.util.ClassPathResource" scope="application">
		<property name="resourcePath">/test</property>
		<property name="classPath">/de/test/ui/resource</property>
	</component>

Dann der Tage "menu":

<ui:composition>
	<i:meta var="comp" useId="#{id}" rendered="#{empty rendered or rendered}"
			stylesheet="#{resource.get('/test.css')}">
		<table id="#{comp.clientId}" cellpadding="0" cellspacing="0">
			<i:clear var="id,rendered">
				<ui:insert/>
			</i:clear>
		</table>
	</i:meta>
</ui:composition>

Und schließlich der Tag "menuItem":

<ui:composition>
	<i:meta var="itemComp" useId="#{id}" rendered="#{empty rendered or rendered}"
			stylesheet="#{resource.get('/test.css')}">
		<tr>
			<td class="#{itemComp.last ? 'test-menuL' : 'test-menuI'}"/>
			<td class="test-menuContent">
				<i:clear var="id,rendered">
					<ui:insert/>
				</i:clear>
			</td>
		</tr>
	</i:meta>
</ui:composition>

Schließlich wird das Ganze wieder in die test.taglib.xml eingetragen, und in die test.tld, sodass der Content-Assist in der Entwicklungsumgebung den Tag auch anzeigen kann. Die die vorkommenden Styles in die test.css eingetragen. Mit diesen Tags können dann einfach eigene Menüs inklusive beliebig verschachtelter Untermenüs gebaut werden:

<o:menu>
	<o:menuItem>
		<s:link value="#{msg['menu1']}" action="menu1"			propagation="none"/>
	</o:menuItem>
	<o:menuItem>
		<s:link value="#{msg['menu2']}" action="menu2"/>
		<o:menu>
			<o:menuItem>
				<s:link value="#{msg['menu2.1']}" action="home" propagation="none"/>
			</o:menuItem>
		</o:menu>
	</o:menuItem>
</o:menu>

Beispiel Handler#

Es soll ein Tag gebaut werden, der an einen Event-Handler in Javascript hinzugefügt werden kann.

<i:meta var="comp" useId="#{id}" rendered="#{empty rendered or rendered}" attribute="#{event}" get="#{comp.javascriptId}(this); #{value}">
	<script type="text/javascript">
		function #{comp.javascriptId}() {
			<ui:insert/>
		}
	</script>
</i:meta>

Der Facelet-Taglib-Eintrag:

	<tag>
		<tag-name>setValue</tag-name>
		<source>/com/intersult/ui/tag/setValue.xhtml</source>
	</tag>

Und die TLD-Definition:

	<tag>
		<name>setValue</name>
		<tag-class/>
		<body-content>empty</body-content>
		<description>Set a value and call onchange handler</description>
		<attribute>
			<name>id</name>
		</attribute>
		<attribute>
			<name>rendered</name>
		</attribute>
		<attribute>
			<name>event</name>
			<description>Event to set value for</description>
		</attribute>
		<attribute>
			<name>clientId</name>
			<description>ClientId of the target input html element to set value for.</description>
		</attribute>
		<attribute>
			<name>value</name>
			<description>Value to apply to target input html element</description>
		</attribute>
	</tag>

Der Tag kann nun eingesetzt werden, um z.B. bei einer rich:suggestionbox weitere Felder mit zu befüllen:

<h:inputText id="patient" autocomplete="off"/>
<h:inputText id="gender"/>
<h:inputText id="room"/>
<h:inputText id="patientId"/>
<rich:suggestionbox id="suggest" for="patient" var="suggestion" width="500" height="200"
        suggestionAction="#{patient.suggestLabel}" fetchValue="#{patient.label}"
        usingSuggestObjects="true" minChars="3">
    <i:setValue event="onselect" clientId="#{comp.namingPrefix}gender"
        value="#{suggestion.gender}"/>
    <i:setValue event="onselect" clientId="#{comp.namingPrefix}room"
        value="#{suggestion.room}"/>
    <i:setValue event="onselect" clientId="#{comp.namingPrefix}patientId"
        value="#{suggestion.id}"/>
    <h:column>
        <h:outputText value="#{suggestion.label}" escape="false"/>
    </h:column>
</rich:suggestionbox>

Beispiel Invertierte Checkbox#

Hat man z.B. eine Modellklasse Article mit einem Property sold und möchte auf der Oberfläche eine Checkbox available anzeigen, ist dies mit JSF nicht möglich. Der Modellklasse möchte man in der Regel keinen transientes getter-/setter-Paar hinzufügen, da keine Business-Logik eingebracht werden soll.

Eine sehr einfache Lösung erfolgt mit dem meta-Tag:

<h:selectBooleanCheckbox id="available" value="#{article.sold}">
    <i:meta attribute="value" get="#{!value}" set="#{!value}"/>
</h:selectBooleanCheckbox>

Der meta-Tag im Beispiel arbeitet wie folgt:

Tag "clear"#

Bei verschachtelter Anwendung des meta-Tags werden Facelet-Parameter unkontrolliert in innere Facelets weiter gegeben. Insbesondere gefährdet ist ui:insert, da hier Facelets generisch eingefügt werden.

Dies findet bei ui:include und ui:param ebenso statt, wie bei der Definition von Tags in der Facelets-Taglib. Um dies zu verhindern, steht der Intersult clear-Tag zur Verfügung:

    <i:clear var="id,label">
        <ui:insert/>
    </i:clear>

Was geschieht hier? Der clear-Tag verhindert die weitere Propagierung der UIParameter (also auch Tag-Attribute) "id" und "label".

Beispiel inputGroup#

Das Beispiel soll den Nutzen des clear-Tags verdeutlichen. Zunächst wird ein Tag eingesetzt, der die Gruppierung von Eingabefeldern vornimmt:
    <i:inputGroup label="Gruppe">
        <i:inputText id="test" value="#{test}"/>
    </i:inputGroup>

Die Implementierung der inputGroup ist wie folgt:

	<i:meta var="comp" useId="#{id}" rendered="#{empty rendered or rendered}">
		<fieldset id="#{comp.clientId}">
			<legend>
				<h:outputText value="#{label}"/>
			</legend>
			<i:clear var="id,label">
				<ui:insert/>
			</i:clear>
		</fieldset>
	</i:meta>

Die Implementkerung von inputText:

<i:meta var="comp" useId="#{id}" rendered="#{empty rendered or rendered}">
	<h:panelGrid>
	        <h:inputLabel for="#{comp.id"} value="#{empty label ? 'Default' : label}"/>
		<h:inputText id="#{comp.id}" value="#{value}"/>
		<i:message id="#{comp.id}-message" for="#{comp.id}"/>
	</h:panelGrid>
</i:meta>

Der Tag inputText enthält wieder eine Referenz auf die Variable Label. Ohne Verwendung des clear-Tag wäre diese nun auf den äußeren Wert gesetzt, da der inputGroup-Tag den Wert nach innen hinein propagiert.

Tag "inputHidden"#

Dieser Tag ist eine Erweiterung des inputHidden aus der Tomahawk Bibliothek um das Property "from". Selbst in Seam-Projekten werden Informationen aus der Oberfläche, insbesondere bei Listen, als URL-Parameter übergeben. Dies unterwandert das Konzept der Komponenten, da der URL-Parameter dann auf Page-Ebene behandelt wird. Dafür muss zusätzlicher Code geschrieben werden, vermindert die Wartbarkeit und erhöht die Fehleranfälligkeit durch Inkonsistenzen zwischen URL-Parameter und Code-Verarbeitung. Des Weiteren widerstrebt es dem Konzept von meaningful URL und läd zur Manipulation ein.

Beispiel#

Folgendes Beispiel zeigt den Einsatz des inputHidden-Tags für ein Steuerelement eines Paging-Mechanismus einer Liste. Es ist ein Mini-Formular mit einem commandLink und dem inputHidden, das den gewünschten Index direkt auf die Liste schreibt.
<h:form>
	<h:commandLink action="redirect">#{index}</h:commandLink>
	<i:inputHidden value="#{list.firstResult}" from="#{index}"/>
</h:form>

Statt dem h:commandLink kann natürlich auch ein a4j:commandLink benutzt werden.

Tag cell#

Der cell-Tag erlaubt die Generierung klickbarer Tabellen, wie z.B. eines Kalenders. Die herausragende Eigenschaft von <i:cell> ist der Event "onclick". Cell kann innerhalb von table/tr verwendet werden, um Events zu steuern. Insbesondere AJAX-Calls wie durch a4j:support. Im Folgenden ist die "<<"-Taste eines Kalenders dargestellt, der bei einem Kunden produktiv eingesetzt wird (Code aus datenschutzrechtlichen Gründen leicht modifiziert):
<i:cell styleClass="calendar-cell">
    <h:outputText value="&lt;&lt;"/>
    <a4j:support event="onclick" action="#{comp.page(-3)}" reRender="#{comp.id}-panel"
	limitToList="true" ajaxSingle="true"/>
</i:cell>

Tag validator#

Mit dem validator-Tag können Validators anhand EL-Expressions geschrieben werden. Das Schreiben vieler Validator-Classes wird dadurch erspart. Darüber hinaus ist mit dem validator-Tag eine Parametrisierung der Validierung möglich.

Beispiel ist ein Validator für zwei Datumswerte, der den Anfang eines Events vor dessen Ende prüft:

<i:inputCalendar value="#{event.from}/>
<i:inputCalendar value="#{event.to}>
    <i:validator expression="#{event.from lt value}" message="From before to!"/>
</i:inputCalendar>

ClassPathResource#

Mit dieser Klasse kann ein Resource-Path direkt aus dem ClassLoader in die Web-Applikation exponiert werden. Eine JAR-Datei die sich in einem WAR befindet, hat zunächst keine Möglichkeit auf einen URL zu mappen. Innerhalb eines Facelets aus einer Taglib können zwar URLs erzeugt werden mit h:graphicImage, h:outputLink etc., jedoch keine Dateien als Linkziel exponiert. Die Klasse ClassPathResource exponiert einen Resource-Path mittels eines Eintrag in die components.xml:
<component name="taglibResource" class="com.intersult.ui.util.ClassPathResource" scope="application">
    <property name="resourcePath">/taglib</property>
    <property name="classPath">/resource</property>
</component>

Die Resourcen sind dann zugreifbar mit:

<t:stylesheet path="#{taglibResource.get('/style.css')}"/>

Alle Resources mit Mime-Type text/* werden dabei interpoliert. Insbesondere CSS-Stylesheets und Javascript-Dateien die durch den meta-Tag geladen werden, können dabei mit EL-Expressions versehen werden. Dies ist z.B. interessant beim Styles vom Typ "background-image", da hier über "url(...)" auf eine Resource zugegriffen wird.

UrlResource#

Web-Sites bestehen heute nicht mehr aus einem einzigen Server, sondern aus einer Infrastruktur mit internen und externen Services. Diese Services können Binärdaten als Stream liefern, z.B. wenn Icons angezeigt werden müssen. Die URLs sind oft aufgrund der Sicherheit oder aus technischen Gründen nicht direkt durchreichbar, werden also über den Web-Server gestreamt.

Die Intersult Taglib stellt eine Bean auf Applicationebene zur Verfügung, mit der URLs gestreamt werden können:

<h:graphicImage value="#{urlResource.get(some.bean.url)}"/>

Was macht urlResource.get(...)? Die Methode generiert aus einem URL, der nur innerhalb der Infrastruktur zugreifbar ist einen virtuellen URL auf dem Web-Server. Über diesen URL kann später auf den ursprünglichen URL zugegriffen, also auch gestreamt werden. Der Zugriff ist dabei sicher, da von außen keine URLs generisch erzeugt werden können. Nur im Component-Tree gerendete URLs sind gültig.

ResourceBundle#

Das ResourceBundle ist eine Erweiterung der Resource-Bundles (länder- und sprachspezifisches Laden von *.properties Resourcen) von JSF und Seam um folgende Features:

Dazu wird einfach ein Einträge nach folgendem Schema in die components.xml der Taglib-JARs und der Applikation gemacht:

	<component name="transportBundles" class="com.intersult.ui.util.ResourceBundle" scope="application" startup="true">
		<property name="bundleNames">
			<value>transport-messages</value>
		</property>
	</component>

Die Resource-Bundles werden dann sauber zusammengeführt und geladen.

Interception#

Mit der Intersult taglib können EJB3 Interceptoren durch Events realisiert werden. Konkret heißt das, alle Method Invocations auf Seam- und EJB3-Beans können per Observation abgefangen werden.

Eigene Interceptoren#

@Observer("com.intersult.interceptor.preInvoke")
public void preInvoke(InterceptorContext context) throws Exception {
    ...
}

@Observer("com.intersult.interceptor.postInvoke")
public void postInvoke(InterceptorContext context) throws Exception {
    ...
}

InterceptorContext enthält dabei die Felder:

EjbInjector#

Der EjbInjector ist eine bereits in der Intersult taglib enthaltener Interceptor der auf der Event-Technologie basiert. Werden in Seam-Beans @EJB-Injections benutzt, sind diese normaler Weise unwirksam. Der EjbInjector löst diese Abhängigkeiten auf. Entweder der vollständige Name wird angegeben:

@EJB(name = "earName/beanName/local"
private Bean bean;

Oder der Name wird generiert aus dem Seam jndiPattern aus der Init-Komponente, die in der components.xml definiert wird:

JBoss:

<core:init jndi-pattern="ear-name/#{ejbName}/local"/>

Glassfish:

<core:init jndi-pattern="java:comp/env/#{className}/#{fieldName}"/>

In der Seam-Bean braucht dann nur noch die @EJB-Annotation zu stehen:

@EJB
private Bean bean;

Installation#

Maven2 Integration#

Läuft der Build-Prozess in Ihrem Projekt bereits auf Maven2? Wenn nicht, sollten Sie es sich gründlich überlegen. Der Aufwand zum Einlernen ist sehr gering und der Benefit beim Dependency, Build, Deployment, Distribution, Source Code, Development Environment und andere Prozesse profitieren wesentlich.

Um die Taglib einzubinden, brauchen Sie nur folgende Einträge in Ihrem pom.xml vorzunehmen:

<project>
    ...
	<dependencies>
	    ...
		<dependency>
			<groupId>com.intersult</groupId>
			<artifactId>meta-ui</artifactId>
			<version>1.1-SNAPSHOT</version>
		</dependency>
	    ...
	</dependencies>
    ...
	<repositories>
            ...
		<repository>
			<id>intersult-repository</id>
			<name>Intersult Repository</name>
			<url>http://repository.intersult.com/repository</url>
		</repository>
	    ...
	</repositories>
    ...
</project>

Bei dem Arbeiten mit Eclipse, ist die Taglib nach einem "mvn eclipse:eclipse" und einem Refresh des Projekts verfügbar. Ihnen ist unklar, wie der Content-Assist unter Eclipse für Tags korrekt konfiguriert wird? Wir beantworten gerne diese und weiter Fragen. Nehmen Sie Kontakt mit uns auf.