Beaucoup d’applications web sont désormais basées sur AJAX, et offrent ainsi une interface plus réactive et plus dynamique, en minimisant (voire supprimant) les rechargements de page : les données à affichées sont chargées en arrière-plan puis insérées dans la page web, à coup de JavaScript (manipulation du DOM).

Contrairement à une page web entièrement générée par le serveur, cette page doit alors faire preuve d’une intelligence supplémentaire afin de gérer l’état de l’application à chaque instant de son utilisation. Peu importe donc la technologie employée sur le serveur, on s’attarde donc à trouver une technologie évoluée coté client, forcément basée sur JavaScript, seul langage à être embarqué dans tous les navigateurs modernes. Ont alors vu le jour de multiples framework JavaScript, tels jQuery, MooTools, Prototype ou encore Scriptaculous.

Google a tenté une approche différente en proposant un framework Java, couplé à un traducteur Java vers JavaScript. Une réelle prouesse technique, qui promet une abstraction quasi-totale du navigateur. L’avantage est que le codage se fait en Java, langage plus rigoureux que JavaScript, et ainsi plus simple à débugguer.

Avec GWT, l’application web, c’est une page web. S’il y a différentes vues (un formulaire de création, une liste, …), c’est via le DOM qu’on affiche les widgets qu’il faut. Mais pour le navigateur, l’URL reste la même, et si on recharge la page, la vue peut alors être perdue. De la même manière, l’utilisation du bouton Précédent dans le navigateur ne permet pas de naviguer, comme sur un site web classique. Sauf si… Si on réussit à faire croire au navigateur que l’URL a changé sans recharger la page, ça peut marcher.

Les ancres dans les URL

Dans une URL, on peut faire référence à un élément de la page (identifié de manière unique par un attribut id) en suffisant l’URL par #<id de l’élément>. La navigateur va alors défiler la page jusqu’à ce que l’élément concerné soit visible (s’il l’est). Cela permet de naviguer au sein d’une même page, et c’est très souvent utilisé pour une table des matières en début de page. L’avantage est que le navigateur considère tout changement derrière le caractère # d’une URL (l’ancre) comme étant une URL différente, ayant son entrée propre dans l’historique des pages visitées. En même temps, un changement d’ancre ne recharge pas la page.

La classe History de GWT

En GWT, on peut être notifié par un système d’événement d’un changement d’ancre (et donc sans que la page ne soit rechargée) et connaître le nom de l’ancre. Ainsi, on peut exécuter du code et afficher telle ou telle vue en fonction de l’ancre actuelle. La classe History dans GWT permet cela. Et GWT propose une technique très efficace pour gérer ces changements dans l’interface en fonction de l’URL

Activity et Place

GWT part du principe suivant  : à une URL correspond un affichage de l’application. Cet état (visuel) de l’application qui dépend de l’URL s’appelle une Place.

A cette Place, on associe généralement une Activity, c’est à dire une fonctionnalité de l’application.

Cette Activity va alors exécuter le code qu’il faut pour placer l’interface dans l’état voulue par l’URL.

Pour résumer :

  1. de l’URL (ancre, encore appelé history token), on déduit une Place via un PlaceHistoryMapper,
  2. de cette Place, on déduit une Activity via un ActivityMapper.

Dans la pratique, cela se traduit se cette manière-là.

  • une classe par Place (ici : EditPlace et ListPlace),
  • une classe par Activity (ici : EditActivity et ListActivity),
  • un ActivityMapper (ici AppActivityMapper),
  • un PlaceHistoryMapper (ici AppPlaceHistoryMapper),
  • un point d’entrée (ici Index),
  • une Factory (ici ClientFactory) pour quelques objets dont on a souvent besoin.

Dans notre point d’entrée (Index.java)

// Nous avons besoin d'un panneau dans lequel vont s'afficher les différentes
// vues de l'application.
// Ici, ce sera la page entière, mais on pourrait n'avoir qu'une partie
// centrale par exemple, avec des menus globaux autour qui seraient toujours
// là.
SimplePanel rootPanel = new SimplePanel();
RootLayoutPanel.get().add(rootPanel);
 
// Le bus où tous les événements "métiers" de l'application seront déclenchés.
EventBus eventBus = ClientFactory.INSTANCE.getEventBus();
 
// Un peu de plomberie...
PlaceController placeController = ClientFactory.INSTANCE.getPlaceController();
ActivityManager activityManager = new ActivityManager(
   new AppActivityMapper(), eventBus
);
activityManager.setDisplay(rootPanel);
 
AppPlaceHistoryMapper historyMapper = GWT.create(AppPlaceHistoryMapper.class);
PlaceHistoryHandler historyHandler = new PlaceHistoryHandler(historyMapper);
// Place par défaut si rien n'est spécifié dans l'URL.
ListPlace defaultPlace = new ListPlace();
historyHandler.register(placeController, eventBus, defaultPlace);
 
// On déclenche tout ça !
historyHandler.handleCurrentHistory();

Ce qu’il est important de retenir ici :

  • Il est obligatoire d’avoir un seul conteneur qui sera la destination des différentes vues de l’application.
  • Ces initialisations sont certes un peu fastidieuses, mais elles peuvent être copiées/collées d’un projet à un autre (ou mutualisées, pourquoi pas, dans la ClientFactory).
  • Le déclenchement réalisé sur la dernière ligne n’est pas automatique : ne l’oubliez pas !
  • AppPlaceHistoryMapper est instancié via un defered binding (GWT.create()).

AppPlaceHistoryMapper :

package com.greenivory.gwt.tutorial.client;
 
import com.google.gwt.place.shared.PlaceHistoryMapper;
import com.google.gwt.place.shared.WithTokenizers;
 
@WithTokenizers( {
	EditPlace.Tokenizer.class,
	ListPlace.Tokenizer.class,
} )
public interface AppPlaceHistoryMapper extends PlaceHistoryMapper {
}

Ce qu’il est important de retenir ici :

  • Il suffit de lister les différentes Place (en fait, leur Tokenizer) dans l’annotation @WithTokenizers.

AppActivityMapper :

package com.greenivory.gwt.tutorial.client;
 
import com.google.gwt.activity.shared.Activity;
import com.google.gwt.activity.shared.ActivityMapper;
import com.google.gwt.place.shared.Place;
 
public class AppActivityMapper implements ActivityMapper {
 
    @Override
    public Activity getActivity(Place place) {
 
        if (place instanceof ListPlace) {
            return new ListActivity((ListPlace) place);
        }
 
        if (place instanceof EditPlace) {
            return new EditActivity((EditPlace) place);
        }
 
        return null;
    }
 
}

Ce qu’il est important de retenir ici :

  • A chaque Place, son Activity !

ClientFactory :

package com.greenivory.gwt.tutorial.client;
 
import com.google.gwt.event.shared.EventBus;
import com.google.gwt.event.shared.SimpleEventBus;
import com.google.gwt.place.shared.PlaceController;
 
public class ClientFactory {
 
	private EventBus eventBus;
	private PlaceController placeController;
 
	public static final ClientFactory INSTANCE = new ClientFactory();
 
	protected ClientFactory() {
		eventBus = new SimpleEventBus();
		placeController = new PlaceController(eventBus);
	}
 
	public EventBus getEventBus() {
		return eventBus;
	}
 
	public PlaceController getPlaceController() {
		return placeController;
	}
}

Ce qu’il est important de retenir ici :

  • Il est impératif d’utiliser toujours les mêmes instances du PlaceController et du EventBus dans toute l’application. La ClientFactory (qui est un singleton) est donc une bonne pratique pour les contenir.

ListPlace :

package com.greenivory.gwt.tutorial.client;
 
import com.google.gwt.place.shared.Place;
import com.google.gwt.place.shared.PlaceTokenizer;
import com.google.gwt.place.shared.Prefix;
 
/**
 * Les URLs sont du type : http://server/index.html#ancre:token.
 * 'ancre' sert à déterminer la Place à utiliser.
 * 'token' sert à transmettre des paramètres à la Place.
 * Ce token peut être vide, mais le double-point est obligatoire.
 */
public class ListPlace extends Place {
 
	public ListPlace() {
        super();
    }
 
    @Prefix("list") // Ancre utilisée pour identifier cette Place.
    public static class Tokenizer implements PlaceTokenizer {
 
        @Override
        public String getToken(ListPlace place) {
        	// Retourner le token en fonction des paramètres de la place.
        	// Ici, jamais de paramètre, donc toujours chaîne vide
        	// (mais jamais null !).
        	return "";
        }
 
        @Override
        public ListPlace getPlace(String token) {
        	// En fonction du 'token' (derrière le double-point
        	// après l'ancre), instancier la Place.
        	// Ici, on ne gère pas de paramètres, donc on instancie
        	// toujours une ListPlace tout simple.
        	return new ListPlace();
        }
 
    }
}

ListActivity :

package com.greenivory.gwt.tutorial.client;
 
import com.google.gwt.activity.shared.Activity;
import com.google.gwt.event.shared.EventBus;
import com.google.gwt.place.shared.Place;
import com.google.gwt.user.client.ui.AcceptsOneWidget;
import com.google.gwt.user.client.ui.HTML;
 
public class ListActivity implements Activity {
 
	/**
	 * @param place
	 */
	public ListActivity(Place place) {
	}
 
	@Override
	public String mayStop() {
		// Retourner null pour autoriser l'arrêt de l'Activity.
		// Retourner une chaîne non-nulle pour demander à
		// l'utilisateur si l'Activity doit être stoppée ou non.
		return null;
	}
 
	/* (non-Javadoc)
	 * @see com.google.gwt.activity.shared.Activity#onCancel()
	 */
	@Override
	public void onCancel() {
		// Exécutée quand l'Activity n'est pas démarrée.
	}
 
	/* (non-Javadoc)
	 * @see com.google.gwt.activity.shared.Activity#onStop()
	 */
	@Override
	public void onStop() {
		// Exécutée quand l'Activity est arrêtée.
	}
 
	/* (non-Javadoc)
	 * @see com.google.gwt.activity.shared.Activity#start()
	 */
	@Override
	public void start(AcceptsOneWidget panel, EventBus eventBus) {
		// Code de l'Activity.
		panel.setWidget(new HTML(
		"Liste d'éléments | &lt;a href='#edit:1'&gt;Editer id=1&lt;/a&gt; | &lt;a href='#edit:3'&gt;Editer id=3&lt;/a&gt;"
		));
	}
 
}

Ce qu’il est important de retenir ici :

  • La méthode start() est indéniablement la plus importante, mais les autres sont très pratiques pour contrôler la navigation : l’utilisateur peut-il arrêter cette Activity (méthode mayStop()) ?

EditPlace :

package com.greenivory.gwt.tutorial.client;
 
import com.google.gwt.place.shared.Place;
import com.google.gwt.place.shared.PlaceTokenizer;
import com.google.gwt.place.shared.Prefix;
 
/**
 * Les URLs sont du type : http://server/index.html#ancre:token.
 * 'ancre' sert à déterminer la Place à utiliser.
 * 'token' sert à transmettre des paramètres à la Place. Peut être vide, mais le double-point est obligatoire.
 */
public class EditPlace extends Place {
 
	private int id;
 
	public EditPlace(int id) {
        super();
        this.id = id;
    }
 
	public final int getId() {
		return id;
	}
 
    @Prefix("edit") // Ancre utilisée pour identifier cette Place.
    public static class Tokenizer implements PlaceTokenizer {
 
        @Override
        public String getToken(EditPlace place) {
        	// Retourner le token en fonction des paramètres de la place.
        	// Ici, on s'attend à avoir l'ID en paramètre, donc on retourne une chaîne contenant l'ID de la Place.
        	return String.valueOf(place.getId());
        }
 
        @Override
        public EditPlace getPlace(String token) {
        	// En fonction du 'token' (derrière le double-point après l'ancre), instancier la Place.
        	// Ici, le token contient l'ID de l'élément à éditer : nous n'avons qu'à le transformer en entier...
        	try {
        		return new EditPlace(Integer.parseInt(token));
        	} catch (NumberFormatException e) {
        		return new EditPlace(-1);
        	}
        }
 
    }
}

Ce qu’il est important de retenir ici :

  • Il est possible de passer autant de paramètres qu’on le souhaite dans le token, pourvu qu’on le parse correctement.
  • Vous êtes libres de créer tous les paramètres nécessaires dans la Place.
  • Ne prenez pas à la légère la méthode getToken() : il est important qu’elle retourne le token qui permet de reconstruire une Place identique (avec les mêmes paramètres). getToken() doit être, en quelque sorte, le miroir de getPlace().

EditActivity :

package com.greenivory.gwt.tutorial.client;
 
import com.google.gwt.activity.shared.Activity;
import com.google.gwt.event.shared.EventBus;
import com.google.gwt.user.client.ui.AcceptsOneWidget;
import com.google.gwt.user.client.ui.HTML;
 
public class EditActivity implements Activity {
 
	private EditPlace place;
 
	/**
	 * @param place
	 */
	public EditActivity(EditPlace place) {
		this.place = place;
	}
 
	@Override
	public String mayStop() {
		// Retourner null pour autoriser l'arrêt de l'Activity.
		// Retourner une chaîne non-nulle pour demander à l'utilisateur si l'Activity doit être stoppée ou non.
		return null;
	}
 
	/* (non-Javadoc)
	 * @see com.google.gwt.activity.shared.Activity#onCancel()
	 */
	@Override
	public void onCancel() {
		// Exécutée quand l'Activity n'est pas démarrée.
	}
 
	/* (non-Javadoc)
	 * @see com.google.gwt.activity.shared.Activity#onStop()
	 */
	@Override
	public void onStop() {
		// Exécutée quand l'Activity est arrêtée.
	}
 
	/* (non-Javadoc)
	 * @see com.google.gwt.activity.shared.Activity#start(com.google.gwt.user.client.ui.AcceptsOneWidget, com.google.gwt.event.shared.EventBus)
	 */
	@Override
	public void start(AcceptsOneWidget panel, EventBus eventBus) {
		// Code de l'Activity.
		if (place.getId() == -1) {
			panel.setWidget(new HTML("<strong>Paramètre incorrect dans l'URL.</strong>"));
		} else {
			panel.setWidget(new HTML("Edition de l'élement " + place.getId()));
		}
	}
 
}

Ce qu’il est important de retenir ici :

  • Il est intéressant d’avoir accès à la Place pour en récupérer des informations (paramètres issus du token dans l’URL).

Une fois que tout est en place, changer de vue est un véritable jeu d’enfant ! Il y a deux possibilités :

  1. Dans un lien hypertexte, mettre l’URL de la Place vers laquelle on souhaite aller (attention au double-point derrière l’ancre, obligatoire, et souvent source de perte de temps).
    Par exemple #list: pour se rendre sur la liste des éléments.
    Ou #edit:3 pour se rendre sur la EditPlace pour éditer l’élément qui a l’ID 3.
  2. Dans du code GWT avec (par exemple) :
    ClientFactory.INSTANCE.getPlaceController().goTo(new EditPlace(47));

Dans un prochain article, nous étudierons comment utiliser le pattern MVP pour construire des UI propres et éviter de faire des vues à rallonge…

Jean Georges Perrin, CEO de GreenIvory

Jean Georges Perrin, fondateur de GreenIvory, envoie une lettre à ses clients, ses partenaires et amis. Vous pouvez le lire directement sur notre site web global. Quatre années de présence active en Alsace et en Europe, La société qui croit que « l’information doit être vécue » se porte bien et promet d’autres surprises en 2012… et avant…

A lire sur le site de GreenIvory.

Le 13 septembre 2011 a eu lieu l’inauguration du télécentre de Reichstett, au nord de Strasbourg. Il s’agit du premier télécentre expérimental du maillage Alsace ; il se situe 4 rue de l’Artisanat à Reichstett.
Alsace20 vous propose de revenir sur cet événement au travers de la vidéo ci-dessous.
Pour tous renseignements au sujet de ce télécentre et plus généralement du télétravail en Alsace, rendez-vous sur http://www.bureau-mobile.fr/.


Un télécentre pour travailler loin de chez soi! par Alsace20

VoiceObserver™

Ne laissez plus votre marque aux autres.
VoiceObserver™ est désormais un produit mature et quitte son statut de bêta. Nous y avons ajouté une nouvelle option fantastique : VoiceObserver™ remonte désormais dans le temps pour analyser la réputation en ligne. Vous voulez savoir combien et à quelle distance? Découvrez tout cela sur notre blog.

N’oubliez pas que VoiceObserver™ est vraiment adapté pour le benchmarking des marques. Alors n’oubliez pas d’ajouter vos concurrents « favoris » lorsque vous démarrez (ou modifiez) une analyse.

Découvrez VoiceObserver™ : http://VoiceObserver.com.

OnliGence™

L’info à votre mesure.
Imaginez… Un espace personnel, rien qu’à vous, qui vous présente uniquement l’actualité qui vous intéresse. Imaginez ce même espace vous faisant découvrir de nouvelles sources d’information… Eh bien, cet espace, ne l’imaginez plus : utilisez-le !

OnliGence™ est un excellent outil pour aider à lutter contre la surcharge d’information (l’infobésité) en classant automatiquement les nouvelles qui sont significatives pour vous. Aujourd’hui, OnliGence™ vous laisse découvrir plus de 280 thèmes et grâce à la version Premium, vous pouvez en afficher jusqu’à 20 en cinq colonnes à la une…

Découvrez OnliGence™ : http://OnliGence.com.

Cette semaine je profite de la Friday Update de VoiceObserver pour annoncer que le blog est enfin en ligne. Sur ce blog, vous retrouverez des articles sur les différentes évolutions du produit, les dernières nouveautés, des trucs et astuces, ou encore des exemples de projets. Les futures Friday Update concernant VoiceObserver seront aussi disponibles sur le blog. Le blog est disponible en français a l’adresse suivante : http://blog-fr.voiceobserver.com/ et en anglais à cette adresse : http://blog-en.voiceobserver.com/
Ce blog vous permettra aussi d’échanger des informations entre utilisateurs ainsi que de laisser vos avis sur le produit et ses fonctionnalités.

Parmi les bonnes nouvelles du mois, Jean-Georges Perrin a remporté le 8 mai 2011 le trophée Réseau Entreprendre et nous nous en réjouissons ! Certains se demandent sans doute ce que signifie Réseau Entreprendre. Je dirais simplement que c’est une manière d’accompagner les jeunes PME à travers un réseau d’acteurs. Jean-Georges vous en parlera surement mieux que moi, voici son avis quant à la définition d’un réseau :

« Pour moi, le réseau entreprendre est un instrument un peu transgénérationnel où les entrepreneurs qui ont réussi (pas uniquement financièrement) coachent et soutiennent leurs cadets »

Il nous parle également des bénéfices en tant que lauréat :
« Certes un réseau apporte un soutien financier, et c’est important quand on investit très lourdement dans la technologie, mais le fait d’être lauréat ouvre les portes de beaucoup à nouvelles choses qui ne sont pas forcément très « matérielles » ni quantifiables. Je tiens à remercier Nicole Martin-Spittler, Bertrand Fonquernie, Francois Borocco et Annick Rudolf. »

Toute la GreenTeam le félicite pour cette belle réussite, en espérant qu’elle serve de modèle aux futures PME souhaitant se lancer dans le monde de l’entrepreneuriat en étant épaulé.

Au lendemain du lancement d’OnliGence, le nouveau produit de GreenIvory, nous tenions à partager avec vous un screenshot de l’évolution du buzz autour de ce produit vu par VoiceObserver.

Une fois de plus, VoiceObserver montre une nette évolution suite au lancement d’OnliGence et à l’ouverture du site : http://www.onligence.com

Avec l’explosion du web 2.0 la masse d’information est énorme et les comportements des internautes sont très divers. GreenIvory a essayé de décrypter ces comportements afin de toujours mieux répondre aux attentes des internautes. Nous avons choisi de vous présenter notre vision sous forme d’infographie.

Retrouvez là sur notre blog OnliGence


Marc Lipskier est un penseur libre. Bon, il est avocat au barreau de Paris, travaille pour le cabinet Bamboo & Bees et a participé à TEDxAlsace 2010.

Marc est le gagnant du concours que nous avons fait, il a invité le plus de personnes à s’inscrire sur la pré-page d’OnliGence… Il a aussi accepté d’être notre « guest speaker » pour l’occasion. Il nous parlera de web, de sa vision du web… Ce sera plutôt amusant, parce qu’il en sait autant que vous tous sur OnliGence, soit, à peu près rien…

Je crois qu’il reste encore quelques places pour le lancement.

 

Cette semaine l’actualité politique a été un peu bousculée par l’affaire DSK qui se déroule à New York actuellement. VoiceObserver nous montre l’impact qu’a cette affaire sur le nombre d’articles qui sont écrits concernant Dominique Strauss-Kahn. Malheureusement pour lui, c’est d’un buzz négatif qu’il s’agit. On peut aussi noter une légère augmentation des articles écrits citant Martine Aubry et François Hollande, sans doute liée à l’affaire DSK.

DSK comparé aux autres politiciens français

 

Retrouvez une autre preuve de l’efficacité de VoiceObserver en matière de buzz avec les 30 ans de la SNCF.