Saturday, January 26, 2008

A weekend with GWT, JavaScript & client-side xml processing using XSLT

I wanted a simple, effective, technologically advanced and appealing online test (exam) system for the trainings that I occasionally provide. I had two intentions while developing this - one, to test participants' knowledge in the topics on that I provide training - second, this weekend project itself is a demonstration of 'Assembly Line Software Development' - a post lean method - that I devised to make software development more cleaner and faster.

I donated this prototype to TME, Capgemini France - where I work as a Telco Architect in the week days. It will undergo a massive makeup and dressing room session to have more commercial features in order to make it usable for TME certification exam.

I used GWT (Google Web Toolkit) - some standard and some advanced features - and some javascript. GWT is fun to use with one disappointing thing - it does not yet support java generics and other Java 5/+ features at the client side. That means it does not have a compiler to translate java 5 specific code to javascript. For the folks not yet exposed to GWT - GWT is a sophisticated Java to JavaScript compiler with some added server side utility-ish features.

Let's just see where GWT is useful: GWT allows Java developers to write browser based application that comprises components (GUI stuffs), contents (text, image, xml etc.), events and content manipulation. A Java developer writes this in Java, allowing him/her to develop browser based application in a true object oriented way (more accurately in java way). GWT compiler translates the 'client-side' java code into cross-browser JavaScript code. It also has some server side utility-ish features - naturally that doesn't need to be translated into JavaScript.

Some quick start references 1) GWT official getting started guide 2) A good article on oracle site

The GWT features used in the the project are given below. I will write about them from three aspects - one - why I have used it, two - a brief note about the feature with reference and three - a note on how I have used it to gain some special benefit/effect.

1) RemoteServiceServlet:
Why I have used it: to serve the question set. The question set is in XML. Initially I thought to put it in the public html (equivalent) directory and thought to process them at the client side. I dropped the idea in order to avoid users' sniffing into the the questions as the questions have answers inbuilt to it for instant and easy evaluation. So... I have used this feature to serve question set from server side.

Note on the feature: At the the server side there is a service implementation that extends the RemoteServiceServlet and implements the service interface. A good place to have a look into - how to use RemoteServiceServlet is Remote Procedure Call. The catch here is to write an asynchronous interface corresponding to the service interface. Look at the above link content to understand more.

How I have used it in online exam module: The service implementation - the extension of RemoteServiceServlet - used to construct questions from XML. A question is a serialized object constructed from the said XML fragment and finally the questions - the serialized objects - put into a list and sent across to the client.

A special note: At the server side code you can freely use Java 5/6 features depending on the compiler version you are using that is not possible at the client side code. GWT 1.4.61 - the latest GWT distro - doesn't support Java 5/+ style of coding at the client side. I love using Java 5/+ features very much; specially generics.

The below picture is taken from GWT official page.


You can very well enjoy using Java 5/6 features to write server side components shown at the right hand side of the picture. I am waiting to see Java 5 related features in GWT soon.

At the client side - you instantiate YourServiceAsync is special way to get the service instance.




YourServiceAsync yourService = (YourServiceAsync) GWT
.create( YourService.class );
ServiceDefTarget endpoint = (ServiceDefTarget) yourService;
String moduleRelativeURL = GWT.getModuleBaseURL()
+ "servletalias";
endpoint.setServiceEntryPoint( moduleRelativeURL );

Now your service instance 'yourService' is ready and the operations in it are ready to be called. 'servletalias' points the implementation of the Service extending RemoteServiceServlet and is specified in .gwt.xml. Refer Remote Procedure Call for more clarity.

Architectural consideration: Simply pushing most of the processing to the client side, saving the server from crushing large data and perform more operations. There are only two calls between client and server - one - to get the set of questions and another - sending the report back to the server - not for processing but to store for future reference. For a particular exam XML (containing questions) processing is required only once at the server side after that its only functionality would be just to send the set of questions in the form of a list to the client. Efficient and simplistic - is not it?

2) UI features:
This is the most used and most useful set of features in GWT.
Why I have used it: To save myself writing JavaScriot codes and making them compatible with different browser families. To be more precious - GWT has been chosen for that matter. Like many others, I am comfortable in writing Java codes, moreover it is easy to detect development time errors and correct them. Debugging is also easier compare to JavaScript debugging.
Note on the feature: The UI components are similar to Swing UI components. It is the essence of GWT but very easy to use. Refer the GWT getting started article on GWT official web page. I am not going to talk much on this as it is standard and available in detail on the official website.
How I have used it: In standard way. haha...

Special Note: I had tough time dealing with RadioButton. It made me to bang my head several times. What was the problem? The problem was a bit complicated; the behavior of radio button is just as it should be; the problem originated from my special expectation from RadioButton behavior. I used check box for the question that has multiple right answers to show the options and radio buttons for single right answer. Each selection event makes a particular flag 'true' and deselection event 'false'. In check box there are clear selection and de-selection events upon onClick() - so no problem with check box. In case of radio button - there is no event on de-selection as onClick() only can check a radio button but can not uncheck - and this behavior is required. I, then, tried to use onFocus() and onLostFocus(), it seemed to work well but did not; due to the fact when you navigate away from the group where the radio buttons are placed - it fires the onLostFocus() event that is not desirable. Finally I solved this problem with some other way. So be careful when you are frequently updating any global value based on the events captured from radio button. ;)

3) JavaScript Native Interface (JSNI): Very useful, wonderful feature.
Why I have selected this: I wanted to process XML with the XSLT at the browser end. GWT does not have XSLT processor API so I had to write a Java Method in JavaScript. Intersting... right!!! Both were new and exciting for me. Please have a look at the code I have written for your quick reference.


package com.objectengineering.onlinetest.client;
...
.....

public class OnlineTest {
...
.....
private String xmlString = "";
...

int generateReport() {
...
.....
xmlString = doc.toString();
xmlString(); // the native method is called the way a normal
//java method is called
.....
...
}
public native void xmlString() /*-{
var xmlDoc =
this.@com.objectengineering.onlinetest.client.OnlineTest::xmlString;
//the global class variable xmlString is called in this way
var win = $wnd.open("","","");
win.document.open("text/xml");

// code for IE
if (window.ActiveXObject)
{
var doc=new ActiveXObject("Microsoft.XMLDOM");
doc.async="false";
doc.loadXML(xmlDoc);
xslDoc=new ActiveXObject("Microsoft.XMLDOM");
xslDoc.async=false;
xslDoc.load("test.xsl");
var outDoc = doc.transformNode(xslDoc);
win.document.write(outDoc);
}
// code for Mozilla, Firefox, Opera, etc.
else
{
var parser=new DOMParser();
var doc=parser.parseFromString(xmlDoc,"text/xml");
var xsldoc = document.implementation.createDocument("", "", null);
xsldoc.async = false;
xsldoc.load("test.xsl");
var xsltProcessor = new XSLTProcessor();
xsltProcessor.importStylesheet(xsldoc);
var xmlObject = xsltProcessor.transformToFragment(doc, document);
// var xmlObject = xsltProcessor.transformToDocument(doc);
var htmlDoc = new XMLSerializer().serializeToString(xmlObject);
win.document.documentElement.appendChild(xmlObject);
win.stop(); //required to solve a mozilla javascript bug
}
// return htmlDoc;
}-*/;
}

The native operation's code should be enclosed within the block comment.
That means if there is a method written in Java script - it should look
like
access_modifier native return_type  method_name( argunments ) /*-{
//your java script code goes here
}-*/;
If the method is
'aMthoed()' and if the return type is void and no argument then it should look like
public native void aMethod () /*-{
//your java script code goes here
}-*/

2 comments:

Chiradip Mandal said...

going to update this with GWT 1.5 soon

inptisto said...

great work,

I'm waiting for the update with gwt 1.5

thanks for sharing :)