Diese JSF-Erweiterung ermöglicht es, einfache Javascript und JQuery Komponenten JSF-Konform zu implementieren. JSF-JQuery enthält zwar einige fertig einsatzfähige Komponenten, versteht sich allerdings vorwiegend als API zum Implementieren eigener Komponenten.
Der Nachteil von JSF besteht jedoch in einem Aufwendigen Rekonstruieren der gesamten Seitenstruktur. Gerade bei komplexen Web-Seiten führt dies zu einer immensen Rechenzeit, sowieso hohem Speicherverbrauch.
Reine Javascript-Komponenten haben den Vorteil, dass die Elemente auf der Web-Seite sehr unabhängig voneinander agieren können. Änderungen, Server-Requests und andere Aktionen sind daher sehr leichtgewichtig. Allerdings liefert JQuery und andere Javascript-Bibliotheken aufgrund ihrer Beschaffenheit, kein Mittel um den Aufbau der Web-Seite zu strukturieren. Javascript-Bibliotheken werden daher selten im Business-Bereich eingesetzt.
JSF-JQuery kombiniert nun die Vorteile beider Welten:
Zusätzlich sind einige Hilfskomponenten und APIs vorhanden, die das Implementieren von Komponenten und deutlich erleichtern.
<c:forEach begin="1" end="3" var="index"> <p:commandButton id="index-#{index}" value="Index Button #{index}" ajax="false"> <j:ajax id="ajax-#{index}" listener="#{jqueryController.indexAction(index)}"/> </p:commandButton> </c:forEach>
Das Beispiel erzeugt 3 Knöpfe durch einen For-Each-Loop und ruft die Action-Methode eines Controllers auf. Zusätzlich wird die Index-Variable als Parameter übergeben.
Ein Beispiel:
<j:function name="hello" arguments="name"> alert('Hello ' + name); </j:function>
Im JSON-Array erscheint folgendes:
{ [...] "hello": function(name) { alert('Hello ' + name); }, [...] }
Hinweis: Der Wert des JSON-Elements "hello" enthält keine Anführungsstriche. Auf Javascript-Seite wird dadurch direkt ein Javascript-Code erzeugt, der nachher ausgeführt werden kann.
<j:autocomplete id="autocomplete" complete="#{jqueryController.complete}"> <p:inputText id="input" value="#{jqueryController.value}" autocomplete="off"/> </j:autocomplete>
Bei der Implementierung wurde bewusst darauf verzichtet, den kompletten Input-Text-Tag ebenfalls zu implementieren, da es dafür bereits umfangreiche Tags gibt. Er wird daher als Child-Element eingefügt, wie im Beispiel zu sehen ist.
Der Tag <j:autocomplete> unterstützt den Parameter "cellRenderer", mit dem eine Javascript-Function übergeben werden kann, um das standardmäßige Cell-Rendering zu ersetzen. Hier eine Beispiel, wie die Option eingesetzt werden kann:
<j:function name="cellRenderer" arguments="$row, key, value"> if (key == "styleClass") { $row.addClass(value); } else { var $cell = $("<td/>"); $cell.attr("title", value); $cell.html(value); if (key == "label") { $cell.css("background-color", "black"); $cell.css("color", "yellow"); } $row.append($cell); } </j:function>
<h:link value="#{transaction.signal.symbol}" outcome="/chart.xhtml"> <j:json name="analyzer"> <j:array name="generators"> <j:element> <j:element name="type" value="stockDataMongoGenerator"/> <j:element name="name" value="#{transaction.signal.symbol}"/> <j:element name="symbol" value="#{transaction.signal.symbol}"/> <j:element name="count" value="#{simulatorController.ticks}"/> </j:element> <j:element> <j:element name="type" value="indicatorGenerator"/> <j:raw name="indicator" value="#{simulatorController.json}"/> </j:element> </j:array> </j:json> </h:link>
@Override public Object processRequest(Map<String, String> parameters) { return getListener().invoke(getFacesContext().getELContext(), new Object[] {}); }
Das Implementieren dieser Methode ist denkbar einfach. Der Aufruf wird einfach entgegen genommen, zusätzlich wird die Request-Parameter-Map übergeben, da diese hier häufig benötigt wird. Zurückgegeben wird ein Java-POJO, welches vom Framework JSON-Serialisiert wird und an den AJAX-Aufrufer aus dem Javascript zurückgesendet wird. Damit ist Hin- und Rückweg vollständig in einer Methode implementierbar.
Die Parameter im folgenden erklärt:
Name | Bedeutung | Standard-Quelle |
---|---|---|
baseUrl | Der Servlet-Path bis zum aktuellen Faces-Servlet | JQueryHelper.getBaseUrl() |
viewId | Die aktuelle View-Id der XHTML-Page auf der sich die Komponente befindet | FacesContext.getCurrentInstance().getViewRoot().getViewId() |
source | Die Komponente, die den AJAX-Request ausführt | getClientId() |
options | Zusätzliche Optionen | getOptions() |
Ein Beispiel aus dem generierten Javascript-Code:
ext.jquery.action('/spring-boot-test/faces', '/page/jquery.xhtml', 'form:rest-index-buttons:ajax-2', {});
Üblicherweise wird der Javascript-Code für den Action-Aufruf aus dem Encoding der JQuery-Komponente geschrieben:
@Override public void encodeEnd(FacesContext context) throws IOException { ResponseWriter writer = context.getResponseWriter(); writer.startElement("script", this); writer.writeAttribute("type", "text/javascript", null); writer.writeText("ext.jquery.action('" + JQueryHelper.getBaseUrl() + "', '" + context.getViewRoot().getViewId() + "', '" + getClientId() + "', " + getOptions());", this, null); writer.endElement("script"); }
Hat man Primefaces im Projekt, werden die meisten Komponenten so aussehen:
@FacesComponent(namespace = JQueryComponent.NAMESPACE, createTag = true) @ResourceDependencies({ @ResourceDependency(name = "jquery/jquery.js", library = "primefaces"), @ResourceDependency(name = "jsf-jquery.js", library = "jquery-js") }) public class Test extends JQueryComponent { [...] }
Damit stellt JSF automatisch sicher, dass sich die benötigten Scripte auf der Web-Seite befinden und lädt diese gegebenenfalls nach.
Die Annotation FacesComponent kann dazu verwendet werden, um den Tag direkt zu erzeugen, ohne eine taglib.xml zusätzlich anlegen zu müssen.