The most appealing thing about GWT for me isn't the fact that I, as a Java developer, don't have to write JavaScript anymore (hey, I like JavaScript!), but the single development experience. Since everything is in Java, you can have common libraries, coding patterns, frameworks, and editor behavior, drastically simplifying the development experience. My second favorite aspect of GWT is its component library. For far too long, we Java developers have treated HTML as just text to output, which is like treating programming as a series of ones and zeros - surely we can work at a higher level than that.
Therefore, I set out to explore what it would be like to have a GWT-like development experience for JavaScript developers, only instead of Java everywhere, you write JavaScript. Also, I wanted the bulk of the UI to be built on the server-side to minimize XHR calls and improve accessibility. To implement the server-side component library, I ripped of as much of Apache Wicket as I could, while keeping it 100% stateless (no session usage or complicated state sharing schemes). I'm not sure how to package this yet (new framework, Struts 2 plugin, require OSGi, or whatever), but before I go too far down the road of making it a proper framework, I thought I'd get some feedback on the basic design.
This is what the code of a simple application looks like:
/controllers/comment.js
/templates/comment/comment.html
comment.html
The comment.html template is a simple HTML page with "jsid" attribute markers to determine where the components are rendered. Absolutely no logic is allowed in a template:
<html>
<head>
// import jquery and json
</head>
<body>
<form jsid="commentForm" action="#">
<input jsid="title" type="text">
<input jsid="body" type="text">
<input jsid="submit" value="Submit" type="submit">
</form>
<script type="text/javascript" jsid="proxies"></script>
<body>
</html>
comment.js
Where it gets interesting is in comments.js. This follows the basic design of Ruby on Rails controllers with some interesting twists:
page({
comment : function(params) {
this.commentForm = new Form();
this.commentForm.add(new TextField("title"));
var body = new TextField("body", {
value : "Great feature",
label : "Body",
description : "The body of the comment"
});
this.commentForm.add(body);
this.commentForm.add(new Submit("submit"));
this.commentForm.onsubmit = function() {
alert('calling comment');
saveComment({
title : jQuery('#title').val(),
body : jQuery('#body').val()
}, function() {
alert("Comment saved");
});
return false;
}.toString();
this.proxies = new Proxies(['saveComment']);
},
saveComment : function(comment) {
print("comment title: " + comment.title + " body: " + comment.body);
this.result = "success";
}
});
Ok, starting from the top. You pass into the "page" function a map of methods to be used as controller methods, although there is a mix of HTML and JSON-producing methods: "comment" returns a comment page, while "saveComment" is a JSON-invokable method.
comment()Since this is the method that will render HTML, we will build up our component tree. Instance variables like 'commentForm' are the first to be matched against comment.html, with subcomponents matching accordingly as the template processor works its way through the HTML. There are four components used in this page:
- "Form" - represents an HTML form
- "TextField" - represents an HTML text input field
- "Submit" - represents a submit button
- "Proxies" - generates client-side proxy methods that call the corresponding server-side ones
The main advantage of building your components in server-side code and not in a template is the much greater options you have for presentation logic. In this example, I just use hard-coded strings, but you can see how these could just as easily be initialized using service calls.
Nothing eye-opening so far...until you get down to the onsubmit() method. What we are doing is creating a method that will be executed on the client-side in our server-side code, using the nifty toString() feature on functions to get the source. We could have just as easily wrote it in a big string, but this way we get better editor support and fail-fast parser failure reporting. This also serves to help "blur" the line between server and client-side code ala GWT.
Finally, we create the "Proxies" component, passing the server-side function names that we want to be able call from the client. My implementation uses JQuery and JSON to stringify objects, but those details are irrelevant here. The point is we are able to call "saveComment()" on the client-side as if it was on the server-side, with all the json parsing and construction happening behind the scenes. Since both sides are JavaScript, you don't have the immedience mismatch you do in other languages.
saveCommentThe "saveComment()" function is a remotely executable function, meant to receive and return JSON, although you aren't limited to it. JSON in the request body is generally a good idea since it limits your vulnerability to cross-domain request forgery. Of course, this technique also means your form only works with JavaScript turned on, which may not be an option. The "comment" argument is the serialized object passed from the client, and we return a serialized instance of our controller, which is why we set the result by setting the instance variable.
This basically completes the tutorial. One other thing to note is the components are also written in JavaScript, and since that means Rhino, you get all the E4X niceities in constructing HTML. This means even the HTML is syntaxally processed when the script is loaded.
Next steps
My next step is to build a real application using this little framework and see how it scales as the feature demands pile on. Thanks to Rhino, I can keep developing the backend of this application in Java with all the libraries and frameworks I depend on. The consistent use of JavaScript for the presentation layer hopefully means less code, more reuse, and more productivity.