10/29/2010

Die eigene JSF Komponente (incl Bean - Verweis) unter JSF 1.2

Hier eine kleine Anleitung, wie man seine eigene JSF - Komponente baut, und diese so gestaltet, daß sie mit verschiedenen Beans wiederverwenderbar ist. Nicht ganz so komfortabel wie die custom components bei JSF 2.0, aber es lassen sich fast die gleichen Ergebnisse erzielen.

1) Referenz auf die zu erstellende TagLib erstellen:

Den folgenden Eintrag der web.xml hinzufügen:


<context-param>
<param-name>facelets.LIBRARIES</param-name>
<param-value>/ui/templates/taglib.xml</param-value>
</context-param>
Damit ist eure Taglib JSF bekannt gemacht.

2) Die TabLib mit Verweisen zu den einzelnen Files füllen:

Hier ein Beispieleintrag in der taglib (liegt unter /ui/templates/taglib.xml), die auf eine Komponente verweist:
<?xml version="1.0"?>
<!DOCTYPE facelet-taglib PUBLIC
  "-//Sun Microsystems, Inc.//DTD Facelet Taglib 1.0//EN"
  "facelet-taglib_1_0.dtd">
<facelet-taglib>
<namespace>http://some.namespace.com</namespace>
<tag>
<tag-name>beanComponent</tag-name>
<source>beanComponent.xhtml</source>
</tag>
</facelet-taglib>

Über den angegebenen namespace (some.namespace.com) könnt ihr später von euren referenzierenden Files auf eure Komponenten zugreifen. Der Zugriff erfolgt über den tag namen (hier: beanComponent).

3) Aufruf der Komponente

Für den Aufruf innerhalb einer facelets Datei, fügt den namespace zu den referenzierten namespaces im header hinzu:
xmlns:some="http://some.namespace.com"
Auch das geht unter JSF 2.0 einfacher ... anyway ... der Zugriff kann nun folgendermaßen erfolgen:

<some:beanComponent bean=${backingbean}/>

In diesem Beispiel übergeben wir eine komplette Referenz auf eine JSF backing bean an unsere Komponente. Egal wie wir die Komponente dort nutzen wollen, wichtig ist, daß die Referenz nicht wie bei Standard - JSF üblich mittels #{backingbean} sondern über den JSTL Ausdruck ${} erfolgt.

4) Die Komponente

Die Komponente enthält zuerst einmal alle üblichen Referenzen, startet dann ein ui:composition, alles wie immer, und greift dann auf Methoden in der Bean zu:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:z="http://www.qualcomm.com/jsf/core"
xmlns:c="http://java.sun.com/jstl/core"
xmlns:fn="http://java.sun.com/jsp/jstl/functions">
<ui:composition>
<h:outputText value="#{backingBean.wert}"/>
</ui:composition>
</html>


Die Bean kann somit ganz einfach (wie immer) über #{backingBean.wert} angesprochen werden. Das ermöglicht es, dieselbe Komponente mit verschiedenen Beans anzusprechen.
Damit könnte dann ein dokumenten browser oder ähnliches erstellt werden.

5) Erweiterung

Sollen nicht nur die Beans sondern auch die Methoden - Namen dynamisch sein, so kann der Methodenname als eigener Parameter übergeben werden:

<some:beanComponent bean=${backingbean} method="wert"/>
Der Zugriff erfolgt dann folgendermaßen:
<h:outputText value="#{backingBean[method]}"/>

Zu empfehlen ist das jedoch nicht - die Coupling steigt und die Wartbarkeit sinkt.


10/15/2010

Inter - Portlet Communication mit Liferay, Teil II

Public render parameter (JSR 268)

Mit Hilfe von public render parametern können Portlet-übergreifend String oder String[] - Parameter verwendet werden. Um dies zu nutzen,müssen die public render parameter einzeln in der portlet.xml festgehalten werden.
Zuerst als Definition in einem der deployten Portlets: 
<public-render-parameter>
     <identifier>pr</identifier>
     <qname xmlns:x="http://sun.com/params">x:pr</qname>
  </public-render-parameter>
Alle Portlets die diesen Parameter nutzen möchten, müssen den folgenden EIntrag zur portlet.xml hinzufügen:
<supported-public-render-parameter>pr</supported-public-
Der Zugriff erfolgt dann (in einem non - JSF - Portlet):
public class SomePortlet extends GenericPortlet {
           
 public void processAction(ActionRequest req, ActionResponse res)
         throws IOException, PortletException {
                 res.setRenderParameter("pr", "wert");
           }
      }
Der Zugriff in einem JSF - Portlet erfolgt folgendermaßen:
FacesContext context = FacesContext.getCurrentInstance();
PortletRequest request = (PortletRequest)context.getExternalContext().getRequest();
return request.getParameter("pr");
Soll der PR - Parameter in einem JSF Portlet nicht nur abgefragt, sondern auch gesetzt werden, so ist der Folgende Wert in der portlet.xml zu setzen:
<init-param>
 <name>com.sun.faces.portlet.SAVE_REQUEST_SCOPE</name>
 <value>true</value>
</init-param>  

ice:messages und popups

Problem:

ice:messages existieren nur so lange, bis ... naja, wie lange eigentlich ? Nun, zumindest so lange bis die Seite neu gerendert wird. Und das scheint auch der Fall zu sein, wenn ein Popup verschwindet.
Wenn also ein Popup genutzt wird, um vor einer Aktion nachzufragen ob diese wirklich durchgeführt werden soll, so werden alle ice:messages die in der backing bean hinzugefügt werden, sofort wieder verschwinden.

Meine Lösung:

ice:outputText gestylt mit den styles für errors und info. Gerendert nach der Aktion, rendered=false bei der nächsten Aktion.

10/01/2010

Probleme mit dem ICEFaces DataTable und Liferay

Problem : Wird das attribut scrollable="true" an eine ICEFaces dataTable gesetzt, kommt ICEFaces bei der justierung der Breiten der Tabellenheader und der Tabellenspalten durcheinander.
Konkret: Die Tabellenheader passen nicht zur Breite der Tabellenspalten.

Lösung: Zurzeit habe ich nur ein Provisorium gefunden:

1) Die Tabelle bekommt mit columnWidths die pixelgenaue Breite der Tabellenspalten übergeben. Prozentuale Werte sind leider nicht möglich (siehe Tag Beschreibung)

2) Die columns bekommen auch die Breite pixelgenau übergeben, per css.

Beispiel:
<ice:dataTable id="selectedDocuments"
style="width:100%;border-width:0px" columnWidths="900px, 900px;"
value="#{bean.werte}" var="wert" scrollable="true" resizable="false">

<ice:column style="width: 900px;">
<f:facet name="header">
<ice:outputText value="Title" />
</f:facet>
<ice:outputText value="text" />
</ice:column>
<ice:column style="width: 900px;">
<f:facet name="header">
<ice:outputText value="Titel" />
</f:facet>
<ice:outputText value="text2" />
</ice:column>
</ice:dataTable>


Problem 2: Auch wenn man jetzt Header und Spalten "gleichgeschaltet" hat - man kann die Tabellen immer noch nicht einfach so in einem Portlet nutzen - denn wie groß soll die Tabelle denn sein ? Ein Portlet kann seine Größe schließlich ändern ...

Lösung:  Wenn die dataTable mit 100% gestylt wird, nimmt die erst einmal die gesamte Breite ein. Dann die Tabellenspalten breiter machen, wie die Bildschirmauflösung - also so breit, daß es auf jeden Fall immer die gesamte Breite einnehmen wird (oben habe ich 900px genommen). Die Kombination aus 100% und den zu breiten Spalten sorgt dafür, daß die Tabelle die gesamte Breite einnimmt, aber nicht mehr. Sie skaliert sich zu recht. Sollte ich noch einen Weg finden, wie ich an die Portletbreite komme, dann werde ich diesen Eintrag noch updaten.


UPDATE

Das funktioniert natürlich nicht für IE 6 - IE8. Dort bleibt uns nichts anderes übrig als absolute Werte zu nehmen.

Styling von ice:menuItem

Problem:

ICEFaces erlaubt das erstellen von <ice:menuItem> EInträgen entweder durch Nutzen eines ActionListeners oder eines Links. Je nach dem, welche Version genommen wird, wird entweder die css Klasse iceLink oder iceCmdLnk genommen. Das kann dazu führen, daß die Einträge innerhalb eines< ice:menuBar> Eintrags unterschiedlich aussehen.

Weiteres Problem: Es lässt sich kein Style direkt an das <a href ... welches gerendert wird, anhängen. direkt iceCmdLnk zu nutzen, bringt auch nichts, da iceLink die Werte überschreibt.

Einzige Lösung: Eigene leere CSS - Klasse, deren untergestellt ein spezieller Eintrag nur für a - Elemente unterhalb der neuen Klasse:


.myMenuItem{ }


.myMenuItem a{
    //CSS Werte ...
}


Diese neue eigene Klasse lässt sich nun dem <ice:menuItem> verpassen.



<ice:menuItem value="value" styleClass="myMenuItem
link="http://etc"
id="myId"/ >


Der zweite Schritt besteht nun darin, auch den Style anzuwenden, der bei Hover funktioniert.
Hierzu muss zuerst der Style applied werden, der die Box um den Wert herum stylt:



.myMenuItem a:hover {}

Danach wird der Style angewandt, der den Text innerhalb der Box stylt:


myMenuItem a
:hover span {}



Genau so, und nicht anders wird dafür Sorge getragen, daß andere menuItems nicht unabsichtlich mitgestylt werden.