Posts tagged javascript
AJAXified Location Search Text Field in 15min Using MS Life Search, Java and EXTJS
Dec 15th
A recent project involved a Microsoft Virtual Earth mashup. As it turns out, the initial component set shipped with the Virtual Earth (VE) SDK is rather limited and – quite frankly – very ugly.
One of the things needed was an input field the user can utilize to type in an address or location, which gets then of course shown in the map. Ideally this input field would provide a “suggestion drop down” containing places known to VE. As it turns out, this is accomplished using…
- Java Servlets for the middle tier (project requirement)
- EXTJS for the components and the AJAX communication
- The MS Virtual Earth Life Search web service
The Servlet
Wrapping the call to the VE web service into a servlet has several advantages: We can perform caching of the requests, thus significantly lowering the response time of the service while being able to create nice statistics of what people are searching for. It also allows us to process both the search string and the results on the server to perform e.g. filtering, limiting searches to geographic areas, etc.
Calling the web service to perform the address lookup is very easy:
private static String URL_PARAMETER_PREFIX ;
private static String URL_PARAMETER_POSTFIX;
static{
try {
URL_PARAMETER_PREFIX = URLEncoder.encode("a", "UTF-8") + "=" + "&" + URLEncoder.encode("b", "UTF-8") + "=";
URL_PARAMETER_POSTFIX = "&" + URLEncoder.encode("c", "UTF-8") + "=" + URLEncoder.encode("0.0", "UTF-8");
URL_PARAMETER_POSTFIX += "&" + URLEncoder.encode("d", "UTF-8") + "=" + URLEncoder.encode("0.0", "UTF-8");
URL_PARAMETER_POSTFIX += "&" + URLEncoder.encode("e", "UTF-8") + "=" + URLEncoder.encode("0.0", "UTF-8");
URL_PARAMETER_POSTFIX += "&" + URLEncoder.encode("f", "UTF-8") + "=" + URLEncoder.encode("0.0", "UTF-8");
URL_PARAMETER_POSTFIX += "&" + URLEncoder.encode("g", "UTF-8") + "=" ;
URL_PARAMETER_POSTFIX += "&" + URLEncoder.encode("i", "UTF-8") + "=" ;
URL_PARAMETER_POSTFIX += "&" + URLEncoder.encode("r", "UTF-8") + "=" + URLEncoder.encode("0", "UTF-8");
} catch (UnsupportedEncodingException e) {
URL_PARAMETER_POSTFIX = null;
URL_PARAMETER_PREFIX = null;
e.printStackTrace();
}
}
private String performMSLiveSearch(String address){
if (liveSearchURL == null
|| URL_PARAMETER_PREFIX == null || URL_PARAMETER_POSTFIX == null ){
// log error here
return "";
}
StringBuilder requestResult = new StringBuilder();
try {
String paramteterString = URL_PARAMETER_PREFIX
+ URLEncoder.encode(address, "UTF-8")
+ URL_PARAMETER_POSTFIX;
// Send request
URLConnection conn = liveSearchURL.openConnection();
conn.setDoOutput(true);
OutputStreamWriter wr = new OutputStreamWriter(conn.getOutputStream());
wr.write(paramteterString);
wr.flush();
// Get the response
BufferedReader rd = new BufferedReader(new InputStreamReader(conn.getInputStream()));
String line;
while ((line = rd.readLine()) != null)
requestResult.append(line);
wr.close();
rd.close();
} catch (Exception e) {
// log exception here
e.printStackTrace();
}
System.out.println(requestResult.toString());
return requestResult.toString();
}
Instead using heavyweight XML parsers, we can extract the needed information fast and simple using regular expressions:
private Pattern ambiguityListPattern = Pattern.compile(".*?new Array\\('(.*?)',.*?\\)");
private Pattern geoLocationPattern = Pattern.compile(".*?AddGeoLocation\\('(.*?)',.*?\\)");
private String[] analyzeSearchResults(String searchResultString){
if (searchResultString == null)
throw new IllegalArgumentException("Parameter must not be null!");
// unfortunately we are stuck with 1.4, so no generics
ArrayList results = new ArrayList();
// extract the "ambiguity list"
Matcher m = ambiguityListPattern.matcher(searchResultString);
while (m.find())
results.add(m.group(1));
// extract the "geolocation list"
m = geoLocationPattern.matcher(searchResultString);
while (m.find())
results.add(m.group(1));
return (String[])results.toArray(new String[results.size()]);
}
To finalize the servlet, we implement the doPOST method as follows:
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String searchAddress = req.getParameter("address");
PrintWriter out = resp.getWriter();
// chaching and search string processing goes here
String[] results = analyzeSearchResults(performMSLiveSearch(searchAddress));
LocationSearchCache.getInstance().addSearchResults(searchAddress, results);
// format output for EXTJS array reader
out.print("[");
for (int i=0; i< results.length; i++){
if (i>0)
out.print(",");
out.print("['");
out.print(results[i]);
out.print("']");
}
out.println("]");
out.close();
}
This leaves us with the HTML and Javascript, which – thanks to the great EXTJS library, is a breeze:
Ajax Virtual Earth Search Test
That’s it! All the magic is done in…
Ext.onReady(function() {
var store = new Ext.data.Store({
proxy: new Ext.data.HttpProxy({
url:SERVLET_ADDRESS, method: 'POST'}),
reader: new Ext.data.ArrayReader({}, [{name: 'address'}])
});
var combo = new Ext.form.ComboBox({
store: store,
queryParam:'address',
displayField:'address',
hideTrigger:true,
typeAhead: false,
triggerAction: 'all',
emptyText:'City, Address or Postal Code',
selectOnFocus:true
});
combo.applyTo('addressCombo');
});
That’s it! We end up with a very flexible component that can be easily plugged into any page, or even modularized into a JSF component.
We used the doGet method of the servlet to spit out statistics about the current state of the query cache, which also only takes a few lines of code.