Saturday September 29, 2007
Communicating Comboboxes with jMaki, Part 2
For my previous blog entry, I explained how to create a jMaki application that causes a user action on one combobox widget to change the values displayed in another combobox widget. My example allowed a user to select a state in one combobox in order to make the other combobox load the names of a set of cities contained in the chosen state.
In this blog entry, I'll show how you can make this application a little more practical by adding a map widget so that when the user selects a city from the second combobox, the application will plot that city on the map, as this screen shot shows:
So, here's what you do:
publish property to the second combobox, and give it the topic
/cities, as shown here:
<a:widget
name="dojo.combobox"
publish="/cities"
subscribe="/cb"
value="${StateBean.cities}" />
Now, when the user selects a city from this combobox, the selected value is published to the /cities topic.
glue.js file:
jmaki.subscribe("/cities/onSelect", function(item) {
var location = item.value;
var encodedLocation = encodeURIComponent("location=" + location);
// jmaki.xhp is provided as part of jmaki and maps to the XMLHttpProxy
var url = jmaki.xhp + "?id=yahoogeocoder&urlparams=" + encodedLocation;
jmaki.doAjax({url: url, callback : function(req) {
if (req.responseText.length > 0) {
// convert the response to an object
var response = eval("(" + req.responseText + ")");
var coordinates = response.coordinates;
v = {results:coordinates};
jmaki.publish("/jmaki/plotmap", coordinates);
} else {
jmaki.log("Failed to get coordinates for " + location );
}
}
});
});
This function subscribes to the /cities/onSelect topic. In the previous step, you made it so the second combobox widget publishes its value to the /cities topic. Recall from the previous blog entry that a combobox widget always publishes its selected value to the global onSelect topic and to the onSelect sub-topic of a developer-defined parent topic. In this case, you've specified that this combobox widget publish its value to the parent topic, /cities, and therefore, the value is published to /cities/onSelect.
The subscribe function gets the selected city name from the item variable that is passed to it. The function takes the city name, encodes it, and uses Ajax to pass it to the Yahoo geocoder service by way of the XMLHttpProxy client. The Yahoo geocoder service returns the coordinates of the location to the XMLHttpProxy client, which returns it to the subscribe function.
When the subscribe function receives the coordinates, it publishes them to the /jmaki/plotMap topic, which is a standard jMaki topic to which all the jMaki map widgets subscribe. Now, the map widget you added to your application displays the city the user selects from the cities combobox.
subscribe function will need to get this widget's value:
Notice that I've given this widget the ID,<a:widget id="thisState" name="dojo.combobox" publish="/cb/getState" value="${StateBean.states}" />
thisState
- Now, you need to add a couple lines to the
subscribe function that get the selected state name and add it to the city name:
jmaki.subscribe("/cities/onSelect", function(item) {
var city = item.value;
var state = jmaki.attributes.get('thisState').getValue();
var location = city + ", " + state;
// the rest of the function stays the same
...
});
First I added a line that gets the value of the widget called thisState. After that, I added another line that creates a location specifying both the city name and the state name so that the Yahoo geocoder service knows exactly which city I want.
Posted at 10:44PM Sep 29, 2007 by jenniferb in Sun | Comments[3]
Wednesday September 26, 2007
Simple Communicating Comboboxes in a jMaki Application
Note: I have updated this blog in response to Greg's comment.
It's been a long time since I've blogged because I was on maternity leave. Now that I'm back and have brushed the cobwebs off my blog, I'm trying to get into the swing of things again, including working with jMaki.
Because it's been so long, I decided to start with something simple. I heard from Greg Murray that, with the new features available with jMaki 1.0, you can now easily get a user action on one combobox widget to change the set of data in another combobox widget.
You've probably seen this use case quite a bit in forms on the web, such as when you need to enter your address. The form asks you to select your state from a list of states in a combobox. When you select a state, another combobox is updated with the list of cities located in that state, as shown in this screenshot:
So, I decided this would be a nice, simple example to help me get to know what's new with jMaki. Here are the steps I took:
useBean tag:
<jsp:useBean id="StateBean" scope="session" class="dualCombobox.StateBean" />
Later on in this blog, I'll show you what to put in StateBean.java.
<a:widget name="dojo.combobox"
publish="/cb/getState"
value="${StateBean.states}" />
This combobox widget displays the names of a set of US states, which it fetches from the states property of StateBean in the following JavaScript object literal format:
[{label: 'Alaska', value: 'AK'},
{label: 'Arizona', value: 'AZ'},
{label: 'California', value: 'CA'},
{label: 'Oregon', value: 'OR'}]
When the user selects a state from this widget, the powerful publish and subscribe mechanism publishes the value of the user's selection to the topic /getState, which is a subtopic of the topic, /cb, as shown in the widget tag's publish property.The value that a particular kind of widget is allowed to publish is determined by the widget's data model. See Carla's blog for more information about widget data models.
<a:widget name="dojo.combobox"
subscribe="/cb"
value="${StateBean.cities}" />
When the page loads, this combobox widget fetches its initial set of city values from the cities property of StateBean. To update its values when a user chooses a state from the other combobox, the cities combobox subscribes to the same parent topic, /cb to which the states combobox publishes the state code that the user chose, as you can see by looking at the widget's subscribe property. In the next step, you'll see how the cities combobox updates its values.
glue.js file included in the web application under the Web Pages directory.
jmaki.subscribe("/cb/getState/*", function(args) {
var message = args.value;
jmaki.doAjax({method: "POST",
url: "Service?message=" + encodeURIComponent(message),
callback: function(_req) {
var tmp = _req.responseText;
var obj = eval("(" + tmp + ")");
jmaki.publish('/cb/setValues', obj);
// handle any errors
}
});
});
The subscribe function subscribes to all subtopics of the /cb/getState/ topic because of the wildcard character at the end of the topic argument of the function. Because the states combobox widget publishes the state code to the /cb/getState topic, the subscribe function receives the state code as the args variable that is passed to it. The subscribe function takes the state code and posts it to the Service servlet, which returns the appropriate set of cities. Finally, the function publishes this set of cities to the /cb/setValues topic.
By default, all combobox widgets automatically obtain their values in response to a user action by subscribing to the global /setValues topic that jMaki provides unless the developer specifies otherwise. You can also make a combobox widget subscribe to a /setValues subtopic of a developer-defined parent topic, which is what I do in this example. Because the cities combobox subscribes to the parent topic, /cb, it also subscribes to the /setValues subtopic by default and is therefore automatically updated with the new set of cities.
Normally, if you have only one combobox in a page, you do not have to create a parent topic for the purpose of updating values in the combobox; instead, you can simply publish the new values to the global /setValues topic, and the combobox will be updated with these new values automatically. We can't do that in this case because we have two comboboxes. Because all combobox widgets subscribe to the global /setValues topic automatically, we would end up setting the values of both of our comboboxes to the set of cities returned by the Service servlet if we published to the global /setValues topic.
StateBean that builds JSONArray objects for the state names and the city names that correspond to a chosen state:
...
protected String[] states =
new String[] {"Alaska", "Arizona", "California", "Oregon" };
protected String[] stateCodes =
new String[] {"AK", "AZ", "CA", "OR" };
private ResourceBundle cityNames = null;
public StateBean() {
this.init();
}
private void init() {
cityNames = ResourceBundle.getBundle("mapcity.cities");
}
...
public String getStates() throws JSONException {
JSONArray statesData = new JSONArray();
JSONObject stateData = new JSONObject();
for (int loop = 0; loop < states.length; loop++) {
stateData.put("label", states[loop]);
stateData.put("value", stateCodes[loop]);
statesData.put(stateData);
stateData = new JSONObject();
}
return jsonArrayToString(statesData, new StringBuffer());
}
public String getNewCities(String state) throws JSONException {
JSONObject city = new JSONObject();
JSONArray cities = new JSONArray();
String[] names = null;
try {
names = cityNames.getString(state).split(",");
// obtaining the cities from a resource bundle keyed by state code, but you
// can get them however you like
} catch (Exception e){
return null;
}
for(int i = 0; i < names.length; i++){
city.put("label", names[i]);
city.put("value", names[i]);
cities.put(city);
city = new JSONObject();
}
return jsonArrayToString(cities, new StringBuffer());
}
public String jsonArrayToString(JSONArray ja, StringBuffer buff) throws JSONException {
if (buff == null) buff = new StringBuffer("[");
else buff.append("[");
for (int key=0; (ja != null) && key < ja.length(); key++) {
String value = null;
if (ja.optJSONObject(key) != null){
jsonToObjectLibertal(ja.optJSONObject(key), buff);
} else if (ja.optJSONArray(key) != null) {
jsonArrayToString(ja.optJSONArray(key), buff);
} else if (ja.optLong(key, -1) != -1) {
value = ja.get(key) + "";
buff.append(value);
} else if (ja.optDouble(key, -1) != -1) {
value = ja.get(key) + "";
buff.append(value);
} else if (ja.optBoolean(key)) {
value = ja.getBoolean(key) + "";
buff.append(value);
} else if (ja.opt(key) != null) {
Object obj = ja.opt(key);
if (obj instanceof Boolean) {
value = ja.getBoolean(key) + "";
} else {
value = "'" + ja.get(key) + "'";
}
buff.append(value);
}
if (key < ja.length() -1) buff.append(",");
}
buff.append("]");
return buff.toString();
}
}
The getStates and the getNewCities methods use the org.json APIs to create a JSONArray object representation of the data. The jsonArrayToString method is boilerplate code that you can copy to your bean. What it does is converts the JSON code created by the JSON APIs to JavaScript object literals. Greg says that he might include this code in jMaki 1.1 so that you won't have to copy it.
In addition to these methods, you'd also need to add a getCities method that returns the initial set of city names for the second combobox.
Service, and add the code that gets the value from the client, passes it to StateBean, receives the list of cities from StateBean, and returns this list of cities to the client:
public void doPost(
HttpServletRequest request,
HttpServletResponse response) throws IOException, ServletException {
HttpSession session = request.getSession();
StateBean stateBean = (StateBean)session.getAttribute("StateBean");
String cityData = new String();
String message = request.getParameter("message");
try{
cityData = stateBean.getNewCities(message);
} catch (Exception e){
System.out.println("could not get city data");
}
PrintWriter writer = response.getWriter();
writer.write(cityData);
session.setAttribute("stateBean", stateBean);
}
As this example has shown, the publish and subscribe mechanism gives you a simple and powerful way to get widgets to interact in jMaki. Thank you, jMaki development team! And thank you, Greg, for enduring all my questions.
For more in-depth information and real-world examples using publish and subscribe, take a look at these blogs:
Posted at 01:25PM Sep 26, 2007 by jenniferb in Sun | Comments[11]