venerdì 1 gennaio 2010

Insert custom controls on Google maps

We definitely like maps, for this reason me and Alberto decided to explore some features related to maps and we realized some sample app in order to play with a few features:
- Twitter & maps api
- Random walk on the map

The main features of these applications (use of twitter api, put the tweets and personalized marker on the map, geocoding, paged infowindows) are described in others post (cfr. How to Play with Google Maps and Twitter api & Super simple way to use Google maps api geocoding service) but here let us to explore the definition of controls on the google map.

Map's custom controls

The google map contain UI elements that allow user interaction, as pan/zoom (e.g. GLargeMapControl), a scale (e.g. GSmallZoomControl), a button that switches the view between different map types (GMapTypeControl, per default allowing to swith between Map, Satellite, Terrain, ...).
These elements, inserted directly on the map, are known as controls and they can be included in any Google Maps API application.

All the default controls (i.e. provided by the maps API) are described here but it is also possible (and even straightforward) to build a custom control by subclassing the GControl class; Custom controls are useful for any sort of user interaction and are the standard way of putting something 'in the area' of the map (e.g. to display a custom logo in the map constructing a custom control is far the simplest way).
On the HelloKitty sample we use, for example, a control on the map in order to let the user to switch on/off the visualization of the path.

Let us to describe briefly how to setup an user generated control.

The definition of a custom control is possible through assigning as prototype object an instance of the GControl object; we define the Control on the map "TrivialMapControl" as:


   TrivialMapControl = function () {}
TrivialMapControl.prototype = new GControl();

TrivialMapControl.toggleOn
= document.createElement("span");
TrivialMapControl.toggleOn.innerHTML ="Trace ON";

TrivialMapControl.toggleOff
= document.createElement("span");
TrivialMapControl.toggleOff.innerHTML ="Trace OFF";


The definition of a custom control requires te definition of two methods:
initialize(): that creates and returns a DOM element;
getDefaultPosition(): that defines and returns an object of type GControlPosition.

The following is the initialize function that we use in our sample code:


TrivialMapControl.prototype.initialize = function(map) {
var container = document.createElement("div");
//setAttribute does not work consistently
//for 'class' with IE and ... the rest of the world
container.classNode = "map_control";

this.setButtonStyle_(TrivialMapControl.toggleOff);
GEvent.addDomListener(TrivialMapControl.toggleOff,
"click",
function() {
isViewTrace = false;
container.replaceChild(TrivialMapControl.toggleOn,
TrivialMapControl.toggleOff);
});

GEvent.addDomListener(TrivialMapControl.toggleOn,
"click",
function() {
isViewTrace=true;
container.replaceChild(TrivialMapControl.toggleOff,
TrivialMapControl.toggleOn);
});

container.appendChild(TrivialMapControl.toggleOn);

var lb = document.createElement("div");

var jl = document.createElement("img");
jl.className = "trivialmap_logo";
jl.src = document.getElementById("logo_small").src;

lb.appendChild(jl);
container.appendChild(lb);

map.getContainer().appendChild(container);
return container;
}


Our prototype.initialize() method simply displays a custom logo and defines the button (with a special style), used to show or to hide the Trace of the path along the way.

Furthermore, the getDefaultPosition function:


TrivialMapControl.prototype.getDefaultPosition =
function() {
return new GControlPosition(G_ANCHOR_TOP_LEFT,
new GSize(80, 12));
}

defines the default position of the control (so the name :o) G_ANCHOR_TOP_LEFT in our sample (the others possible choices are G_ANCHOR_TOP_RIGHT, G_ANCHOR_BOTTOM_RIGHT, G_ANCHOR_BOTTOM_LEFT) and the distance from the top/left corner.

After the definition, the map control has to be added to the map container through the following instructions:


var map =
new GMap2(document.getElementById("map_canvas"), ...);
map.addControl(new TrivialMapControl());

That's all Folks !!!

mercoledì 30 settembre 2009

Using Appengine to store and retrieve data

TBD

giovedì 17 settembre 2009

Geolocation

Keychain_team is actually working on a maps mashup (JooinK) based on the google maps api.
During the development we faced the problem of geolocate the remote user.

We looked a lot around the web and we found that it is possible
to obtain the position of the remote user accessing the "google
loader" ClientLocation field, unfortunately appear not possible to do
this directly through gwt's maps api but defining a jni function


private static native LatLng UserLocation() /*-{
if( $wnd.google ) {
if( $wnd.google.loader ) {
if ($wnd.google.loader.ClientLocation){
var center = new $wnd.google.maps.LatLng(
$wnd.google.loader.ClientLocation.latitude,
$wnd.google.loader.ClientLocation.longitude
);
return center;
}
}
}
return null;
}-*/;


and then calling the native method


LatLng userDefaultLocation = UserDefaultLocation();


We get the LatLng position of the user
Do not forget to load google's jsapi, e.g. putting in the head of your html file


<script type="text/javascript"
src="http://www.google.com/jsapi">
</script>


you will find soon the complete project on http://www.keychain.it

That's all folk!!

martedì 8 settembre 2009

Authentication on demand (using appengine UserService)

During the development of our map's mashup (JooinK, still in progress) we faced the problem of handling authentication (in this post we focus on Google Accounts).

Our target was to let the user to authenticate on demand during the navigation of JooinK without losing the current status (say, the current position on the map). We know this is in no way an unexplored field (actually, blogspot does something similar) but we think that the description of the complete procedure worth a note.

So let us suppose we want to write a simple application (named JooinK ;-) ) to be published on Google App Engine using the provided UserService (see http://code.google.com/webtoolkit/tutorials/1.6/appengine.html#user for the general overview).

Roughly speaking authentication on-demand should proceed as follows:

  1. the user navigate anonymously JooinK;
  2. the user begin the process of authentication (e.g. triggered by a component of the user interface);
  3. the control is transferred to the "google login page";
  4. at the end of the authentication process the control returns back to JooinK;
  5. the user interface changes accordingly to the result of the authentication, not loosing the "current status".


In particular:

Step a. represents a first look to the application by an anonymous user, which can navigate in and, for instance, choose a location on the map.

To proceed through step b. it is required to know the URL of the google account's identity provider, which has to be retrieved using a GWT-RPC call to an ad hoc service, as described in the aforementioned example and quoted below:


public class LoginServiceImpl
extends RemoteServiceServlet
implements LoginService {

public LoginInfo login(String ComeBackURL) {
UserService userService
= UserServiceFactory.getUserService();
User user = userService.getCurrentUser();
LoginInfo loginInfo = new LoginInfo();

if(user != null) {
loginInfo.setLoggedIn(true);
loginInfo.setEmailAddress(user.getEmail());
loginInfo.setNickname(user.getNickname());
loginInfo.setLogoutUrl(
userService.createLogoutURL(ComeBackURL));
} else {
loginInfo.setLoggedIn(false);
loginInfo.setLoginUrl(
userService.createLoginURL(ComeBackURL));
}
return loginInfo;
}
}


After a successful call to the login service the returned LoginInfo object will contain:

  • informations about the user i.e. username etc, for a logged in user;

  • the URL of the identity provider for the anonymous user.


Please note that ComeBackURL is the URL of the page where the idp will redirect the user's browser on completion of the login process, i.e. it is the URL of the part of our code that performs Step d. (we will give further details later on this paper).

When the user express the aim of authenticating (so entering into step b), say with a click on the "let me authenticate" link, we call the loginService as in the following code:


// do something informing the user that
// we are processing the
// authentication request,
//e.g. change the link text with something
//else
.....
loginService.login(ComeBackUrl,
new AsyncCallback<logininfo>() {
public void onFailure(Throwable error) {
// error comunication with the server
//.... handle this.
Window.alert('Something unhappy happened
to Jooink, try later');
}
public void onSuccess(LoginInfo result) {
loginInfo = result;
if(loginInfo.isLoggedIn()) {
// the user is already logged in ...
// nothing to do
// modify the user interface
// e.g remove the authentication link and
// sostitute it with the logout one
// show somewhere the username
//(it is inside loginInfo)
} else {
// the user is not logged in
// we have to pass the control to the idp
manageLogin(loginInfo);
}
);


Here the function manageLogin(...) represents step c and is obviously executed only if the user is not already authenticated.


public void manageLogin(LoginInfo li) {
dialogBox = new DialogBox();
dialogBox.setModal(true)
dialogBox.setText("-- Authentication --");
final Frame idpFrame
= new NamedFrame("idpFrame");
dialogBox.add(idpInteractionFrame);
idpFrame.setUrl(loginInfo.getLoginUrl());
}


The purpose of the above function is to let the user interact directly with the identity provider but this cannot be realized simply redirecting the user to loginInfo.loginURL because this will
trash the status of the application (or force the status to be serialized somewhere in ComeBackUrl), instead we use an IFRAME (IFrames or windows appear to be the only choice here).

Actually the shown code does what we expect, i.e. let the user to authenticate but unfortunately, after a successful login we will get ComeBackUrl loaded inside the FRAME that may be not the desired result (this is where we enter into step d: taking back control).

Definitely what we want is: closing the dialog box, having a variable in our code bounded with the current username.
To accomplish this we need two ingredients:
  • a servlet associated to ComeBackUrl;
  • a bit of jsni;
as shown in the following code:

The servlet is simply a non RPC version of LoginServiceImpl (but we cannot call the RPC as a result of a redirection) that returns an html page containing javascript cote that "injects" variables in the running GWT application.


...
public class LoginServlet extends HttpServlet {
public void doGet(HttpServletRequest request,
HttpServletResponse res)
throws ServletException {
UserService userService
= UserServiceFactory.getUserService();
User user = userService.getCurrentUser();
LoginInfo loginInfo = new LoginInfo();
if (user != null) {
returnSuccess(res,
user.getEmail(),user.getNickname());
} else {
returnFailure(res,
user.getLoginUrl(ComeBackUrl));
}
return;
}
}


Unfortunately now we are unable to return the loginInfo as we did in the RPC: a servlet cannot return an object to our application so we have to play with html, plain javascript and jsni.

The returnFailure method is the simpler, we have to generate an html noticing the user that some problem happened and eventually a courtesy link to let the user to retry authentication. For instance:


public void
returnFailure(HttpServletResponse res,
String loginURL) {
try {
PrintWriter out = response.getWriter();
out.println("Autentication UNsuccessfull. " +
"Retry");
} catch (Exception e) {
...
}
return;
}


On the other hand returnSuccess should trigger a javascript method to close the dialog and "inject" username and email in the application running in the browser. Let us remark that the following code is for demonstration purpose and is cumbersome because a more elegant
implementation will require to describe a more structured set of classes that are out of the scope of this note.


public void
returnSuccess(HttpServletResponse res,
String email, String name) {
String onload =
"window.parent.userData('"+name+"');";
onLoad +=
"window.parent.userEmail('"+email+"');";
onLoad += "window.parent.dialogHide();";
try {
PrintWriter out = response.getWriter();
out.println("Autentication OK");
} catch (Exception e) {
...
}
return;
}


where userData, userEmail, dialogHide (step e!) have to be provided by jsni; to do this we have to call, e.g. in manageLogin just after the assignment of dialogBox, a special function (actually a native function) defined as follows:


public static native void
prepareAuthHookMethods(LoginInfo logI,
DialogBox db) /*-{
$wnd.userName = function(s) {
return
logI.@FULL_PACKAGE_NAME_HERE.
LoginInfo::
setUserName(Ljava/lang/String)(s);
}
$wnd.userEmail = function(s) {
return
logI.@FULL_PACKAGE_NAME_HERE.
LoginInfo::
setUserEmail(Ljava/lang/String)(s);
}
$wnd.dialogHide= function() {
return
db.@FULL_PACKAGE_NAME_HERE.
LoginInfo::hide()();
}
}


That's all folks.