 |
RJS API For Java
Posted by jhook on October 12, 2006 at 12:47 PM | Comments (19)
If you take a look at GWT (Google Web Toolkit), you are dealing with a subset Java API to accomodate JavaScript execution on the client. While this does tend to push more execution on the client, it does limit your ability to coordinate traditional transactions and APIs that you are used to with the full JEE stack. What if you were to flip the tables on GWT and use server-side Java to generate JavaScript to execute on the client?
With this approach, an AJAX request is made to the server, and instead of responding with data, you respond back with JavaScript code to execute which manipulate the screen or refresh different parts of the screen. JavaScript generation is supplemented by 'native' APIs to Java.
While I'm sure there were others doing this long before, the Rails community has made the approach quite popular with RJS. You can read a bit on Rails solution here.
As part of our JavaOne demo last year, I included an example of this with JSF, but the API looked like:
ClientWriter writer = ...;
String errorMsg = "This is an Error to push to the client.";
writer.select('myDiv', Element.hide());
writer.select('errorMsg', Element.setHtml(errorMsg), Element.show(), Element.highlight());
This would generate the following JavaScript in the response, which would use JavaScript libraries such as Prototype or Scriptaculous.
<script type="text/javascript">
Element.hide($('myDiv'));
$('errorMsg').innerHTML = 'This is an Error to push ....';
Element.show($('errorMsg'));
Effects.highlight($('errorMsg'));
</script>
This JavaScript would then be evaluated on the client when the AJAX response was received, just like any other local JavaScript function, except we've generated the logic on the fly, using the full JEE stack on the server.
While we can identify elements in the browser DOM by ID, JSF has a (complex) clientId system for identifying elements in the DOM. We can simplify this by adding methods to the ClientWriter which just take in a UIComponent as your selection criteria:
<h:inputText value="#{bean.name}" binding="#{bean.nameInput}"/>
<h:commandButton action="#{bean.action}"/>
// MyBean.java
private UIInput nameInput;
public void setNameInput(UIInput input) {
this.nameInput = input;
}
public String action() {
....
if (loginFailed) {
ClientWriter writer = ....;
writer.select(this.nameInput, Effect.highlight());
}
}
You can see a live prototype of this in the context of constructing a cart and updating various parts of the page here.
Some of the logical pros of this approach to server-side generation of JavaScript with AJAX are:
- Keep data models on the server
- Prevents unecessary data marshalling
- Minimizes JavaScript development required on the client (instead of receiving XML/data, interpreting, and then updating the DOM)
- Full JEE stack available, such as working within (Hibernate/EJB3) transactions
- Can implement on top of familiar, existing frameworks such as Struts, WebWork, or JSF
So what does the API look like in implementation?
// ClientWriter.java
public ClientWriter select(String id, Script... s) throws IOException {
if (s == null) return this;
String var = this.getBuffer().append("${'").append(id).append("')").toString();
for (Script i : s) {
i.write(var, this);
}
return this;
}
// ClientWriter.java (for CSS-like selectors from Prototype.js)
public ClientWriter select(String selector, Script... s) throws IOException {
if (s == null || selector == null) return this;
this.writer.write(this.getBuffer()
.append("$$('")
.append(selector)
.append("').each(function(e){").toString());
for (Script i : s) {
i.write("e", this);
}
this.writer.write("});");
return this;
}
// Script.java
public interface Script {
public void write(String variable, ClientWriter writer) throws IOException;
}
// Element.java
private final static Script Show = new Script() {
public void write(String variable, ClientWriter writer)
throws IOException {
writer.write(variable);
writer.write(".style.display='';");
}
};
private final static Script Hide = new Script() {
public void write(String variable, ClientWriter writer)
throws IOException {
writer.write(variable);
writer.write(".style.display='none';");
}
};
public static Script toggle(boolean show) {
return show ? Show : Hide;
}
I'd be interested if others have ideas on a better method of handling/coordinating generation of JavaScript code in a Java API? As you add more JavaScript libraries, you can add additional Script singletons to generate the necessary code-- I guess similar to what would be required to code custom GWT components with their method of handling 'native' calls.
Bookmark blog post: del.icio.us Digg DZone Furl Reddit
Comments
Comments are listed in date ascending order (oldest first) | Post Comment
-
Another interesting approach is XML11, here's a
talk about it on google video,
really worth watching for the cracktastic stuff he does about 25 mins in. Javascript generation is most of the second half of the talk, from 37m25s on: Its quite similar in ways to GWT, but without the special compiler. I still can't imagine using this in production, but I'm impressed. (Aside: the bookmarks seem to jump to the timecode immediately on my mac, but have to buffer on windows (both ff2). Anyone know why?)
Posted by: bazzargh on October 12, 2006 at 03:27 PM
-
Related to Javascript templating, YUI community star Jack Slocum has posted a great pure Javascript solution.
http://www.jackslocum.com/yui/2006/10/06/domhelper-create-elements-using-dom-html-fragments-or-templates/
Posted by: ilazarte on October 13, 2006 at 05:19 AM
-
The XML11 approach is not what I'm shooting for-- it's all about where the code gets executed.
The YUI stuff is very cool, this is exactly what I think would be cool to provide Java API wrappers for-- such that you can easily generate the JavaScript for a JS Grid with Drag'n'Drop from with a JSP tag (jMaki) or do so from an AJAX call to basically take in a collection of Beans and generate the JS Grid on the fly from an EL evaluation or whatever.
Posted by: jhook on October 13, 2006 at 10:51 AM
-
Amen to the Jmaki integration. I need to check up on that, haven't looked at that project in a while.
Posted by: ilazarte on October 13, 2006 at 01:53 PM
-
I would like to see this feature into DynaFaces, but rewrited so that presentation logic can be customized in the JSP or facelets page:
DynaFaces.ajaxTransaction(...., render:"name.highlight('red')");
Probably would be smart if you let us decide what to re-render in the case of a validation failure or not:
DynaFaces.ajaxTransaction(...., renderIfNotValid:"name.highlight('red')");
I don't know if you are still developing DynaFaces...but what do you think?
Posted by: agoria on October 14, 2006 at 06:04 AM
-
I second the request to marshal this feature into DynaFaces.
Right now, DynaFaces can only re-render a component on the page. I've messed around with creating a custom component that renders a JSON array to be used in, for example, a Jack Slocum YUI-ext grid; then using a "postReplace" javascript function to update the grid after the updated JSON comes back, but that feels kludgy. I would prefer something like this, where DynaFaces could optionally return Javascript to be executed on the client. Maybe a "type" parameter in the .ajaxTransaction |OPTIONS| array, allowing values such as htmlUpdate, jsExecute, or jsObject. (htmlUpdate being the default)
(side point) Selecting jsObject would require a MethodName parameter and would basically enable "DynaFaces remoting," which I don't think is redundant with Shale remoting, since DynaFaces has the advantage of staying within the JSF lifecycle. (i.e. a straight Shale remoting request can't incorporate ValueChangeEvents and such before rendering)
Posted by: jeff_bond on October 16, 2006 at 08:31 AM
-
well, in the original demo, there was the ability to wire custom event handlers following the convention 'onXXXX', so an autosuggest would send an AJAX request to the server with the clientId, and the event type of 'suggest', this would be wired to the component on the server, invoking, onSuggest. I'm not sure if this made it into Ed Burn's rendition of Dynafaces, but that's how the autosuggest works in the public demo from JavaOne
Posted by: jhook on October 16, 2006 at 06:35 PM
-
There is an ability to specify a MethodName, and then invoke that on a given component. DynaFaces doesn't respond with the actual result of that method, though; it simply processes the method, then re-renders whatever components you've requested updates for in the DynaFaces request.
I do think, for 95% of your AJAX needs, this is the way to go: just re-render components, hiding the complexity of the xml response and client handling thereof. Once in a while, though, you'd like to drop in a gizmo from your favorite JS library; and that's where it'd be nice if DynaFaces was a little more flexible in allowing you to return straight executable JS code or JSON objects like you can with Shale remoting, and then (obviously) process it on the client in some way other than a straight markup-replacement. I guess JMaki people must be doing this somehow ... will have to read up there.
I did notice, debugging a maddening problem in Venkman last night (turns out Trinidad debug-output="true" breaks DynaFaces; it inserts comments into component markup, which mess up the rather fragile XML object in the client), that the DynaFaces JS library seems to have the capability to execute straight JS if that's what the response looks like. Don't know the particulars and don't think the server-side piece is set up for rendering straight JS yet, but I can see somebody's baked that in.
Posted by: jeff_bond on October 17, 2006 at 08:12 AM
-
huh, see, in the demo/prototype of Avatar, you always had a 'body' response-- which could be populated by the event handler on the component. So your response would have a body, then in addition, any re-renders that were queued. This is how the above linked demo works where you are refreshing stuff, but at the same time, I had used Scriptaculous to write JavaScript effects in the 'body' of the response.
Posted by: jhook on October 17, 2006 at 08:40 AM
-
btw, have you tried, within that method, to get the ResponseWriter and write to it?
Posted by: jhook on October 17, 2006 at 08:54 AM
-
you know, I think I'm wrong. I see in the DynaFaces docs about an xjson object being passed to the server, and then back to client, and a postInstallHook JS function. Not sure how I access the xjson object server-side, but I presume it's possible. I'll play around with it some.
Posted by: jeff_bond on October 17, 2006 at 10:20 AM
-
Jacob, whether you need a "full J2EE" stack or "transactions" is immaterial. For any given application, there's an equilibrium or ideal balance between code which lives on the client and code on the server.
Your approach isn't Turing complete; even if a piece of code belongs on the client side, if your framework doesn't support the desired functionality, the code must run on the server.
GWT on the other hands lets you code client code in Java in a fashion limited only by the client's capabilities. You can put as much logic as makes sense on the client and server and attain the aforementioned equilibrium.
Also, with GWT, you don't need a heavyweight framework like JSF on the server side. You have services on the server side, and you have type safety end to end.
I know you've invested a lot in AJAX/JSF, but you shouldn't let that cloud your judgement.
Finally, take serious note of the security implications of your approach. Among other hacks, someone can trick your users into visiting a web page which includes your Javascript and steals your user's information.
Posted by: crazybob on October 17, 2006 at 11:45 AM
-
Hey Bob, I probably could have written this post sans any mention of JSF, but my points are guided at the necessity of DTOs given the way people write applications today with the JEE stack. While in some cases, it is more efficient to handle pure UI facilities on the client, you also find common UI use cases must call the server (working with large datasets). Now either you respond with DTOs, or you respond with pure UI-related modifications. I'm questioning whether we can get by without DTOs if we do basically execute GWT-ish code on the server and respond back with only the output from the execution.
With this approach, it seems much more secure than exposing business models to the client and falsely assuming the validity of your business logic as executing on the client system.
Posted by: jhook on October 17, 2006 at 12:05 PM
-
You either have to write DTOs, or code which marshals your Java objects to JSON (which won't be typesafe), or run your code on the server. That's fine if you want to run your code on the server and keep your AJAX code minimal. People have been doing so for years, but it isn't as elegant, it won't be as snappy for your users, and it won't scale as well.
Security is orthogonal. You always have to validate user input. With JSF, you do so in your beans. With GWT, you validate in your services just as you would any other user facing web service.
Posted by: crazybob on October 17, 2006 at 12:24 PM
-
I said security is orthogonal, but I'm referring to server side validation. There are still client side (i.e. XSS) security risks inherent in your approach.
Posted by: crazybob on October 17, 2006 at 12:30 PM
-
WIth JSF, validation is declarative and externally facilitated on both the client and server as the request/response processor. So no one needs to 'code' for each UI case in validation, while retaining input validity on the server. This type of validation isn't externalizable (from my knowledge) in the RPC solutions presented and would require coding of validation for the parameters passed.
By no means are we talking black or white with the best possible solution. You can still have very rich interactions on the client, facilitated by JS/DHTML manipulations if the code is powered by GWT or otherwise. In the latter case, even something like JSF can facilitate mediating and marshalling changes in a lazy fashion, just as GWT would with its RPC implementation. The difference here though is that JSF/others wouldn't require DTO development where GWT would require a layer of Java development.
Posted by: jhook on October 17, 2006 at 12:51 PM
-
Interesting stuff,
Jacob, is it possible to get the UISuggestHandler/UISuggest/the rest of the classes source codes of the JavaOne public presentation? I'd like to see how it all constructed together :)
thanks.
Posted by: trouby on October 27, 2006 at 05:52 AM
-
Could you open sources of your beautiful J1 demo - ?
Posted by: shamoh on May 01, 2007 at 10:02 AM
-
Could you open sources of your beautiful J1 demo -
http://sunapp1.whardy.com:8090/jsf-j12/home.jsf?
Posted by: shamoh on May 01, 2007 at 10:04 AM
|