Simplifying XmlHttpRequest with JSON-RPC
In my day job, we decided to use a little XMLHttpRequest lovin' to populate one drop-down from another. This is my review of JSON-RPC, an open source JavaScript library and servlet for simplifying XMLHttpRequest. I considered integrate Direct Web Remoting (DWR) as well, but its java.net site was down the day I needed it. I started out with JSON-RPC 0.7, which caused some conflicts with Commons Validator client-side validation. This was fixed in the 0.8 release. JSON-RPC takes a little more setup than I care for, but it's pretty easy nonetheless:
- Download the 0.8 release from http://oss.metaparadigm.com/jsonrpc-dist/json-rpc-java-0.8.tar.gz.
- Add the JAR to your project and the webapps/jsonrpc/jsonrpc.js to your projects' "scripts" folder. Include this file in your SiteMesh decorator or Tiles layout. If you're not using SiteMesh or Tiles, it's high time you started.
- JSON-RPC currently requires that you register each class you want call
methods on. In our project, I registered a Spring bean (LookupHelper)
that's a singleton with references to Maps in the ServletContext. Then
we used JavaScript functions to call JSON-PRC and look up units for
a plant, and vice versa. I'm not going to put the LookupHelper class
here - you'll have to trust its methods return a single String
or a comma-separated list of Strings. To register this bean with JSON-RPC,
I created an HttpSessionListener and configured it in web.xml.
/**
* UserListener class used to add/remove session attributes when
* a user first logs in. Mainly for JavaScript Remote Scripting stuff.
*
* @author Matt Raible
*/
public class UserListener implements HttpSessionListener, HttpSessionAttributeListener {
private final Log log = LogFactory.getLog(UserListener.class);
public final static String BRIDGE_KEY = "JSONRPCBridge";
/**
* Initializes LookupHelper singleton with values needed for lookup
*
* @param event the HttpSessionEvent to grab session information from
*/
public void sessionCreated(HttpSessionEvent event) {
// Find the JSONRPCBridge for this session or create one
// if it doesn't exist. Note the bridge must be named BRIDGE_KEY
// in the HttpSession for the JSONRPCServlet to find it.
HttpSession session = event.getSession();
JSONRPCBridge jsonBridge = new JSONRPCBridge();
jsonBridge.setDebug(true);
session.setAttribute(BRIDGE_KEY, jsonBridge);
}
/**
* Destroys LookupHelper
*
* @param event the HttpSessionEvent to grab session information from
*/
public void sessionDestroyed(HttpSessionEvent event) {
if (event.getSession() != null) {
event.getSession().removeAttribute(BRIDGE_KEY);
}
}
public void attributeAdded(HttpSessionBindingEvent event) {
if (event.getName().equals(BRIDGE_KEY)) {
HttpSession session = event.getSession();
// register LookupHelper so we can call methods on it
ApplicationContext ctx =
WebApplicationContextUtils
.getWebApplicationContext(session.getServletContext());
// check for null so we don't have to initialize Spring in tests
if (ctx != null) {
log.debug("Registering lookupHelper for XmlHttpRequest...");
JSONRPCBridge jsonBridge =
(JSONRPCBridge) session.getAttribute(BRIDGE_KEY);
jsonBridge.registerObject("lookupHelper",
ctx.getBean("lookupHelper"));
}
}
}
public void attributeRemoved(HttpSessionBindingEvent event) {
// don't care
}
public void attributeReplaced(HttpSessionBindingEvent event) {
// same as attribute added
attributeAdded(event);
}
} - After this setup was complete, I was able to add the following JavaScript
to the bottom of my JSP. These are functions that our drop-downs call
to populate each other, and keep their options in synch.
<script type="text/javascript">
var jsonurl = "${ctx}/jsonrpc";
var jsonrpc = null;
var unitDropDown = document.getElementById("equipmentName");
function filterUnits(plantDropDown) {
var plantName = plantDropDown.options[plantDropDown.selectedIndex].value;
if (plantName == "") {
reloadUnits("");
return;
}
try {
jsonrpc = new JSONRpcClient(jsonurl);
} catch(e) {
alert(e);
}
// Call a Java method on the server
var units = jsonrpc.lookupHelper.getUnitsForPlant(plantName);
setUnits(units);
}
function reloadUnits(value) {
if (value == "") {
try {
jsonrpc = new JSONRpcClient(jsonurl);
} catch(e) {
alert(e);
}
// Call a Java method on the server
var units = jsonrpc.lookupHelper.getAllUnits();
setUnits(units);
}
}
function setUnits(units) {
var unitArray = units.split(",");
unitDropDown.options.length = 1; // keep "All" option
for (i=0; i < unitArray.length; i++) {
unitDropDown.options[unitDropDown.options.length] =
new Option(unitArray[i], unitArray[i]);
}
}
</script>
The hardest part of using JSON-RPC is setting it up. We only experienced minor issues with Commons Validator, but since the JSON-RPC 0.8 release - everything has worked great, on all browsers we need to support. The only thing I don't like about this library is that you have to register objects for each user's session. I briefly looked at DWR and it looks a little cleaner - especially b/c of its Spring integration. The next time we need XMLHttpRequest, we'll probably use DWR just to compare the two.
http://sourceforge.net/projects/japano
This XML-RPC is a very nice thing, especially when one has components that already know how to talk to the server: http://www.domapi.com/
However, the best way would be if the Web Frameworks by themselfs would have real support for XML-RPC (e.g. Tapestry, Spring, Struts with a plug-in, etc.)
This way, the programmer wouldn't even care about this, and only choose the right component or tag - just dreaming :).
Posted by Ahmed Mohombe on March 03, 2005 at 04:30 PM MST #
Posted by Thomas Sandor on March 03, 2005 at 05:14 PM MST #
Posted by Lars Fischer on March 03, 2005 at 09:24 PM MST #
A very cool implementation of this standard is on Google's "Suggest" site, as you type, a drop-down of suggestions are made for you (pulled r/t from the google server via XMLHttpRequest)
http://www.google.com/webhp?complete=1&hl=en
Further info on the tech implementation is available on the "Learn More" link on the site:
http://labs.google.com//suggestfaq.html
Love it myself.
Posted by David Thompson on March 03, 2005 at 09:42 PM MST #
(Sorry for the double post)
Another good blog article concerning XMLHttpRequest from "The Man in Blue":
<a href"http://www.themaninblue.com/writing/perspective/2005/03/02/">http://www.themaninblue.com/writing/perspective/2005/03/02/
Posted by David Thompson on March 03, 2005 at 09:50 PM MST #
Posted by Jim Cook on March 04, 2005 at 02:05 PM MST #
> The only thing I don't like about this library is that you have to register
> objects for each user's session.
Matt. It looks like this library can do what you want.
From http://oss.metaparadigm.com/jsonrpc/manual.html :
Global bridge
There is a global bridge singleton object that allows exporting objects to all HTTP clients. This can be used for registering factory classes although care must be taken with authentication and security issues as these objects will be accessible to all clients. It can be fetched with JSONRPCBridge.getGlobalBridge().
To export all instance methods of an object to all clients:
To export all static methods of a class to all clients:
Posted by Igor E. Poteryaev on March 23, 2005 at 02:58 PM MST #
Posted by Raziel on April 18, 2008 at 03:11 PM MDT #