I continue the blog post series (Part 1, Part 2) about the internal of QxWT by describing how one can even extend qooxdoo-JS-classes using the GWT-generator features without writing a single line of JavaScript-code.
In the last blog entry we learned how to define a qooxdoo-JS-Class from scratch which was quite an easy task and so is the extending of qooxdoo classes. The idea is that we need to generate code which overloads an existing qooxdoo-class method and delegate the method call to our JavaCode.
Let’s take a look at an qooxdoo-example at first to see how extending a class and changing it’s behavior is done in “native” code. A good example is the JsonStore which makes it very easy to work with JSON-Datasources in qooxdoo.
A problem though is that if you need to load the data from an URL which is different than the one our application is coming from you can not use the JsonStore coming with the qooxdoo-framework but need to extend it so that the requested is done differently.
In plain qooxdoo a specialized JsonStore which e.g. connects to twitter looks like this:
qx.Class.define("demobrowser.demo.data.store.Twitter", { extend : qx.data.store.Json, statics : { saveResult: function(result) { this.__result = result; } }, construct : function(user) { var url = "http://twitter.com/statuses/user_timeline/" + user + ".json"; this.base(arguments, url); }, members : { _createRequest: function(url) { var loader = new qx.io.ScriptLoader(); url += "?callback=demobrowser.demo.data.store.Twitter.saveResult"; loader.load(url, function(data) { this.__loaded(); }, this); }, __loaded: function() { var data = demobrowser.demo.data.store.Twitter.__result; if (data == undefined) { this.setState("failed"); return; } // create the class this._marshaler.toClass(data); // set the initial data this.setModel(this._marshaler.toModel(data)); // fire complete event this.fireDataEvent("loaded", this.getModel()); } }
The above class inherits from the base implementation and overloads the method _createRequest to make a cross domain request. What we want to achive is that the GWT-Generator creates a qooxdoo-class like this:
qx.Class.define("demobrowser.demo.data.store.Twitter", { extend : qx.data.store.Json, members : { _createRequest: function(url) { // Delegate to Java } } }
The first thing we need is another annotation which makes it possible to declare a Java method to be the delegate of the JavaScript-method:
/** * Define a the native method that will call back to this java method */ @Target(ElementType.METHOD) public @interface QxNativeMethod { /** * @return name of the javascript method */ String name(); /** * @return <code>true</code> if the js method should call the overloaded * method before delegating to java */ boolean callsuper() default false; }
We can now annotate Java-Methods like this:
@QxNativeMethod(name = "_createRequest") public void qxcb_createRequest(String url) { // Logic written in Java }
and our GWT-Generator takes care of generating a qooxdoo-class and delegating method calls to our Java implementation.
Reimplementing the Twitter-JsonStore from above in plain QxWT would look like this:
@QxClass(jsBaseClass = "qx.data.store.Json", properties = {}) public abstract class TwitterStore extends QxJsonStore<Object> { private JavaScriptObject result; /** * @param o */ public TwitterStore(JavaScriptObject o) { super(o); } @QxNativeMethod(name = "_createRequest") public void qxcb_createRequest(String url) { QxScriptLoader loader = new QxScriptLoader(); url += "?callback=saveResult"; VoidParam<String> statusCallback = new VoidParam<String>() { @Override public void invoke(String value) { loaded(); } }; VoidParam<JavaScriptObject> globalFunctionCallback = new VoidParam<JavaScriptObject>() { @Override public void invoke(JavaScriptObject value) { result = value; } }; loader.load(url, statusCallback, "saveResult",globalFunctionCallback); } private void loaded() { if (result == null) { setState(State.FAILED); return; } QxModelObject model = QxJsonMarshaler.createModel(result, true); setModel(model); // fire complete event this.fireDataEvent("loaded", this.getModel()); } }
The Store can now be used like this:
// fetch some data from Twitter final TwitterStore store = GWT.create(TwitterStore.class); store.setUrl("http://twitter.com/statuses/user_timeline/wittemann.json");
Behind the scenes GWT generated the following Java-Class for us:
package org.ufacekit.qx.wrapper.demo.client.demobrowser.demo.data; public class TwitterStore_Gen extends org.ufacekit.qx.wrapper.demo.client.demobrowser.demo.data.TwitterStore { /* Define the native-qooxdoo class and load it when the class is loaded */ static { defineQxClass(); } private static native com.google.gwt.core.client.JavaScriptObject defineQxClass() /*-{ $wnd.qx.Class.define("qxwt.org.ufacekit.qx.wrapper.demo.client.demobrowser.demo.data.TwitterStore_Gen", { extend : $wnd.qx.data.store.Json, properties : { } , members: { _createRequest: function(url) { this._gwt.@org.ufacekit.qx.wrapper.demo.client.demobrowser.demo.data.TwitterStore::qxcb_createRequest(Ljava/lang/String;)(url); }} }); }-*/; /* Create an instance of the native class */ private static native com.google.gwt.core.client.JavaScriptObject createNativeObject() /*-{ var rv = new $wnd.qxwt.org.ufacekit.qx.wrapper.demo.client.demobrowser.demo.data.TwitterStore_Gen(); return rv; }-*/; public TwitterStore_Gen() { super(createNativeObject()); } }
I think this is once more a nice feature QxWT and GWT is bringing to you because it gives you the power to modify and extend framework classes quite easily without dealing with JavaScript but staying in your wellknown Java environment.
Though we now solved our uses cases from the last blog this blog series will continue because QxWT 1.0.0.2 is going to leverage the extensibility of the GWT-Compiler-Toolchain (GWT-Generator and Linker this time) even more by collecting meta data about the used QxWT-classes and feeding them into the qooxdoo-toolchain to optimize the qooxdoo framework code based upon your projects need. So stay tuned a give QxWT a try by checking out the dev-Demos provided and the source.
Pingback: QxWT explained Part 4 « Tomsondev Blog
Pingback: QxViewers – The first steps towards a UFaceKit-Webport « Tomsondev Blog