Jun 11 2008

Gwt-Ext 2.0.4 is out

Tag: Ext, Gwt, Java, Programming, WebAbhijeet Maharana @ 10:37 pm

Gwt-Ext 2.0.4 was released today. Mario announced the release at http://gwt-ext.com/forum/viewtopic.php?f=12&t=1622. I quote from the release notes.

Features

  • Support for GWT 1.5 RC1
  • FF3 Support
  • PageBus
    Messaging support which allows loosely coupled components to communicate with each other via traditional pubhish / subscribe paradigm. See demo in Showcase under ‘Miscellaneous -> Publish Subscribe’

  • Eclipse Project Files
    You can now use GWTExt directly from SVN as an Eclipse project. You just need to add the GWT_HOME variable to point to the GWT Directory in Eclipse
    Windows|Preferences|Java|Build Path|Classpath Variables

Changes

  • Various Bug Fixes
  • Fixed Memory leak when add and remove Panels (Mix of GWT and GWT-Ext)

GwtExtUx Changes

  • FileTreePanel
  • SwfUploadPanel
  • CustomReader
  • Removed individual UX from GwtExtUx.gwt.xml. This means whenever a UX is needed, They have to be added individually in the users gwt.xml file.
  • Latest patch in Multiselect.js including memory leak fixes, drag back, etc…

Download the latest version from http://gwt-ext.com/download/. Gwt-Ext-UX is being subjected to some last minute tests and a new version should be out in a couple of days.

If you have just started, the forums are always buzzing with activity and you are most welcome. These beginner screencasts may also be helpful.


Jun 04 2008

Gwt-Ext Custom Reader

Tag: Ext, Gwt, Java, Javascript, Programming, WebAbhijeet Maharana @ 11:36 pm

Screenshot

If I had to populate a Gwt-Ext store with data in arbitrary format,

  1. I could parse data on the client and use a MemoryProxy + ArrayReader or

  2. I could have the server do the parsing and send JSON data to the client which could then be handled by a JsonReader for populating the store.

This thread at Gwt-Ext.com made me wonder if a good third option would be to have a reader that can be used directly with the store and proxy mechanism and that allows one to write parsing logic in Java on the client. I have attempted a CustomReader that does just that. You can use it with a proxy that fetches remote data.

CustomReader registers an Ext user extension of the same name. Ill refer to them as JavaCustomReader and JSCustomReader to avoid confusion. JavaCustomReader has a static method for registering the user extension.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
...
static {
	init();
}
...
private static native void init()/*-{
	$wnd.Ext.namespace("GwtExt");
 
        $wnd.GwtExt.CustomReader = function(meta, recordType, readerInstance) {
	      	meta = meta || {};
	       	this.readerInstance = readerInstance;
	        $wnd.GwtExt.CustomReader.superclass.constructor.call(this);
	};
 
	$wnd.Ext.extend($wnd.GwtExt.CustomReader, $wnd.Ext.data.DataReader, {
	      	read : function(response){
	       		return this.readerInstance.@com.maharana.gwtextcustomreader.client.CustomReader::read(Lcom/google/gwt/core/client/JavaScriptObject;)(response);
	        	},
 
        	readRecords : function(o){
			alert('Custom reader does not work with local data.');
        	}
        });
}-*/;

JSCustomReader is passed one additional parameter when it is instantiated. This parameter is a reference to the JavaCustomReader that created it.

1
2
3
protected native JavaScriptObject create(JavaScriptObject config, JavaScriptObject recordDef)/*-{
	return new $wnd.GwtExt.CustomReader(config, recordDef, this);
}-*/;

JSCustomReader’s read() method is called by a proxy which has finished loading remote data. The proxy expects a return object with the success status, total record count and actual records as its fields. JSCustomReader invokes JavaCustomReader.read() which looks like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public JavaScriptObject read(JavaScriptObject response) {
	ReaderResult r = handleRemoteResponse(new RemoteResponse(response));
	JavaScriptObject o = JavaScriptObjectHelper.createObject();
 
	JavaScriptObjectHelper.setAttribute(o, "success", r.success);
 
	JavaScriptObject[] jsRecords = new JavaScriptObject[r.records.length];
	for(int i =0; i<r.records.length;i++)
		jsRecords[i] = r.records[i].getJsObj();
 
	JavaScriptObjectHelper.setAttribute(o, "records", JavaScriptObjectHelper.convertToJavaScriptArray(jsRecords));
	JavaScriptObjectHelper.setAttribute(o, "totalRecords", r.records.length);
 
	return o;
}
 
// subclass and put your parsing logic here
public abstract ReaderResult handleRemoteResponse(RemoteResponse remoteResponse);

In turn, it handles the to/from JavaScriptObject stuff and calls handleRemoteResponse(). Users just need to provide their parsing code in this method in derived classes. RemoteResponse and ReaderResult are convenience classes.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class RemoteResponse {
	JavaScriptObject response;
 
	public RemoteResponse(JavaScriptObject response) {
		this.response = response;
	}
	public String getResponseText() {
		return JavaScriptObjectHelper.getAttribute(response, "responseText");
	}
	public String getResponseXML() {
		return JavaScriptObjectHelper.getAttribute(response, "responseXML");
	}
}
 
class ReaderResult {
	public final boolean success;
	public final Record[] records;
 
	public ReaderResult(boolean success, Record[] records) {
		this.success = success;
		this.records = records;
	}
}

This completes CustomReader. As an example, I made a simple CSV parsing reader as mentioned in that thread at Gwt-Ext.com. This reader gets stock quotes from Yahoo finance as a CSV file and creates records out of them.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
RecordDef recordDef = new RecordDef(  
	new FieldDef[]{  
        	new StringFieldDef("name"),
                new StringFieldDef("lasttrdate"),
                new StringFieldDef("lasttrval")
        }
); 
 
CustomReader c = new CustomReader(recordDef,1){
       	 public ReaderResult handleRemoteResponse(RemoteResponse remoteResponse) {
 
       		String csvalue = remoteResponse.getResponseText();
 
       		String[] lines = csvalue.split("\n");
       		Record[] records = new Record[lines.length];
 
       		for(int i=0; i<lines.length; ++i)
       		{
       			String[] columns = lines[i]. replace('\"', ' ').split(",");
       			records[i] = this.recordDef.createRecord(columns);
       		}
 
       		return new ReaderResult(true, records);
       	}
};

For getting the file from Yahoo, I made a utility Servlet Redirect.java which fetches the contents of a url and passes it to the client.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
	String queryString = request.getQueryString();
	String address = queryString.split("url=")[1];
	URL url = new URL(address);
 
	PrintStream out = new PrintStream(response.getOutputStream());
	String inputLine = "";
	BufferedReader br = new BufferedReader(new InputStreamReader(url.openStream()));
	while((inputLine = br.readLine()) != null)
	{
		// System.out.println(inputLine);
		out.println(inputLine);
	}
 
	out.close();
}

It is invoked by an HttpProxy on the client as follows:

1
2
3
4
5
6
...
String url = "http://download.finance.yahoo.com/d/quotes.csv?s=INFY+XOM+BBDb.TO+JNJ+MSFT+GOOG+YHOO&f=nd1l1";
DataProxy proxy = new HttpProxy("redirect?url=" + url);
 
Store store = new Store(proxy, c);	// CustomReader c from above
...

Once we have the utility servlet and CustomReader as part of the project, I think it will be relatively easier to populate stores with any arbitrary format data. Feel free to drop your comments / suggestions. I would love to hear them.

Download Eclipse project from Gwt-Ext.com or Rapidshare

Reference: GwtProxy by michal.bergmann