JQueryWidget2 ist das neue Werkzeug, um noch effizienter das am weitesten verbreitete Browser-Framework JQuery mit dem am weitesten verbreiteten Business-Framework und Microservices zu kombinieren.
Erstellen Sie eigene Komponenten mit einem hohen Grad an Interaktivität, indem Sie auf JQueryWidget2 aufbauen. Sie erhalten damit eine Programmierschnittstelle (API), mit der Sie sich auf das Wesentliche konzentrieren können.
public static final String DEFAULT_EVENT = "timeout"; public static final Collection<String> EVENT_NAMES = Arrays.asList(DEFAULT_EVENT); @Override public Collection<String> getEventNames() { return EVENT_NAMES; } @Override public String getDefaultEventName() { return DEFAULT_EVENT; }
Diese Events können später von Tags empfangen werden, wie <j:behavior> oder <p:ajax>.
JQueryWidget2 sendet die entsprechenden Javascript Funktionen direkt an die JQuery Komponente, sodass diese Events im Javascript Code aufgerufen werden können:
if (widget.options.timeout) widget.options.timeout();
Vorher wird abgefragt, ob eine Funktion für diesen Code geliefert wurde. In der Regel ist die Nutzung der Events optional.
@FacesComponent(namespace = JQueryWidget2.NAMESPACE, createTag = true) @ResourceDependencies({ @ResourceDependency(name = "jquery/jquery.js", library = "primefaces"), @ResourceDependency(name = "jsf-jquery.js", library = "jquery-js"), @ResourceDependency(name = "jquery.throttle.min.js", library = "jquery-js"), @ResourceDependency(name = "matrix.js", library = "jquery-js"), @ResourceDependency(name = "matrix.css", library = "jquery-css") }) public class Matrix extends JQueryWidget2 implements NamingContainer { [...] }
Damit ist es möglich, der Komponente einzelne HTML-Tags hinzuzufügen, die dann ein eindeutiges Prefix besitzen. Dazu wird die Methode addElements überschrieben:
@Override public void addElements(List<UIComponent> elements) { elements.add(new WidgetElement<String>("table") {}); }
Hinweis: WidgetElement ist die einfachste Klasse zum Implementieren eines Elements. Da es sich um eine generische Klasse mit einem Typ-Parameter handelt, muss der Typ-Parameter spezifiziert werden. Da dieser in dem Beispiel nicht wirklich benötigt wird, geben wir hier String an.
Dadurch wird folgender HTML-Code geschrieben:
<span id="form:matrix" class="j-matrix" style="height: 500px;"> <span id="form:matrix:table" class="j-matrix-table"/> </span>
Das Element erhält eine Id, die sich aus der Client-Id des JQueryWidget2 zusammensetzt zuzüglich des Namens des Elements selbst: "<client-id>:table".
Im Javascript Code wird dann über das Property elements auf die Elemente zugegriffen, also mit this.elements.table. Darin befindet sich unter anderem das HTML-Element (JQuery-gewrappt):
this.elements.table.element()
Damit kann man bequem alle möglichen Operationen auf dem Element des Widgets ausführen.
@Override public void addElements(List<UIComponent> elements) { elements.add(new WidgetBehavior<String>("table") {}); }
Hinweis: Hier wird die Klasse WidgetBehavior implementiert. Der Typparameter erfüllt hier die Aufgabe, den Typ des AJAX-Models festzulegen. Aufgrund der Einfachheit verwenden wir hier wieder String.
JSF verwendet zum Abschicken des Requests immer ein Element mit einer Client-Id. Um bestimmte Operationen auszuführen, wird auch ein Element upgedatet. Die Grundeinstellung bei JQueryWidget2 ist, dass das Element sowohl den AJAX-Request abschickt, als auch upgedated wird. So braucht man zunächst keinerlei Konfiguration vorzunehmen, AJAX läuft also "out of the box":
this.elements.table.request()
Auf der Java-Seite können wir den Aufruf nun empfangen mit:
@Override public void addElements(List<UIComponent> elements) { elements.add(new WidgetBehavior<String>("table") { @Override public void invokeApplication() { System.out.println("table was invoked"); } }); }
WidgetBehavior besitzt folgende Methoden, um den JSF-Lifecycle abzubilden:
void decodeData(T data) void invokeApplication() void encodeContent(FacesContext context) throws IOException Object encodeData()
Methode | Bedeutung |
---|---|
decodeData | Hier werden die vom Javascript gesendeten JSON-Daten fertig deserialisiert als Java-Objekt geliefert |
invokeApplication | Aufruf in der Invoke Application Phase |
encodeContent | Hier kann HTML-Content des Elements geschrieben werden |
encodeData | Hier kann ein Java-Objekt in der Form von JSON-Daten an den Javascript-Code zurückgegeben werden |
Auf der Javascript Seite sieht die API dann folgendermaßen aus:
this.elements.table.request({ data: "Hello", callback: function(data) { console.log("Response: " + data); } });
Es können also zwei unterschiedliche Klassen für den AJAX-Aufruf und die AJAX-Antwort verwendet werden. Sowohl Request-Parameter als auch Response-Parameter sind jeweils optional und können bei Bedarf verwendet werden. Werden sie nicht gebraucht, können die entsprechenden Methoden einfach weggelassen werden.
Der AJAX-Aufruf erfolgt asynchron. Die Daten werden durch das Property "data" mitgegeben. In unserem Beispiel entspricht der Datentyp String. Es kann hier jede JSON-Deserialisierbares Java-Klasse verwendet werden. Die Antwort erfolgt in der Form eines Callbacks, da die Antwort asynchron kommt. Dazu übergeben wie die Callback-Funktion durch das Property "callback". Die Funktion kann optional das Argument "data" haben, welches dann die AJAX-Response bringt.
Daten | Javascript | Java |
---|---|---|
Request | data | decodeData(T data) |
Response | callback(data) | Object encodeData() |
Hinweis: Die AJAX-Response Methode in Java hat den Rückgabetyp Object. Dieser kann beim Überschreiben auf eine Klasse festgelegt werden. Allerdings kann auch weiterhin Object verwendet werden, da die JSON-Serialisierung die Klasse aus dem tatsächlich zurückgegebenen Objekt verwendet.
public class ScrollableElement extends WidgetBehavior<String> { public ScrollableElement(String id) { super(id); } @Override public void encodeContent(FacesContext context) throws IOException { ResponseWriter writer = context.getResponseWriter(); String prefix = getClientId() + UINamingContainer.getSeparatorChar(context); writer.startElement("div", null); writer.writeAttribute("id", prefix + "cells", null); writer.writeAttribute("class", getParent().getWidgetStyleClass() + "-cells", prefix); writer.endElement("div"); } }
Erklärung: Hier wurde WidgetBehavior abgeleitet um ein scrollbares <div>-Tag innerhalb des default-mäßigen <span>-Tags zu erzeugen. Eine entsprechende Client-Id und Style Klassen werden gesetzt.
Von unserer Seite her, also vom JQueryWidget2 gibt es eine Reihe von Möglichkeiten zum Übertragen von Options:
@Override public String getOptions(Object... properties) { return super.getOptions(properties, "value", "scroll"); }
Hinweis: Um ein kaskadiertes Überschreiben der Methode zu ermöglichen, werden die der Methode selbst übergebenen Properties auch wieder an den Super-Aufruf übergeben.
<app:customTable id="table" value="#{customTableController.model}"> <p:ajax listener="#{customTableController.select}"/> </app:customTable>
<j:matrix id="matrix" value="#{matrixController.model}"> <j:function name="format" arguments="cell, data"> cell.text(data.v) .css({backgroundColor: data.c}); </j:function> </j:matrix>
@Override public void addElements(List<UIComponent> elements) { elements.add(new WidgetBehavior<Drop>("drop") { @Override public void addOptions(Map<String, Object> options) { if (getUpdate() != null && !"".equals(getUpdate())) options.put("update", BuildingBlockDragDrop.this.findComponent(getUpdate()).getClientId()); } @Override public void decodeData(Drop data) { if (action != null) action.invoke(getFacesContext().getELContext(), new Object[] {data}); } }); }
@Override public void addWidgetOptions(Map<String, Object> options) { options.put("tree", findComponent(getTree()).getClientId()); }
Hinweis: In der Regel wird die Anwendung einfacher konfigurierbar und modularer, wenn man zunächst die anderen Optionen ausschöpft. Diese Art zum Hinzufügen von Parametern kann für sehr spezifische Optionen verwendet werden, von der kein allgemeiner Gebrauch abgeleitet wird.
@FacesComponent(namespace = AppComponent.NAMESPACE, createTag = true) @ResourceDependencies({ @ResourceDependency(name = "jquery/jquery.js", library = "primefaces"), @ResourceDependency(name = "jsf-jquery.js", library = "jquery-js"), @ResourceDependency(name = "jquery.countdown.min.js", library = "js"), @ResourceDependency(name = "timer.js", library = "js") }) public class Timer extends JQueryWidget2 { public static final String DEFAULT_EVENT = "timeout"; public static final Collection<String> EVENT_NAMES = Arrays.asList(DEFAULT_EVENT); @Override public Collection<String> getEventNames() { return EVENT_NAMES; } @Override public String getDefaultEventName() { return DEFAULT_EVENT; } public Integer getTime() { return (Integer)getStateHelper().eval("time"); } public void setTime(Integer time) { getStateHelper().put("time", time); } public String getFormat() { return (String)getStateHelper().eval("format"); } public void setFormat(String format) { getStateHelper().put("format", format); } public String getOntimeout() { return (String)getStateHelper().eval("ontimeout"); } public void setOntimeout(String ontimeout) { getStateHelper().put("ontimeout", ontimeout); } @Override public String getOptions(Object... properties) { return super.getOptions("time", "format", "timeout"); } }
Der Javascript-Code für die JQuery-Komponente:
//# sourceURL=timer.js // Documentation: http://hilios.github.io/jQuery.countdown/documentation.html $.widget("ext.Timer", { options: { format: "%M:%S" }, _create: function() { this._super(); var widget = this; this.element.on("update.countdown", function(event) { widget.element.text(event.strftime(widget.options.format)); }); this.element.on("finish.countdown", function(event) { if (widget.options.timeout) widget.options.timeout(); widget.start(); }); if (this.options.time) this.start(); }, start: function() { this.element.countdown(Date.now() + 1000 * this.options.time); } });