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.

Inhalt#

Übersicht#

Folgende Möglichkeiten stehen mit JQueryWidget2 zur Verfügung:
  • Native Tags: Mit JQuery kombinierte JSF-Tags
  • JSF-Lifecycle: AJAX-Request mit voller Unterstützung des JSF-Lifecycles
  • ClientBehaviorHolder: Die Tags können Events definieren, die <f:ajax>, <p:ajax> oder andere Behavior-Tags enthalten können
  • AJAX-Update: Die Tags unterstützen den vollen AJAX-Update mit JSF. Je nach Anforderung werden Daten auf unterschiedliche Weise persistiert
  • Child-Tags: Untergeordnete Tags werden am Anfang mit raus gerendert. Optional kann der untergeordnete DOM-Tree an das JQuery-Widget übergeben werden

Documentation#

Im Folgenden wird die API von JQueryWidget2 dokumentiert.

Events#

Events können standardmäßig wie in JSF2 verwendet werden. Dazu werden zunächst die entsprechenden Methoden der UIComponent implementiert:
	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.

Elements#

Eine komplexe Komponente besteht oft aus mehreren Teilen. Diese Teile möchte man getrennt updaten können, Daten mit unterschiedlichen Modellen austauschen können und unterschiedliche Events auslösen. Möchte man Elemente verwendet, implementiert man zunächst NamingContainer in der Komponente:
@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.

AJAX Requests#

Um einen JSF2 konformen AJAX-Request auszuführen, wird ein Element vom Typ WidgetBehavior verwendet.
	@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()

MethodeBedeutung
decodeDataHier werden die vom Javascript gesendeten JSON-Daten fertig deserialisiert als Java-Objekt geliefert
invokeApplicationAufruf in der Invoke Application Phase
encodeContentHier kann HTML-Content des Elements geschrieben werden
encodeDataHier 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.

DatenJavascriptJava
RequestdatadecodeData(T data)
Responsecallback(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.

Beispiele#

Die folgenden Beispiele verdeutlichen den Einsatz des JQueryWidget2.

Timer#

Der Java-Code für die Komponente:
@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);
	}
});