insideIT.fr : le blog des architectes IT de SFEIR

Aller au contenu | Aller au menu | Aller à la recherche

vendredi 25 mars 2011

Retour d'expérience d'un projet GWT : 8 Internationalisation (8/8)

Le contexte:

Le projet a pour but d'améliorer l'ergonomie et l'usabilité du back office d’un produit en cours de développement réalisé en collaboration avec SFEIR

Dans le cadre de ce projet, la version de GWT utilisée passe de la 1.7.1 à la 2.0.3 afin de bénéficier des nouvelles fonctionnalités offertes par cette version. De nouveaux pattern de développement ont également été mis en place au cours de ce projet.

Voici une série d'article sur les nouveautés de GWT 2, les choix d'architectures, et bibliothèques utilisés qui font part de notre retour d'expérience sur le sujet. Ces articles ont été écris par David Aboulkheir, Patrice de Saint Steban et Cyril Lakech

  1. Nouveautés de GWT 2.0
  2. UiBinder, enfin une forte collaboration entre le designer et le développeur
  3. Intégration facile de maquette Html en GWT 2
  4. Architecture Modèle-Vue-Presenteur
  5. Implémentation Modèle-Vue-Présenteur
  6. Ecrire des tests unitaires avec Mockito
  7. Mise en place de Gin sur le projet
  8. Internationalisation

Dans le cadre du projet, nous avons mis en place l'internationalisation de l'application. Voici la documentation officielle de Google :

Nous avons ajouté le module I18N dans le module GWT "Module.gwt.xml" :

<inherits name="com.google.gwt.i18n.I18N"/>


Internationalisation en JAVA

Nous avons créé 3 classes :

  • /src/main/java/bws/i18n/ModuleConstants.java de type Constants
  • /src/main/java/bws/i18n/ModuleConstantsWithLookup.java de type ConstantsWithLookup
  • /src/main/java/bws/i18n/ModuleMessages.java de type Messages

Ainsi que les fichiers properties suivant qui correspondent à la traduction par défaut :

  • /src/main/resources/bws/i18n/ModuleConstants.properties
  • /src/main/resources/bws/i18n/ModuleConstantsWithLookup.properties
  • /src/main/resources/bws/i18n/ModuleMessages.properties

Et pour la traduction en français :

  • /src/main/resources/bws/i18n/ModuleConstants_fr.properties
  • /src/main/resources/bws/i18n/ModuleConstantsWithLookup_fr.properties
  • /src/main/resources/bws/i18n/ModuleMessages_fr.properties

Les fichiers ".properties" doivent être au format UTF-8, et les apostrophe (') doivent être dédoublé pour les interfaces étendant l'interface Messages (pas pour les Constants)

Par exemple pour afficher un message internationalisé depuis du code java d'une classe cliente (GWT) vous devez respecter la procédure suivante :

1/ Créer une méthode dans ModuleConstants.java, par exemple :

String warnCloseWindow();

2/ Ajouter une entrée dans les fichiers ModuleConstants*.properties avec la traduction correspondante:

warnCloseWindow=Are you sure you want to close this window?

3/ Utiliser cette clé dans votre classe java :

Window.alert(ModuleConstants.CONSTANTS.warnCloseWindow());

Pour la classe ModuleConstantsWithLookup, cela fonctionne de la même façon (méthode + clé correspondante) mais il est également possible de récupérer la valeur d'une clé à partir de la clé et sans appelé la méthode directement. Comme dans le code suivant ou on cherche à récupérer la valeur pour la clé "theKey":

ModuleConstantsWithLookup.CONSTANTS.getString("theKey");

Pour la classe ModuleMessages, il est possible de donner des paramètres aux méthodes qui seront intégrer dans la traduction. Un texte ne se place pas forcement au même endroit en fonction de la traduction (au début ou à la fin).

On met le numéro du paramètre (en commençant à 0) entre accolades (ex {0}), il est ensuite possible d'appliquer un formatage ({1,number} ou {0,date,medium} : en utilisant le formatage de la langue courante). On peux aussi gérer les formes singuliers et pluriels :

String cartItems(@PluralCount int itemCount);

et dans le fichier ".properties" :

cartItems = There are {0,number} items in your cart.
cartItems[one] = There is 1 item in your cart.
cartItems[none] = There is no item in your cart.


Internationalisation avec UiBinder

Pour traduire les libellés contenus dans des écrans UiBinder*.ui.xml vous devez encadrer la chaîne de caractère avec des balises spécifiques. Il vous faut tout d'abord ajouter ceci à votre fichier *.ui.xml dans la balise ui:UiBinder: ui:generateFormat='com.google.gwt.i18n.rebind.format.PropertiesFormat' ui:generateKeys="com.google.gwt.i18n.rebind.keygen.MD5KeyGenerator" ui:generateLocales="default"

Pour avoir au final:

<ui:UiBinder xmlns:ui="urn:ui:com.google.gwt.uibinder"    
 ui:generateFormat='com.google.gwt.i18n.rebind.format.PropertiesFormat'
 ui:generateKeys="com.google.gwt.i18n.rebind.keygen.MD5KeyGenerator"   
 ui:generateLocales="default"
 xmlns:g="urn:import:com.google.gwt.user.client.ui">
...
</ui:UiBinder>

Par exemple, pour traduire : "where you can build your own workspace with widget" Il faut remplacer la chaine par :

<ui:msg description="FourthSentence">where you can build your own workspace with widgets.</ui:msg>

Et ce va automatiquement créer une nouvelle clé pour ce libellé. Vous trouverez les fichier properties générés dans target/extra/module

Ils sont nommés avec le full qualified name du fichier java concaténé avec le nom du fichier ui.xml puis suffixés avant l'extension par GenMessages... Simplissime non ? Merci Google (Cela évoluera très prochainement dans les futures release de GWT pour être simplifié)

Par exemple:
module.client.homepage.HomePageMenuHomePageMenuUiBinderImplGenMessages.properties

Ensuite nous positionnons un fichier dans le répertoire ou se trouve le fichier ui.xml à internationaliser, par exemple pour HomePageMenu.ui.xml :

/src/main/java/module/client/homepage/HomePageMenuHomePageMenuUiBinderImplGenMessages.properties

et évidemment :
/src/main/java/module/client/homepage/HomePageMenuHomePageMenuUiBinderImplGenMessages_fr.properties


Utilisation simplifiée

Une autre technique consiste à utiliser une petite ruse de Google : Si le compilateur, ne trouve pas le fichier de properties selon le nom donnée, il va chercher dans le fichier :
/src/main/ressources/com/google/gwt/i18n/client/LocalizableResource.properties

ou pour la version française :
/src/main/ressources/com/google/gwt/i18n/client/LocalizableResource_fr.properties

Il est ainsi possible de mettre toutes les traductions des fichiers UiBinder dans ce fichier et avoir un gestion simplifier de toutes les traductions. Pour créer ce fichier, un développeur a créé un petit script Python pour scanner tous les fichiers "*.properties" générées par le compilateur et les ajouter dans ce fichier. Il fait ensuite un mixte avec les clés de langues déjà existantes, et indique dans un commentaire, les textes à traduire, et surtout ceux dont la description a changé et qu'il faut peut être revoir la traduction.

Le script python mergelocales.py est disponible ici

Il suffit de le lancer avec :

mergelocale.py target/extra/module src/main/ressources/com/google/gwt/i18n/client/

mardi 22 mars 2011

Retour d'expérience d'un projet GWT : 7 Mise en place de Gin sur le projet (7/8)

Le contexte:

Le projet a pour but d'améliorer l'ergonomie et l'usabilité du back office d’un produit en cours de développement réalisé en collaboration avec SFEIR

Dans le cadre de ce projet, la version de GWT utilisée passe de la 1.7.1 à la 2.0.3 afin de bénéficier des nouvelles fonctionnalités offertes par cette version. De nouveaux pattern de développement ont également été mis en place au cours de ce projet.

Voici une série d'article sur les nouveautés de GWT 2, les choix d'architectures, et bibliothèques utilisés qui font part de notre retour d'expérience sur le sujet. Ces articles ont été écris par David Aboulkheir, Patrice de Saint Steban et Cyril Lakech

  1. Nouveautés de GWT 2.0
  2. UiBinder, enfin une forte collaboration entre le designer et le développeur
  3. Intégration facile de maquette Html en GWT 2
  4. Architecture Modèle-Vue-Presenteur
  5. Implémentation Modèle-Vue-Présenteur
  6. Ecrire des tests unitaires avec Mockito
  7. Mise en place de Gin sur le projet
  8. Internationalisation

Définitions de GIN/GUICE

Définition de Gin# : “GIN (GWT INjection) brings automatic dependency injection (DI) to Google Web Toolkit client-side code. GIN is built on top of Guice and uses (a subset of) Guice's binding language. (See GuiceCompatibility for details.) By using GWT's compile-time Generator support, GIN has little-to-no runtime overhead compared to manual DI.”

Définition de Guice# : “Guice is a dependency injection (DI) container”

Pour résumer, Gin est une librairie permettant de faire de l’injection de dépendance par paramètrage sur des projets GWT. Le but de l’injection de dépendance est de se passer du code permettant de relier les différents modules de l’application entre eux en le centralisant, ce qui améliore la lisibilité du code. Pour cela, il suffit de créer une factory ainsi que la configuration du binding des différents modules.

Configuration de Gin

Ajout de Gin en dépendance du projet (Maven / Classpath)

Il faut que les librairies Gin et Guice soient dans les dépendances Maven / Classpath du projet. Pour cela, il faut ajouter dans le pom.xml les dépendances suivantes :

     <dependency>
          <groupId>com.googlecode.gwt.inject</groupId>
          <artifactId>gin</artifactId>
          <version>1.0</version>
          <scope>provided</scope>
      </dependency>
      <dependency>
          <groupId>com.google.inject</groupId>
          <artifactId>guice</artifactId>
          <version>2.0</version>
          <scope>provided</scope>
      </dependency>

Le scope provided est utilisé pour que ces librairies ne soient pas embarquées dans le war généré par le projet qui les utilise.

Ajout de Gin en dépendance du projet (GWT)

Il faut que les librairies Gin et Guice soient dans les dépendances GWT du projet. Pour cela, il faut ajouter dans le Module.gwt.xml la dépendance suivante :

<module>
...
<inherits name="com.google.gwt.inject.Inject"/>
...
</module>

Création d’un injecteur de dépendances Gin

Pour pouvoir récupérer les modules dont les dépendances sont gérées par Gin, il suffit de créer une interface annotée dans laquelle on défini pour chacun des modules de l’application une méthode permettant de le récupérer.

@GinModules(ModuleInjectorModule.class)
public interface ModuleGInjector extends Ginjector {
  AppPresenter getAppPresenter();
  FooterPresenter getFooterPresenter();
  DisplayFactory getDisplayFactory();
  GlobalLayout getGlobalLayout();
}

Par exemple, dans notre cas, nous avons besoin de Gin pour gérer les dépendances du Presenter AppPresenter, nous avons donc défini une méthode getAppPresenter qui retourne un AppPresenter. En appelant cette méthode, Gin va s’occuper d’instancier automatiquement le Presenter en lui injectant les dépendances telles qu’elles sont configurées dans le module de configuration du binding Gin.

L’interface est annotée avec l’annotation @GinModules qui prend en paramètre la class du module de configuration du binding Gin.

Création du module configuration du binding Gin

Gin a besoin de connaitre la configuration du binding des modules de l’application. Pour cela, il faut créer une classe dans laquelle les liaisons entre les modules sont définies.

public class ModuleInjectorModule extends AbstractGinModule {
  @Override
  protected void configure() {
      //Say to GIN to bind the Display to the FooterView and
      //use a Singleton scope for the application
      //This singleton view will be injected to the MainLayout
      bind(FooterPresenter.Display.class).to(FooterView.class).in(Singleton.class);
      bind(GlobalLayout.class).in(Singleton.class);
      bind(AppPresenter.Display.class).to(GlobalLayout.class).in(Singleton.class);
  }

  /**
   * This Provider is used by GIN to inject all HandlerManager field
   * Use the legacy GlobalEventBus.get()
   */
  @Provides
  HandlerManager getGlobalEventBus() {
      return GlobalEventBus.get();
  }
}

Par exemple, dans notre cas, le FooterPresenter définie sa vue via l’inner classe Display qui est implémentée par la FooterView et cette classe n’a qu’une seule instance dans l’application. Ce qui se traduit par la ligne suivante :

bind(FooterPresenter.Display.class).to(FooterView.class)
                                  .in(Singleton.class);

Il est également possible de fournir a Gin des classes dont il ne gère pas le cycle de vie. Par exemple, dans notre cas, comme nous avons progressivement migré vers Gin, et pendant cette phase de migration, certaines classes devaient être a la fois utilisée par Gin et également être utilisée par le système d’injection de dépendance manuel utilisé précédemment.

C’est le cas pour notre Bus d'évènement par exemple, c’est pourquoi nous avons ajouté cette méthode :

  @Provides
  HandlerManager getGlobalEventBus() {
      return GlobalEventBus.get();
  }

Instancier le Gin Injector dans l’application et l’utiliser

Pour utiliser l’injecteur de dépendance Gin, il suffit d’ajouter dans le Module GWT de l’application un attribut static puis de l’utiliser comme suit:

public class Module implements EntryPoint {
   private final ModuleGInjector injector =  GWT.create(ModuleGInjector.class); 
   public void onModuleLoad() {
      ...
      MyPresenter myPresenter = injector.getMyPresenter();
      ...
   }
}

Il est nécessaire de faire ce GWT.create une fois dans l'application pour que GIN puisse être appeler au moins une fois.

Aller plus loin avec Gin

Gin permet de binder les classes GWT utilisant le deferred binding comme les interfaces des constantes et messages pour l'internationalisation, ce qui facilite leur utilisation car c’est Gin qui s’occupera de les instanciers pour nous.

Gin permet également de binder les services RPC GWT pour faciliter leur utilisation.

Gin permet d’utiliser des fonctionnalités de Guice dans GWT mais toutes ne sont pas accessibles à cause des limitations de GWT.

Voir le tableau récapitulatif des fonctionnailités disponibles

Voir la documentation de Guice pour plus d’informations

mardi 1 mars 2011

Retour d'expérience d'un projet GWT : 6 Ecrire des tests unitaires avec Mockito (6/8)

Le contexte:

Le projet a pour but d'améliorer l'ergonomie et l'usabilité du back office d’un produit en cours de développement réalisé en collaboration avec SFEIR

Dans le cadre de ce projet, la version de GWT utilisée passe de la 1.7.1 à la 2.0.3 afin de bénéficier des nouvelles fonctionnalités offertes par cette version. De nouveaux pattern de développement ont également été mis en place au cours de ce projet.

Voici une série d'article sur les nouveautés de GWT 2, les choix d'architectures, et bibliothèques utilisés qui font part de notre retour d'expérience sur le sujet. Ces articles ont été écris par David Aboulkheir, Patrice de Saint Steban et Cyril Lakech

  1. Nouveautés de GWT 2.0
  2. UiBinder, enfin une forte collaboration entre le designer et le développeur
  3. Intégration facile de maquette Html en GWT 2
  4. Architecture Modèle-Vue-Presenteur
  5. Implémentation Modèle-Vue-Présenteur
  6. Ecrire des tests unitaires avec Mockito
  7. Mise en place de Gin sur le projet
  8. Internationalisation

Tester une application GWT avec une architecture MVP permet de pouvoir écrire des tests JUnit en java pure sans utiliser l'implémentation GWT de JUnit qui démarre un serveur web et un navigateur pour lancer les tests unitaires. Ce qui prend du temps si l'on doit exécuter ses tests à chaque modification de l'application.

Pour qu'il soit possible d'écrire des tests en Java pure, il faut que le Présenter ne possède aucune référence à des classes du package Ui de GWT. C'est à dire toutes les classes qui étendent Widget et qui permettent d'afficher des informations, ce qui logiquement doivent être définie dans la vue. Il faut ainsi écrire un contrat entre le présenteur et la vue sous la forme d'une interface Display. Ce contrat ne doit posséder que des paramètres et valeurs de retour définie par une interface ou pouvant être utiliser par Java (pas de TextBox getLogin() mais un HasValues<String> getLogin() ou toute autre classe faisant référence à des Widget GWT)

De même, il n'est pas possible d'utiliser GWT.create() autrement dit, pour instancier les services RPC, les classes de Constantes pour l'internationalisation, etc ...

Il est donc nécessaire de faire ses instanciations en dehors du Presenter, et ne prendre en paramètre que l'interface de ceux-ci. Il deviens ainsi possible de créer une version spécifique pour les tests, en règle générale, on va écrire des classes qui implémentent les interfaces avec du code minimal pour réaliser nos tests (getter, setter, hash maps pour enregistrer les données, calcule simple ...). Ces classes doivent être maintenue lors que l'on modifie l'application, et peuvent avoir des bugs qui empêche le test de fonctionner correctement.

C'est là que Mockito prend toute sa puissance, en implémentant automatiquement ses interfaces et en ajoutant le comportement de ceux-ci.

Il faut ajouter la dépendance à Mockito à Maven :

     <dependency>
         <groupId>junit</groupId>
         <artifactId>junit</artifactId>
         <version>4.8.1</version>
         <scope>test</scope>
     </dependency>
     <dependency>
         <groupId>org.mockito</groupId>
         <artifactId>mockito-all</artifactId>
         <version>1.8.5</version>
         <scope>test</scope>
     </dependency>

Les tests doivent être écrit avec JUnit 4 et être exécutés par le Runner spécifique de Mockito (si on veux utiliser les annotations automatique permettant de créer les mock et les arguments captor)


Mockito

Mockito est un générateur automatique de doublures. Il fonctionne sur le mode de l'espion :

  • On crée les mocks;
  • On décrit leur comportement
  • Ensuite, à l'exécution, toutes les interactions avec les mocks sont mémorisés ;
  • à la fin du test, on interroge les mocks pour savoir comment ils ont étés utilisés.

Toutes les méthodes de mockito sont des méthodes static de la classe Mockito, il est donc nécessaire d'importer statiquement ses méthodes pour simplifier l'écriture :

import static org.mockito.Mockito.*;

On crée un mock soit avec la méthode mock :

MonInterface mock = mock(MonInterface.class);

soit en utilisant l’annotation @Mock:

@Mock
MonInterface mock;

Il n'est pas nécessaire que MonInterface soit une interface, il est possible de mocker une classe (mock(Widget.class) permet d'utiliser une classe GWT dans le test dans que cela pose de problème, une version vide ayant les mêmes méthodes publique est créé par mockito)

On décrit ensuite le comportement attendu de notre mock :

when(mock.maMethode()).thenReturn(42);

On appelle les tests, qui font des interactions avec notre mock, et enfin on vérifie ses interactions

verify(mock).maMethode();

Cette dernière ligne vérifie que la méthode maMethode() a bien été appelé durant le test.


Définir le comportement d'un mock

Pour définir, le comportement d'un mock, on utilise when() auquel on passe en paramètre l'appel que l'on veux définir, c'est à dire l'appel de la méthode avec les paramètres que l'on doit répondre :

interface Add {
    public int add(int a, int b);
}

Si on veux émuler le comportement de notre méthode add qui additionne les deux paramètres, on va donner les résultats pour des paramètres données :

Add mockAdd = mock(Add.class);
when(mockAdd.add(1, 1)).thenReturn(2);
when(mockAdd.add(5, 7)).thenReturn(12);

Ainsi si dans notre test, on appelle mock.add(1, 1), le retour sera 2.

On peux donner plusieurs valeurs de retour qui seront retourné dans l'ordre :

Par exemple, si on veux simulé les retours de la methode random, on fera :

when(randMock.random()).thenReturn(5, 18, 86, 42);

Ainsi le premier appel de random() retournera 5, et le quatrième appel retournera 42 (ainsi que tous les suivants).

On peux aussi simuler l'envoie d'une exception avec la method thenTrow() :

when(mock.div(anyIn(),0)).thenTrow(new Exception("Division par zéro));

Qui lancera alors une exception lors que le deuxième paramètre est un zéro.


Vérifier les appels aux mock

La méthode verify() qui prend en parametre permet de vérifier qu'une méthode à bien été appeler :

verify(mock).mustCall(1)

Cette ligne permet de vérifier que la méthode mustCall a bien été appelé lors du test, et de plus permet de vérifier que le paramètre est bien 1.

On peux tester le nombre de fois où la methode a été appellé :

verify(mock, times(5)).add(anyString());

On vérifie ici que la méthode add a été appellé 5 fois avec une chaînes de caractères en paramètre.

Si on veux tester plus de choses sur les arguments, il est possible de les capter (les enregistrer) pour ainsi faire des tests plus poussé dessus :

ArgumentCaptor<User> captor = ArgumentCaptor.forClass(User.class);
verify(userservice).login(captor.capture());
assertEquals("login", captor.getValue().getLogin());

On peux aussi vérifier qu'il n'y a pas eu d'interaction avec un mock

verifyZeroInteractions(mock);


Utilisation avec GWT

Si on veux tester qu'un événements a bien été envoyé par GWT, on utilise les mock et les ArgumentCaptor :

@Captor
ArgumentCaptor<ValueChangeEvent<String>> valueChangeEventCaptor;

@Mock
ValueChangeEventHandler<String> handler;

@Test
void testValueChangeEventHandler() {
    TestClassWithValueChangeHandlers test = new TestClassWithValueChangeHandlers();
    //On s'enregistre dans l'eventBus à l'évènement click
    test.addValueChangeHandler(handler);
    //On appelle la methode à tester qui envois notre évènement ValueChange();
    test.setValue("newValue");
    //On vérifie que celui-ci a bien été appellé
    verify(handler).onValueChange(valueChangeEventCaptor.capture());
    //On test que la nouvelle valeur est bien danc l'object event :
    assertEquals("newValue",             valueChangeEventCaptor.getValue().getValue());
}

Références Documentation de Mockito

lundi 21 février 2011

Concours stickers 3 ans du ParisJug : Les votes sont ouverts

juggyannif02.jpg Ca y est : les inscriptions au concours sont closes.
Une quinzaine d'artistes ont répondus à l'appel. Merci à eux !
Ils ont été particulièrement productifs puisqu'ils ont totalisés plus de 20 créations même s'ils ne pouvaient en faire concourir qu'une seule par personne.

Votez dès maintenant pour votre image préférée et repartez lundi avec un exemplaire des stickers les plus plébiscités !

Ca se passe par ici


Pour voter vous devez vous authentifier avec un compte google/gmail.
Votre adresse ne sera pas revendue à des spameurs, c'est promis, c'est juste pour éviter un peu les abus sur les votes.
Les votes s'arrêtent ce jeudi 24/02 à minuit.

Si vous nous avez envoyé votre participation et que vous ne la voyez pas sur le site, merci de nous retransférer votre email original le plus rapidement possible à sticker@sfeir.com

A lundi pour siffler en célébrant les 3 ans du ParisJug avec SFEIR

jeudi 17 février 2011

Concours stickers 3e anniversaire du Paris JUG

parisjug

Le Paris JUG fêtera ses 3 ans le 28 février. Si vous n'êtes pas encore inscrit, il reste encore 5 places à l'heure où j'écris ces lignes, dépêchez vous ! Info et programme ici :
http://www.parisjug.org/xwiki/bin/view/Meeting/20110228 Comme chaque mois des SFEIRiens seront présents mais nous souhaitions marquer le coup pour cet anniversaire.

Nous les geeks on aime bien les goodies donc on a pensé organiser un petit concours :
Donnez nous des idées de stickers funs sur des thèmes geeks qui nous parlent comme par exemple : le parisjug, java, le cloud, le métier de développeur de manière générale...
Vous pouvez proposer soit carrément une image, soit juste une phrase (pas trop longue hein, faut que ca rentre sur un autocollant :))

On ne pourra pas imprimer toutes les propositions donc on va faire un petit vote public sur internet la semaine avant avec quelques lots pour rajouter un peu de piment :

  • 3 stickers différents seront édités et distribués pendant la pause
  • Pour le 1er : un smartphone Android Nexus S
  • Pour le 2e et le 3e : un abonnement d'un an à Parleys

Pour les modalités :

  • Tout le monde peut participer, sfeirien ou non.
  • Vous pouvez envoyer plusieurs créations mais vous devez en choisir 1 seule pour le concours.
  • Seuls les candidats présents à la pause le 28 pourront gagner les lots puisque c'est à ce moment là que se fera la remise des lots.

Si vous voulez proposer une image :

  • Format : JPEG ou PNG
  • Taille : 1063 x 413 pixels (= 90mm x 35mm en 300dpi). Ne pas écrire trop au bord, il se peut qu'il y ai un peu de perte à la découpe.

Planning :

  • Début : dès maintenant
  • Date limite pour l'envoi des propositions : Lundi 21/02/2011 à minuit
  • Votes : du 22 dans la journée au 24/02 à minuit
  • Le 28 à la pause de 19h30 : remise des lots sur le stand SFEIR et distribution des stickers

Si vous êtes fiers de vos créations vous pouvez les partager sur twitter avec le hashtag #stickersfeir mais comme on est comme vous on aime pas trop se faire pourrir la timeline, c'est l'envoi par email à l'adresse sticker@sfeir.com qui compte réellement pour le concours !
On fera une page spéciale pour le vote des stickers, l'adresse sera communiquée ultérieurement.

A bientôt à la cité internationale

@alexandre_t pour l'équipe sfeir

jeudi 10 février 2011

Retour d'expérience d'un projet GWT : 5 Implémentation Modèle-Vue-Présenteur (5/8)

Le contexte:

Le projet a pour but d'améliorer l'ergonomie et l'usabilité du back office d’un produit en cours de développement réalisé en collaboration avec SFEIR

Dans le cadre de ce projet, la version de GWT utilisée passe de la 1.7.1 à la 2.0.3 afin de bénéficier des nouvelles fonctionnalités offertes par cette version. De nouveaux pattern de développement ont également été mis en place au cours de ce projet.

Voici une série d'article sur les nouveautés de GWT 2, les choix d'architectures, et bibliothèques utilisés qui font part de notre retour d'expérience sur le sujet. Ces articles ont été écris par David Aboulkheir, Patrice de Saint Steban et Cyril Lakech

  1. Nouveautés de GWT 2.0
  2. UiBinder, enfin une forte collaboration entre le designer et le développeur
  3. Intégration facile de maquette Html en GWT 2
  4. Architecture Modèle-Vue-Presenteur
  5. Implémentation Modèle-Vue-Présenteur
  6. Ecrire des tests unitaires avec Mockito
  7. Mise en place de Gin sur le projet
  8. Internationalisation

Voyons maintenant comment créer une architecture Model-View-Presenter.

Le présenteur doit étendre la classe Presenter qui fournit des fonctionnalités de base à notre application. Comme l'accès au global event bus et à la factory de vue.

public class TestPresenter extends Presenter {

    /**
     * Interface represents the test view
     */
    public interface Display extends DisplayView {
        public void setUserName(String username);

        public HasClickHandlers getUserNameClickHandler();
    }
   
    // fields
    /** View */
    Display display;
   
    // constructor
    public TestPresenter(Display display, HandlerManager globalEventBus, DisplayFactory factory) {
        super(globalEventBus, factory);
        this.display = display;
       
        bind();
    }
   
    // Bind all event
    private void bind(){
                getGlobalEventBus().addHandler(
            OnUserLoggedEvent.getType(),
            new  OnUserLoggedEvent.Handler() {
            @Override
            public void onUserLogged(OnUserLoggedEvent event) {
                display.setUserName(event.getUser()
                        .getLogin());
            }
        });
        display.getUserNameClickHandler().addClickHandler(
            new ClickHandler() {
                public void onClick(ClickEvent event) {
                    getGlobalEventBus().fireEvent(
                        new OnUserLogout());
            }
        }       
    }

    public void go(Placer panel) {
        panel.setView(display);
    }
}

Nous avons ici l'architecture de base d'un presenter :

  • Le constructeur prend en paramètre toutes les interfaces qu'il va utiliser :
    • sa vue interfacé par l'interface interne au presenter : Display
    • le global event bus (Bus d'évènement global à l'application pour la communication entre les presenter)
    • la factory de vue (utilisée pour instancier les vues des sous-presenters)
    • Éventuellement des services RPC
  • la méthode bind() qui s'occupe d'enregistrer le présenteur aux évènements globaux de l'application (ici le login de l'utilisateur) et aux évènements de la vue (ici le click sur le label affichant le login de l'utilisateur).
  • la méthode go(), qui permet au présenteur de s'afficher
    • la classe Placer est une abstraction d'un Panel GWT, pour être complètement indépendant de GWT, cette interface possède une simple méthode setView qui prend en paramètre un DisplayView. L'interface DisplayView qui doit être étendu par toutes les vues demande juste d'implémenter la méthode asWidget() qui permet de retourner la vue (this) en tant que Widget GWT.

Créons maintenant la vue, en utilisant UiBinder :

public class TestView extends Composite implements TestPresenter.Display {

    private static TestViewUiBinder uiBinder = GWT
            .create(TestViewUiBinder.class);

    interface TestViewUiBinder extends
        UiBinder<Widget, TestView> {}

    @UiField Label label;
   
    public TestView() {
        initWidget(uiBinder.createAndBindUi(this));
    }

    @Override
    public HasClickHandlers getUserNameClickHandler() {
        return label;
    }

    @Override
    public void setUserName(String username) {
        label.setText(username);
    }

    @Override
    public Widget asWidget() {
        return this;
    }
}

Avec le fichier TestView.ui.xml associé :

<!DOCTYPE ui:UiBinder SYSTEM "http://dl.google.com/gwt/DTD/xhtml.ent">
<ui:UiBinder xmlns:ui="urn:ui:com.google.gwt.uibinder"
    xmlns:g="urn:import:com.google.gwt.user.client.ui">
    <g:Label ui:field="label" />
</ui:UiBinder>

Cette classe étend donc notre Display du présenteur et accède aux composant GWT.

On peux ainsi tester facilement le code de notre présenteur car il ne possède aucune adhérence à GWT.

Nous avons choisi dans un premier temps de se passer de ces framework et d’appliquer les pattern MVP en suivant uniquement les pratiques conseillées par Google. Maintenant que nous avons une première expérience avec le développement MVP, nous pouvons penser à utiliser un de ces framework pour bénéficier de leurs utilitaires comme la gestion de l’historique, du pattern action etc. Si nous devions en choisir un à ce jour, ce serait gwt-platform qui est issu de gwt-presenter et gwt-dispatch.

lundi 24 janvier 2011

Retour d'expérience d'un projet GWT : 4 Architecture Modèle-Vue-Presenteur (4/8)

Le contexte:

Le projet a pour but d'améliorer l'ergonomie et l'usabilité du back office d’un produit en cours de développement réalisé en collaboration avec SFEIR

Dans le cadre de ce projet, la version de GWT utilisée passe de la 1.7.1 à la 2.0.3 afin de bénéficier des nouvelles fonctionnalités offertes par cette version. De nouveaux pattern de développement ont également été mis en place au cours de ce projet.

Voici une série d'article sur les nouveautés de GWT 2, les choix d'architectures, et bibliothèques utilisés qui font part de notre retour d'expérience sur le sujet. Ces articles ont été écris par David Aboulkheir, Patrice de Saint Steban et Cyril Lakech

  1. Nouveautés de GWT 2.0
  2. UiBinder, enfin une forte collaboration entre le designer et le développeur
  3. Intégration facile de maquette Html en GWT 2
  4. Architecture Modèle-Vue-Presenteur
  5. Implémentation Modèle-Vue-Présenteur
  6. Ecrire des tests unitaires avec Mockito
  7. Mise en place de Gin sur le projet
  8. Internationalisation

Le MVP ou modèle vue présenteur est un modèle de conception qui a les objectifs suivants:

  • Découplage : le découpage en couche permet de construire des applications modulaires compatibles avec les contraintes de dépendances du chargement à la demande
  • Testabilité : Chaque classe destinée a être testée out-of-the-box possède une interface qui permettra de bouchonner certains comportements.
  • Optimisation des appels de service
  • Centralisation des erreurs
  • Gestion de l'historique
  • Anticipation, sur les évolutions du web comme HTML5...

Les 3 concepts du MVP sont :

  • Le modèle, les données de l'application, souvent issues de la base de données, il est destiné à être manipulé par l'UI. Il n'a pas d'adhérence avec les autres classes MVP, il est isolé.
  • La vue, la partie de la page web affichée dans l'interface utilisateur, qui se compose des widgets et des conteneurs de widgets.
  • Le présenteur, le controleur du pattern MVC sauf qu'il rend totalement étanche les échanges entre la vue et le modèle (alors que MVC permet à la vue de référencer directement ses modèles). Il porte leur le comportement de l'application.

MVP

La vue est passive et c'est le présenteur qui s'occupe des traitements et de la logique évenementielle. La vue et le présenteur partagent un contrat grâce à une interface. Pour que le contrôleur puisse être correctement tester avec JUnit sans utiliser la version de GWT qui utilise HtmlUnit pour lancer les tests, il faut que le contrôleur ne possède que du code Java hors code GWT (composant GWT, GWT.create(), Internationalisation ...).

Le MVP s'appuie sur plusieurs briques dont certaines sont facultatives :

  • Un gestionnaire d'évènement ou EventBus qui a pour but de découpler les différentes couches qui communiquent par l'émission d'évènements.
  • Un moteur d'IOC pour faciliter la création et la liaison des différents object. (facultatif)
  • Le pattern action pour simplifier les appels de services et offrir une meilleur testabilité. (facultatif)

Pour comprendre au mieux, le fonctionnement concret du modèle de conception MVP, il faut lire les 2 articles de Google suivants :

Un projet d'exemple est proposé par Google

Schéma de fonctionnement de GWT avec MVP sans UiBinder:

MVP ss UiBinder

Schéma de fonctionnement de GWT avec MVP avec UiBinder:

MVP avec UiBinder

Il existe également d'autres articles expliquant le fonctionnement de MVP :

Il existe un projet d'exemple illustrant le fonctionnement du MVP : http://code.google.com/p/gwt-mvp-sa...

Dans le livre "la programmation GWT2" de Sami Jaber, un article détaille le fonctionnement de MVP en s'appuyant sur une framework existant : http://code.google.com/p/gwt-presenter/

Il existe de nombreux framework MVP pour GWT comme gwt-presenter, gwt-platform ou mvp4g.

Dans GWT 2.1, GWT a introduit un framework MVP, cependant celui-ci décrit le principe.

lundi 3 janvier 2011

Retour d'expérience d'un projet GWT : 3 Intégration facile de maquette Html en GWT 2 (3/8)

Le contexte:

Le projet a pour but d'améliorer l'ergonomie et l'usabilité du back office d’un produit en cours de développement réalisé en collaboration avec SFEIR

Dans le cadre de ce projet, la version de GWT utilisée passe de la 1.7.1 à la 2.0.3 afin de bénéficier des nouvelles fonctionnalités offertes par cette version. De nouveaux pattern de développement ont également été mis en place au cours de ce projet.

Voici une série d'article sur les nouveautés de GWT 2, les choix d'architectures, et bibliothèques utilisés qui font part de notre retour d'expérience sur le sujet. Ces articles ont été écris par David Aboulkheir, Patrice de Saint Steban et Cyril Lakech

  1. Nouveautés de GWT 2.0
  2. UiBinder, enfin une forte collaboration entre le designer et le développeur
  3. Intégration facile de maquette Html en GWT 2
  4. Architecture Modèle-Vue-Presenteur
  5. Implémentation Modèle-Vue-Présenteur
  6. Ecrire des tests unitaires avec Mockito
  7. Mise en place de Gin sur le projet
  8. Internationalisation

Exemple d'intégration d'un composant d'une page Web dans un projet GWT 2:

Nous devons intégrer un nouveau composant qui nous est livré sous la forme de maquette HTML qui se compose de fichiers HTML, d'image et éventuellement de fichier CSS.

Voici le fichier HTML fourni, il contient le code HTML et le style CSS utilisé:

<style>
<!--

* {
    font-family:calibri;
    font-size:12px;
}

.menu {
    padding: 0px;
    background: url('bgmenu.gif') repeat-y right top white;
    display:block;
    color:#1783c4;
    width:250px;
    height:100%;
}

.menu h2{
    font-family:calibri;
    font-size:18px;
    background: url('menutop.gif') no-repeat bottom left;
    width:250px;
    padding-bottom:10px;
    margin:0px;
}

.menu .close{
    float:right;
    margin:4px;
    margin-right:7px;
}

.menu textarea {
    width:240px;
    min-height:300px;
    border:1px solid #dddddd;
    background: #f6f6f6;
    margin-top:20px;
    margin-bottom:5px;
}

.pages{
    padding:3px 7px 7px 7px;
}

.pages.selected{
     background: url('menubottom.gif') no-repeat bottom left #f5f5f5;
}

.pages .close{
    float:right;
}

.pages a.element{
    color:black;
    font-size:13px;
    font-weight:bold;
    margin-bottom:3px;
    text-decoration:none;
    display:block;
}

.pages a.element:hover{
    text-decoration:underline;
    background:url('croixnoire.gif') 223px 3px no-repeat;
}

.pages .close{
    float:right;
    width:13px;
    height:11px;
}

.right {float:right;}

a.bouton {
    color:white;
    font-size:12px;
    font-weight:bold;
    text-transform:uppercase;
    margin-right:10px;
    padding:2px 5px;
    text-decoration:none;
    font-family:calibri;
    border:1px solid #909090;
    text-align:center;
}

a.bouton.bleu {
    background:#0099cb;
}

a.bouton.bleu:hover {
    background:#0fb3e9;
}

a.bouton.vert {
    background:#4ba600;
}

a.bouton.vert:hover {
    background:#5cc803;
}
-->
</style>
<div class="menu">
      <div class="close"><a href="#"><img src="croix.gif" border="0"></a></div>
     <h2>Jean-Pierre Dupont</h2>   
     <div class="pages selected">
       <a href="#" class="element"><img src="1ptrans.gif" border="0" class="close">Ma page personnel (0)</a>
      </div>
     <div class="pages">
        <a href="#" class="element"><img src="1ptrans.gif" border="0" class="close">Autre page (0)</a>
     </div>
    <br/>
     <a href="#" class="bouton bleu">Créer une nouvelle page</a>
     <textarea>
     Ici je peux mettre des notes
     </textarea>
     <a href="#" class="bouton vert right">Enregistrement</a>
</div>

Nous créons un nouvel élément UiBinder que nous appelons HomePageMenu :

UIBinder create

On modifie le fichier HomePageMenu.gwt.xml comme suit:

  • On ajoute les entêtes nécessaires pour UiBinder
  • On modifie la balise <style /> en <ui:style />
  • On remplace la div principale par une balise <g:HTMLPanel />
  • Les libellés que l’on souhaite rendre dynamiques sont remplacés par des balises <g:InlineLabel />, les liens <a /> par des <g:Anchor /> et les <textarea /> par des <g:TextArea />
  • Les nom des styles utilisés sont modifiés, en lieu et place de “monStyle” on utilise "{style.monStyle}"
<!DOCTYPE ui:UiBinder SYSTEM "http://dl.google.com/gwt/DTD/xhtml.ent">
<ui:UiBinder xmlns:ui="urn:ui:com.google.gwt.uibinder"
     xmlns:g="urn:import:com.google.gwt.user.client.ui">
     <ui:style>
        * {
            font-family: calibri;
               font-size: 12px;
        }
       
        .menu {
               padding: 0px;
            display: block;
             color: #1783c4;
            width: 250px;
            height: 100%;
        }
       
        .menu h2 {
            font-family: calibri;
            font-size: 18px;
            background: url('module/images/homepage/menutop.gif')
                 no-repeat bottom left;
            width: 250px;
             padding-bottom: 10px;
            margin: 0px;
        }
      
        .menu .close {
            float: right;
             margin: 4px;
            margin-right: 7px;
        }
        
        .menu textarea {
            width: 240px;
                min-height: 300px;
            border: 1px solid #dddddd;
               background: #f6f6f6;
            margin-top: 20px;
             margin-bottom: 5px;
        }
       
        .pages {
               padding: 3px 7px 7px 7px;
        }
       
         .pages.selected {
            background: url('module/images/homepage/menubottom.gif')
                 no-repeat bottom left #f5f5f5;
        }
       
         .pages .close {
            float: right;
        }
       
        .pages a.element {
            color: black;
            font-size: 13px;
            font-weight: bold;
            margin-bottom: 3px;
            text-decoration: none;
             display: block;
        }
       
        .pages a.element:hover {
            text-decoration: underline;
             background: url('module/images/homepage/croixnoire.gif') 223px
                3px no-repeat;
        }
       
         .pages .close {
            float: right;
            width: 13px;
            height: 11px;
        }
       
         .right {
            float: right;
        }
       
         a.bouton {
            color: white;
            font-size: 12px;
            font-weight: bold;
            text-transform: uppercase;
            margin-right: 10px;
            padding: 2px 5px;
            text-decoration: none;
            font-family: calibri;
            border: 1px solid #909090;
             text-align: center;
        }
       
         a.bouton.bleu {
            background: #0099cb;
        }
        
        a.bouton.bleu:hover {
            background: #0fb3e9;
        }
       
        a.bouton.vert {
             background: #4ba600;
        }
       
         a.bouton.vert:hover {
            background: #5cc803;
        }
    </ui:style>
    <g:HTMLPanel styleName="{style.menu}">
       <div class="{style.close}">
            <a href="#">
               <img src="module/images/homepage/croix.gif" border="0" />
            </a>
        </div>
       <h2><g:InlineLabel  ui:field="userLabel">Jean-Pierre Dupont</g:InlineLabel></h2>
        <div class="{style.pages} {style.selected}">
            <a href="#" class="{style.element}">
                <img src="module/images/homepage/1ptrans.gif" border="0"
                    class="{style.close}" />
                Ma page personnel(0)
            </a>
        </div>
         <div class="{style.pages}">
            <a href="#" class="{style.element}">
                <img src="module/images/homepage/1ptrans.gif" border="0"
                    class="{style.close}" />
                Autre page (0)
            </a>
        </div>
        <br />
        <g:Anchor ui:field="newButton" href="#" styleName="{style.bouton} {style.bleu}">
            Créer une nouvelle page
        </g:Anchor>
         <g:TextArea ui:field="noteTextArea">
            Ici je peux mettre des notes
         </g:TextArea>

        <g:Anchor ui:field="saveNoteButton" href="#" styleName="{style.bouton} {style.vert} {style.right}">
            Enregistrer
        </g:Anchor>
     </g:HTMLPanel>
</ui:UiBinder>


Puis on modifie la classe associée de la manière suivante:
/**
*
*/
package module.client.homepage.menu;

import com.google.gwt.core.client.GWT;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.Widget;

/**
*
*
*/
public class HomePageMenu extends Composite {

    private static HomePageMenuUiBinder uiBinder = GWT
             .create(HomePageMenuUiBinder.class);

    interface HomePageMenuUiBinder extends UiBinder<Widget, HomePageMenu> {
    }

    //Composant GWT que l'on veux utiliser lié avec l'attribut ui:field=""
    @UiField InlineLabel userLabel;
    @UiField Anchor newButton;
    @UiField TextArea noteTextArea;
    @UiField Anchor saveNoteButton;

    public HomePageMenu() {
         initWidget(uiBinder.createAndBindUi(this));
    }

    @UiHandler("newButton")
    public void onNewClick(ClickEvent event){
        //Création d'une nouvelle page
    }
   
    @UiHandler("saveNoteButton")
    public void onNewClick(ClickEvent event){
        saveNote(noteTextArea.getText());
    }

    public void setUserName(String userName){
        userLabel.setText(userName);
    }

}

Comme on a pu le voir, il est très simple d’intégrer des maquettes HTML/CSS en GWT abec UiBinder, ce qui n’était pas le cas avant la version 2 de GWT. Cela permet d’améliorer la collaboration entre le Web Designer et le développeur puisqu’ils utilisent un langage commun le XML/HTML pour réaliser les écrans. Cela permet également au Web Designer, après une rapide prise en main des outils type SVN/Eclipse, de pouvoir intervenir sur le design de l’application directement sans aucune aide du développeur.

Dans notre cas, on a eu l’agréable surprise de voir notre Web Designer créer des écrans GWT lui même, les modifier et même coder en java pour interagir avec l’IHM; modifier un style programmatiquement ou déclencher une action simple.

mercredi 15 décembre 2010

gwt-table-to-excel

Présentation d’un nouveau projet Open Source.

Lire la suite...

dimanche 28 novembre 2010

1ère réunion du PAUG

C’est ce mardi 23 novembre que s’est tenu la 1ère réunion du Paris Android User Group. Si vous n’avez pas entendu parler de l’organisation de cet événement, c’est normal : le nombre de place étant limité à 40. Si il y avait eu beaucoup de communication sur les blog/forum, il n’y aurait pas eu de place pour tout le monde. Le thème de la soirée est "L'ergonomie des applications Android et 2 présentations seront faites.
La soirée commence par un rapide tour de présentation où chacun expose en quelques mots qui il/elle est et le but de leur présence ce soir. En grande majorité, les gens sont des développeurs Android mais aussi quelques curieux. qui viennent pour découvrir Android. Quant à la principale motivation à assister à la soirée, elle est tout simplement de venir à la rencontre de la communauté.

1ère partie

La première présentation commence avec Nicolas Chambrier, consultant/architecte chez Clever Age, qui vient nous parler de la mise en place de la Quick Action Popup.
Il y a quelques mois de cela, Google a réalisé pour Twitter un client Android. Cette application a marqué les esprits pour son aspect design et ergonomie. Alors qu’Android était critiqué pour ses interfaces “moches”, Twitter for Android arrive et se présente comme un modèle à suivre. Bien que Google ait annoncé qu’elle serait open-source, on attends toujours ….
Ce modèle suit 3 grands principes :
  • Action bar : A la place de la barre fine de titre, une barre plus épaisse qui contient les actions auxquelles l’utilisateur aura souvent accès. Pour les actions secondaires,  profitons du fait qu’un Android contrairement à un IPhone possède un bouton menu. Il y aussi la touche “search” qui est un peu particulière. Elle sert à indiquer à l’utilisateur qu’une recherche est disponible dans l’application. Il aussi recommandé de rendre cette recherche accessible depuis le bouton “Rechercher” du téléphone. A ce propos, attention car bien qu’il soit disponible sur beaucoup de modèle, ce bouton est facultatif. Le Samsung Galaxy S par exemple n’en possède pas.
  • Quick Action Popup : C’est un menu contextuel. Pourquoi pas de menu classique ? Tout d’abord parce que c’est plus joli mais aussi par ce que c’est moins encombrant et qu’au niveau visuel c’est moins stressant. Par contre, inconvénant : c’est plus complexe à mettre en place car ce type de composant n’existe pas dans Android.
  • Voir toutes les zones de l’application : l’écran d’accueil de l’application affiche les différentes Activity. La navigation se retrouve ainsi simplifiée.
Même si Google propose des idées sur la façon d’organiser, il ne donne pas vraiment de conseils sur la façon de faire. 
Il faut aussi savoir prendre du recul, joli est différent d’ergonomique : une application joli peut ne pas être du tout ergonomique et inversement. De plus nos applications n’ont pas forcément les mêmes besoins. Avant de se lancer, il faut bien réfléchir à comment rendre facile l’accès au différentes Activity et ne pas hésiter à crayonner sur papier ses idées.
La théorie, c’est bien beau, mais qu’en est il en pratique ? Nicolas nous livre maintenant un retour d’expérience sur son application : Horaire TER SNCF.
Elle a eu une refonte graphique complète en adoptant certaines des propositions. Cette refonte a été faite avec l’aide d’un designer qui comme pour une maquette web a réalisé un psd. Il ne restait plus qu’à découper cette maquette pour intégrer les images. Cette refonte adopte aussi les Quick Action Popup.
L’idée de ces Quick Action Popup, c’est de pouvoir mettre en place derrière un système de plug-in, pour étendre l’application, au cas où l’application remporte un immense succès. Le plus simple et de commencer par implémenter un simple menu. Il faut aussi se demander si c’est applicable, c’est vrai que c’est joli, mais si le reste est moche ….
Le mieux est de commencer par implémenter par un contextuel, quand c'est ok on fait les QAP.
Il faut aussi se demande sir c'est applicable. C'est joli mais si le reste est moche ….
Reste à voir comment implémenter. Et là, Google n’aide pas . Il faut donc aller voir du coté des initiatives personnelles :
Pourquoi donc a t-il ré-inventé la roue ?
En réalité, ce n’est pas si compliqué à développer, une cinquantaine de lignes de code suffisent. Une autre raison est le besoin spécifique par rapport au système de plug-in.
Il y tout de même des contraintes : il faut des compétences graphiques et le layout est limité à un background.
Pour intégrer, c’est très simple, il suffit d’incorporer le jar dans son projet. Un peu de code suffira pour faire le reste.
Il existe aussi d’autres ressource comme GreenDroid de Cyril Mottier.
Les slides :

2ème partie.

Pour cette partie, c’est Ludovic Perrier, qui nous parle d’Ergonomie et Design sur Android.
Cette présentation se veut très générale, il n’y aura pas de code, juste quelques règles et du bon sens que l’on acquière en développant. Tout cela est extrait de son livre en cours de rédaction. Ce dernier est co-écrit avec Cyril Mottier et devrait être disponible en fin d’année chez DigitBook.
Pourquoi faut il s’attacher au design et à l'ergonomie ?
Tout simplement pour rendre l’application utilisable. Il ne faut pas perdre de vue qu’il faut garder le look&feel Android. On voit parfois des applications qui possèdent un bouton retour tout comme une application Iphone... Ce qui ne sert à rien, un téléphone Android possède un bouton retour. L’utilisateur peut être exigeant et vouloir la même application que sous IPhone, il n’est pas toujours évident de lui faire comprendre que ce n’est pas ce qu’il de mieux pour l’application. 
Coté lanceur d’application, Google a très peu évolué ses roms depuis le début. Les roms constructeurs quant à elles proposent parfois un modèle assez différent. Samsung propose par exemple pour son Galaxy S un fonctionnement assez IPhoneLike avec son défilement horizontal.
Coté navigation matérielle, c’est assez hétérogène, on peut ainsi trouver : clavier physique, trackbal, trackpad, tactile, bouton physique optionnel,  ….
Ce sont des facteurs auxquels il faut prêter attention : le trackpack ne possède pas de diagonale, sous certains modèle la pression sur 2 boutons en même temps ne fonctionne pas, bouton “rechercher” est facultatif, les boutons ne sont pas toujours placé au même endroit. D’où l’importance dans ce dernier cas de laisser la possibilité à l’utilisateur de paramétrer les boutons si l’on s’en sert (cas d’un jeu).
Il est important d’aller à l'essentiel, de fournir uniquement ce dont l’utilisateur a besoin. Pour les autres besoins, il est toujours possible d’utiliser des boutons d’actions. Les splash screens, c’est pour iOS, sous Android, il existe d’autre technique, comme un chargement en arrière plan.
Et pour finir quelques règles d’or :
  • L’utilisateur doit pouvoir annuler une action, il faut donc faire des requêtes annulable. En effet, si l’on lance une action dont on ignore l’action, il est assez gênant ne peut pas pouvoir la stopper.
  • Le mode plein écran est déconseillé : il ne faut pas oublier que notre application n’est pas seule. Il faut penser au notification que laisse les autres comme par exemple : réception d’un mail ou d’un sms.
  • A partir d’environ 4 activités, un écran dashboard est conseillé, c’est moins fouillis.
  • Il faut adapter l’interface à chaque état (selectionné, pressé, focus, ….). Ainsi, l'utilisateur se rends mieux compte de ce qu’il fait.
  • Le scroll est une action assez lassante, il ne faut pas trop en abuser. Mieux vaut sectoriser si l’on a beaucoup de données.
  • Une application intuitive n’a pas besoin d’aide. Qui n’a jamais été agacé par le trombone de Word ? 
  • Il faut penser à M Tout le monde, se mettre à sa place. Il est difficile de faire une application qui plaît à tout le monde.
Les slides :
Comme beaucoup d’évènements, c’est autour d’un pot que se termine la soirée. Il sera la scène de diverses discussions comme la possibilité de créer un évènement semblable sur Lyon, les widget; ou encore des démonstrations d’une application de réalité augmentée de chez DiotaSoft.
Je profite de ce retour pour transmettre une demande. Les organisateurs sont à la recherche de salles de grande taille disponibles pour les prochaines rencontres. Si votre société/école en dispose, je vous laisse entrer en contact avec eux.
Pour ceux que ça intéresse, voici le google group :

mercredi 24 novembre 2010

"Innovation Web", la conférence qui monte (16 décembre)





L'association GET
 (G-EFREI Technologies) organise sa conférence pour la troisième année consécutive. 
Celle-ci se tiendra le 16 décembre prochain à l'EFREI.




Pour avoir une description détaillée de l'évenement vous pouvez vous rendre directement sur le site de l'association, dans la section conférence.

En cette journée du 16 décembre, vont se regrouper professionnels et étudiants qui partagent tous la même passion pour les technologies web et les outils de Google. 

Les portes sont ouvertes à tous, gratuitement, dans la limite des places disponibles (≈ 300) et sous condition d'être inscrit sur le formulaire : conf.g-e-t.fr.

Les thèmes & intervenants

  • Android et son avenir par Cyril MOTTIER, Créateur de GreenDroid

  • GWT 2.1 et mobilité par Salvador DIAZ, Développeur GWT chez SFEIR

  • Cloud computing et la monétisation par Didier GIRARD, Directeur Technique de SFEIR

Après la conférence, venez discuter et échanger autour d’un verre lors du cocktail organisé par GET.

Ateliers Android

Deux ateliers Android sont prévus après la conférence aux alentours de 18h, avec notamment Julien Del Rio de Frandroid, Stéphane Guérin, créateur de Appoke et toujours Cyril Mottier. 
Un atelier sera organisé pour les débutants et un autre pour les développeurs confirmés.

Pour plus d’infos : www.g-e-t.fr

Infos pratiques

  • Lieu : EFREI, 30-32 avenue de la république 94800 Villejuif

  • Métro : Villejuif Louis Aragon Ligne 7 (Terminus)

  • Début de la conférence : 13h30

  • Début des ateliers : 18h

  • Inscription gratuite et obligatoire : conf.g-e-t.fr

  • http://www.g-e-t.fr

jeudi 11 novembre 2010

Google Refine - Les dessous de l'outil pour manipuler des données

Google avait fait l'acquisition en juillet dernier de Metaweb, la société derrière le projet Freebase. Pour ceux qui ne connaissent pas Freebase, c'est un peu le wikipedia de la base de données : Des données tabulaires libres sur des sujets variés (Artistes, Films, Livres, etc.) enrichis et maintenus par la communauté. Metaweb avait également un outil opensource nommé "Freebase Gridworks" permettant de manipuler, corriger, transformer des données. Aujourd'hui Google annonce que Gridworks et renommé en Google Refine et passe en version 2.

3 vidéos de présentations sont proposés pour mieux comprendre comment fonctionne Refine. Elles sont visible ici. On y découvre les fonctionnalités principales de Refine qui sont : corriger les données (fusionner certains types, corriger les valeurs abérantes), transformer (par exemple : découper des chaines de caractères pour créer des nouvelles colonnes), enrichir (en appellant des webservices comme googlemap pour geolocaliser, freebase, ...)

Les sources du projet sont sur google code.

Le produit peut être exécuté sur sa machine. Un simple exécutable qui, une fois lancé, démarre un serveur web. Refine s'utilise donc ensuite depuis un simple navigateur web.

Un coup d'oeil rapide au code nous permet de voir quelques unes des technologies utilisées :

  • LessCss et Lessen : Un préprocesseur CSS très à la mode qui lui rajoute quelques élements très utiles comme les variables, l'imbrication, etc.
  • Jetty 6, qu'on ne présente plus
  • Smilie Butterfly Un framework web écrit en Java. Il a été créé par Stefano Mazzocchi, créateur de Apache Cocoon. Ce monsieur a aussi contribué à quelques JSR (JSR 53 - Servlet, 63 - JAXP, et 170 - Java Content Repository). Il travaillait chez Metaweb au moment du rachat et est donc maintenant chez google (oui encore un :-D ) et travaille sur Refine. Les particularités du framework sont : pensé pour créer des applis web modulaires ; bien qu'écrit en java, fait usage de javascript à la fois sur le client et sur le serveur via Rhino.

Sur le navigateur :

  • jquery avec les plugins jquery ui et event stack

Coté build et outils :

  • Ant
  • Des fichiers .projet, .gitignore qui trainent qui indique l'utilisation d'Eclipse et de Git
  • PMD
  • Launch4j en tant que task Ant pour la génération de l'exécutable

Je trouve Butterfly intéressant, on parle beaucoup du javascript coté serveur avec node.js depuis quelques temps. La frontière entre le client et le serveur est toujours difficile à franchir surtout lorsqu'on vient du monde java, malgré les progrès effectués ces dernières années (GWT, Wicket, ...) Node.js sera justement le sujet de mon prochain billet ! J'investiguerai peut être un peu plus Butterfly pour vous en reparler. A suivre donc...

Alexandre. Twitter : @alexandre_t

lundi 8 novembre 2010

Retour d'expérience d'un projet GWT : 2 UiBinder, enfin une forte collaboration entre le designer et le développeur (2/8)

Le contexte:

Le projet a pour but d'améliorer l'ergonomie et l'usabilité du back office d’un produit en cours de développement réalisé en collaboration avec SFEIR

Dans le cadre de ce projet, la version de GWT utilisée passe de la 1.7.1 à la 2.0.3 afin de bénéficier des nouvelles fonctionnalités offertes par cette version. De nouveaux pattern de développement ont également été mis en place au cours de ce projet.

Voici une série d'article sur les nouveautés de GWT 2, les choix d'architectures, et bibliothèques utilisés qui font part de notre retour d'expérience sur le sujet. Ces articles ont été écris par David Aboulkheir, Patrice de Saint Steban et Cyril Lakech

  1. Nouveautés de GWT 2.0
  2. UiBinder, enfin une forte collaboration entre le designer et le développeur
  3. Intégration facile de maquette Html en GWT 2
  4. Architecture Modèle-Vue-Presenteur
  5. Implémentation Modèle-Vue-Présenteur
  6. Ecrire des tests unitaires avec Mockito
  7. Mise en place de Gin sur le projet
  8. Internationalisation

UiBinder, enfin une forte collaboration entre le designer et le développeur

Dans la version 2 de GWT, Google propose aux développeurs de choisir comment coder les écrans, soit en Java uniquement comme c'était le cas jusqu'à présent soit en utilisant une nouvelle fonctionnalité UiBinder, qui permet de coder les écrans en XML, très proche du HTML.

UiBinder permet d'écrire les interfaces graphiques avec un formalisme XML un peu comme on peut le faire avec JSF, Flex, Silverlight ou encore en HTML. Jusque-là, la création d'interface en GWT se faisait en manipulant du code java à la façon native de Swing ou SWT/JFace. On va maintenant pouvoir les écrire en XML et les lier (les binder) avec une classe java qui contiendra le code de notre vue. Le code en sera d'autant plus lisible, maintenable et plus rapide à développer. Cette évolution va permettre également la multiplication d'outils puissants, comme les éditeurs d'interfaces GWT, plus simples à créer, comme GWTDesigner d'Instantiations, qui a été acheté par Google et qui est maintenant disponible gratuitement: http://code.google.com/intl/fr/webtoolkit/tools/download-gwtdesigner.html

Cette nouvelle organisation, sépare la partie présentation (l'écran) de la partie contrôle (le code java). On écrit un fichier XML contenant nos widgets graphiques, puis on les associe avec les objets dans notre classe java où l'on ajoute aussi les évènements de l'IHM. Il est également possible d'écrire notre interface en HTML en y incorporant nos widgets HTML. Le CSS peut se trouver directement dans le fichier d'interface ou dans un ClientBunddle (système GWT2 de gestion des ressources type CSS en java qui sera détaillé ci-dessous).

Prenons une classe Miniature.java, qui permet d'afficher une image et son titre, pour utiliser UIBinder. Commençons par créer le fichier xml :

<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'
xmlns:gwt='urn:import:com.google.gwt.user.client.ui'>
<ui:style>
   .mini {
    float : left;
    margin: 20px;
    height: 150px;
    width: 150px;
   }
</ui:style>
   <gwt:VerticalPanel styleName="{style.mini}">
        <gwt:Image ui:field="miniature" />
       <gwt:Label ui:field="label"/>
   </gwt:VerticalPanel>
</ui:UiBinder>

Nous voyons ici, que la racine du XML, ui:UiBinder permet de configurer les namespaces pour utiliser les composants, le namespace "ui" contient les configurations du UiBinder, tandis que le namespace "gwt" permet d'utiliser les composants de base de GWT. La balise <ui:Style> permet d'insérer le CSS utilisé par notre composant. Dans la suite du fichier, on peut accéder à la classe CSS en utilisant la syntaxe {style.nomClasse}. Nous voyons ensuite que le composant GWT racine est le VerticalPanel qui contient une Image et un Label. Pour les composants Image et Label, on a ajouté un attribut ui:field associé à un nom de paramètre, ce qui nous permettra d'accéder au composant depuis notre classe Java.

Voyons maintenant la classe Java associée :

public class Miniature extends Composite {
    interface Binder extends UiBinder<Widget, Miniature> { }

    private static final Binder binder = GWT.create(Binder.class);
    @UiField Image miniature;
    @UiField Label label;
    private String destination;

    public Miniature(String url, String titre, String destination) {
        this.destination = destination;
        initWidget(binder.createAndBindUi(this));
        miniature.setUrl(url);
        label.setText(titre);
    }

    @UiHandler("miniature")
    public void onClick(ClickEvent event) {
        Window.open(destination, "Image", "width=200, height=100");
    }
}

L'interface Binder étend UiBinder qui prend deux paramètres de type, le composant de base de notre interface (VerticalPanel dans notre cas, mais nous utilisons une super classe), et la classe qui va contenir les composants reliés (La classe actuel). Nous créons ensuite, en variable static, une instance de cette interface en utilisant GWT.create(); C'est le compilateur de GWT qui s'occupera d'implémenter cette classe en utilisant les informations du fichier XML.

Puis nous ajoutons les champs de notre interface en utilisant l'annotation @UiField pour indiquer à GWT qu'il faut relier ces champs avec les composants GWT définis dans le fichier XML. Le nom du champ doit être celui donné dans l'attribut ui:field. Dans notre constructeur, nous appelons la methode createAndBindUi sur notre interface Binder, qui va alors créer et relier les composants GWT à notre classe. Enfin, la methode onClick, appelée lors du clic sur l'image, est reliée à notre composant miniature grâce à l'annotation @UiHandler("miniature").

UiBinder permet aussi de créer une interface en HTML et d'y intégrer nos composants GWT sans passer par les traditionnels composants Panel de GWT (VerticalPanel, Grid, ...), tout simplement en utilisant le composant HTMLPanel. Utilisons ce procédé pour refaire le formulaire de recherche :

<?xml version="1.0" encoding="UTF-8"?>
<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'
    xmlns:gwt='urn:import:com.google.gwt.user.client.ui'>
    <ui:style>
        .label {
            float: left;
            width: 50px;
            clear: both;
        }
        .element {
            float: left;
            width: 250px;
        }
</ui:style>
    <gwt:HTMLPanel>
        <dl>
            <dt class='{style.label}'>
                <ui:msg>Topic :</ui:msg>
            </dt>
            <dd class='{style.element}'>
                <gwt:TextBox ui:field="saisieSujet" />
            </dd>
            <dt class='{style.label}'>
            </dt>
            <dd class='{style.element}'>
                <gwt:Button ui:field="boutonAjouter">
                    <ui:msg>Search</ui:msg>
                </gwt:Button>
            </dd>
        </dl>
    </gwt:HTMLPanel>
</ui:UiBinder>

Ici, la classe Java est sensiblement équivalente à la classe Miniature.java :

public class FormulaireRecherche extends DialogBox {
    interface Binder extends UiBinder<Widget, FormulaireRecherche> {}
    private static final Binder binder = GWT.create(Binder.class);

    @UiField Button boutonAjouter;
    @UiField TextBox saisieSujet;

    public FormulaireRecherche() {
        super(true);
        add(binder.createAndBindUi(this));
        center();
    }

    @UiHandler("boutonAjouter")
    public void onClick(ClickEvent event) {
        hide();
    }
}

Références :

jeudi 4 novembre 2010

Nouveautés GWT 2.1

GWT 2.1 vient de sortir en version finale, voici les nouveautés apportées par cette version :

  • Data Presentation Widgets
  • MVP Framework (Activities and Places)
  • RequestFactory
  • Editor
  • Server-side Speed Traces
  • Logging
  • Safe HTML

Détaillons un peu ces nouvelles fonctionnalités :

Data Presentation Widgets

Ce nouveau framework permet de construire des tableaux dynamiques (liste, arbres). Ces composants peuvent afficher un très grand nombre de données, car il va construire les données en minimisant les appels au DOM de la page.

MVP Framework (Activities and Places)

Depuis 2 ans, Google pousse le design patern Model View Presenter (MVP) grâce à une présentation faite par Ray Ryan à Google IO ainsi que deux articles :

http://code.google.com/intl/fr/webtoolkit/articles/mvp-architecture.html

http://code.google.com/intl/fr/webtoolkit/articles/mvp-architecture-2.html

De nombreux frameworks ont permit d'implémenter ce nouveau design pattern :

A la différence des frameworks précédents, la version de GWT 2.1 utilise une autre terminologie, en voici un petit lexique, et explication.

Vue

Tous d'abord, la notion de vue n'existe plus, une vue est un simple widget GWT ou plutôt un composite qui peut être une classe UiBinder.

La nouveauté c'est que tous les widgets Gwt implémentent maintenant l'interface IsWidget, qui demande une seule méthode Widget asWidget();

Maintenant une vue dont l'implémentation est un simple Widget peut être définie par une interface, tout en pouvant ensuite l'ajouter dans un conteneur.

Une vue étend une interface du Presenter qui permet à celui-ci de communiquer avec la vue.

Activité (Presenter)

Un Presenter se nomme maintenant une activité (Activity), et s'occupe de toute la logique métier de l'écran.

Un Presenter peut implémenter une interface venant de sa vue pour simplifier la gestion des événements avec UiBinder, c'est à dire qu'on crée dans la vue la méthode onClick accompagné de l'annotation @UiHandler, dans laquelle on appelle une méthode du Presenter (Activity) qui gérera ensuite l'action. L’ancienne technique avait pour principe de définir une méthode dans la vue (getter) qui retourne une interface permettant d’ajouter un Handler par le Presenter (i.e. HasClickHandlers).

Une activité doit implémenter deux méthodes :

  • start(AcceptsOneWidget containerWidget, EventBus eventBus) : Appelé lorsque l'activité doit s'afficher
  • onStop() : Appelé lorsque l'activité doit disparaître (lors qu'une autre activité viendra la remplacer ou que l'on quite la page)

Deux autres méthode doivent aussi être implémentées :

  • mayStop() demande un message à afficher si on veux demander à l'utilisateur si il veux réellement changer de page (indiquer qu'ils n'a pas sauvegarder un formulaire par exemple). Il suffit de retourner null si l'activité accepte le changement de page.
  • onCancel() : Appelé si l'activité n'a pas encore eu le temps d'être afficher et qu'elle doit déjà être remplacer par une autre activité.

Remarque: En étendant la classe AbstractActivity, seule la méthode start() doit être implémentée.

AcceptsOneWidget est aussi une nouvelle interface ajoutée à Gwt laquelle est implémentée par SimplePanel, et dans laquelle la vue doit être ajoutée. Cela évite de devoir utiliser l'interface HasWidgets ainsi que le Presenter fasse un clear() avant de faire un add() pour remplacer le widget composant la vue principale.

ActivityManager

L'ActivityManager, c'est un peu le pendant de l’AppControler (l'autre partie est remplacée par le PlaceController), car il s'occupe de changer les activités affichées lorsqu'une nouvelle page doit être affichée. Cette classe est directement implémentée par GWT 2.1. Chaque zone pouvant afficher des activités aura son propre ActivityManager.

Places

C'est là, la grosse nouveauté de ce nouveau framework MVP, car on ne retrouve pas cette information dans les autres frameworks MVP. Pour ceux qui connaissent la programmation Android, une Place a le même rôle qu'un Intent. Une Place représente le lieu où l'on se trouve, s'est à dire la page (activité) qui est affichée. Changer de page ou d'activité revient à changer de Place, d'où la méthode goTo(Place newPlace) qui est dans le PlaceControler.

Le PlaceControler représente ainsi la deuxième partie de l'AppControler, présenté par les anciens articles. A la différence de l’ActivityManager, le PlaceController est unique pour l’application.

Il ne peut pas y avoir deux Places affichées simultanément. Une Place peut contenir des données, c'est une simple classe à créer qui étend Place où l'on met les propriétés que l'on veut.

Pour qu'une Place puissent être utilisée comme URL (et ainsi utiliser les fonctionnalités précédent et suivant du navigateur), il faut utiliser le PlaceHistoryMapper qui permet de transformer une Place en String et vice versa.

Gwt propose de générer automatiquement cette classe. Il nous faut alors créer un Tokenizer pour chaque place qui va sérialiser et désérialiser les attributs d'une Place de la façon que l'on veut. Il va ensuite afficher dans l'URL le nom de la Place (nom de la classe pouvant être redéfinis par l'annotation @Prefix("")) La classe PlaceHistoryHandler s'occupera ensuite de changer l'url et la gestion de l'historique automatiquement.

ActivityMapper

Cette classe permet de faire la conversion entre une Place et une Activity. Elle s'occupe d'instancier l'Activity pour une Place donnée. C'est à nous de la créer mais avec Gin, cela peut se faire facilement.

EventBus

C'est un bus d'événement, il permet de faire communiquer tous les composants d'une application avec les autres applications. L'ancien HandlerManager n'existe plus, l'interface EventBus est désormais utilisée. Une implémentation simple existe : SimpleEventBus. Attention, une activité doit ajouter ses évènements dans le "start" car une fois qu'elle est supprimée les événements sont retirés automatiquement.

Model

Le model est implémenté par la RequestFactory qui permet de faire transiter des entities JPA /JDO entre le server et le client. Il apporte automatiquement les méthodes nécessaires pour construire un CRUD (Créer, Lire, Mettre à jour, Supprimer). RequestFactory n'a pas pour vocation à remplacer RPC et peux être utilisé indépendemment. Seules les données modifiées de l’entité transiteront sur le réseau.

Editor

Apporte un système de binding entre les objets java et les représentations graphiques en particulier pour créer les formulaires de création et de modification. Plus d'informations Vous pouvez retrouver la documentation officielle avec un projet d'exemple :

http://code.google.com/intl/fr/webtoolkit/doc/trunk/DevGuideMvpActivitiesAndPlaces.html

et voici quatre autres articles qui expliquent le fonctionnement des Places et Activities :

Server-side Speed Traces

Les informations de performances venant du serveur (Google App Engine ou Spring TC Server) peuvent maintenant être affichées dans l'outil Speed Tracer.

Logging

Il existait déjà Gwt-Log qui a été maintenant intégré directement à GWT 2.1. On utilise l'Api standard Java pour logger (java.util.logging). Par-contre la configuration de logging se fait dans le fichier .gwt.xml

# In your .gwt.xml file
<inherits name="com.google.gwt.logging.Logging"/>
# To change the default logLevel
<set-property name="gwt.logging.logLevel" value="SEVERE"/>  
# To disable logging        
<set-property name="gwt.logging.enabled" value="FALSE"/>      
# To disable a default Handler      
<set-property name="gwt.logging.consoleHandler" value="DISABLED"/>  

# In your .java file
Logger logger = Logger.getLogger("NameOfYourLogger");
logger.log(Level.SEVERE, "this message should get logged");

Les logs peuvent être ensuite affichés de différentes façons,

  • SystemLogHandler - Ecrit dans la sortie du mode de développement de GWT
  • DevelopmentModeLogHandler - Utilise l'ancienne méthode GWT.log (les logs ne sont visibles qu'en mode développement)
  • ConsoleLogHandler - Ecrit dans la console Javascript (Firebug ou console Chrome)
  • FirebugLogHandler - Ecrit dans la console Firebug
  • PopupLogHandler - Affiche les logs dans une popup
  • SimpleRemoteLogHandler - Envois les logs par un RPC où l'on peut logger ensuite sur le serveur.

Safe HTML

Safe HTML permet de filtrer du texte pour protéger le HTML qui le contient et éviter les injections XSS. Il fonctionne comme un StringBuilder, mais peut échapper automatiquement les caractères spéciaux. Un système de template permet d’écrire du code HTML facilement en utilisant un mécanisme similaire aux Messages mais en protégeant le code HTML.

public interface MyTemplates extends SafeHtmlTemplates {
   @Template("<span class=\"{3}\">{0}: <a href=\"{1}\">{2}</a></span>")
   SafeHtml messageWithLink(SafeHtml message, String url, String linkText,
       String style);
 }

 private static final MyTemplates TEMPLATES =
     GWT.create(MyTemplates.class);

 public void useTemplate(...) {
   SafeHtml message;
   String url;
   String linkText;
   String style;
   // ...
   InlineHTML messageWithLinkInlineHTML = new InlineHTML(
       TEMPLATES.messageWithLink(message, url, linkText, style));
   // ...
 }

Voilà un rapide tour d'horizon des nouveautés de GWT 2.1, d'autres articles suivront pour expliquer plus précisément certains points. Ces nouveautés une fois maitrisées et utilisée sont vraiment puissante et apporte encore plus de simplicités à la fois pour le développeur mais aussi pour l'utilisateur.

lundi 25 octobre 2010

Retour d'expérience d'un projet GWT : 1 Nouveautés de GWT 2.0 (1/8)

Le contexte:

Le projet a pour but d'améliorer l'ergonomie et l'usabilité du back office d’un produit en cours de développement réalisé en collaboration avec SFEIR

Dans le cadre de ce projet, la version de GWT utilisée passe de la 1.7.1 à la 2.0.3 afin de bénéficier des nouvelles fonctionnalités offertes par cette version. De nouveaux pattern de développement ont également été mis en place au cours de ce projet.

Voici une série d'article sur les nouveautés de GWT 2, les choix d'architectures, et bibliothèques utilisés qui font part de notre retour d'expérience sur le sujet. Ces articles ont été écris par David Aboulkheir, Patrice de Saint Steban et Cyril Lakech

  1. Nouveautés de GWT 2.0
  2. UiBinder, enfin une forte collaboration entre le designer et le développeur
  3. Intégration facile de maquette Html en GWT 2
  4. Architecture Modèle-Vue-Presenteur
  5. Implémentation Modèle-Vue-Présenteur
  6. Ecrire des tests unitaires avec Mockito
  7. Mise en place de Gin sur le projet
  8. Internationalisation

1 Nouveautés de GWT 2.0

Development Mode (anciennement OOPHM : "Out Of Process Hosted Mode")

GWT 2.0 apporte une nouvelle approche du développement des applications GWT en permettant de débugguer le code java et de voir le résultat directement dans les navigateurs sans passer par un navigateur spécifique, anciennement via le HostedMode qui utilisait son propre Navigateur différent selon le système d'exploitation. On assiste ainsi à un changement de terminologies puisqu'il ne faut plus parler de "hosted mode", qui portait souvent à confusion, mais de "development mode". Et pour la même raison on parle maintenant de "production mode" au lieu de "web mode", pour parler du script compilé.

On peut ainsi utiliser les outils du navigateur pour développer comme par exemple firebug sans devoir recompiler en permanence toute l'application. Pour utiliser ce mode, il suffira de copier/coller l'url qui est donnée par GWT lors du démarrage du server dans le navigateur, par exemple: "http://localhost:8080/Tutoriel.html?gwt.hosted=X.X.X.X:NNNN", où les X représentent l'adresse IP de votre ordinateur et les N le port utilisé par GWT.

Le development mode a été pour nous un facteur de gain de productivité important par rapport au hosted mode car grace à cette fonctionnalité il est possible de tester directement l’application dans les navigateurs cibles. On regrettera simplement que cette fonctionnalité ne soit pas disponible dans Chromium, le projet open-source de Google Chrome sur Linux. Layout

La notion de Layout permet d'avoir des composants génériques pour gérer la disposition des widgets. Ces layouts utilisent des panels déjà existant mais uniformisent leur utilisations. De plus, ces layouts se redimensionnent automatiquement en réorganisant plus précisément les widgets. Le rendu a été amélioré pour s'afficher plus rapidement et le redimensionnement peut être animé. A la différences des anciens panels de GWT qui utilisent massivement les tableaux pour afficher les widgets, les nouveaux layouts utilisent uniquement le CSS. Il existe par exemple le StackLayoutPanel, le TabLayoutPanel ou le DockLayoutPanel qui permettent d'avoir un contenu principal (Center) et d'ajouter des contenus au Nord, au Sud, à l'Ouest et à l'Est autour de celui-ci.

Nous avons utilisé ces Layout dans l’application très simplement et notre Web Designer a été particulièrement content de voir le rendu HTML/CSS généré s’améliorer en les utilisant, ce qui a faciliter son travail.

RPC (Remote Precedure Call)

Le système de service distant a été refondu pour gagner en performance et permettre l'ajout de nouvelles fonctionnalités. Pour le développeur, le principe de fonctionnement ne change pas. Il suffit d'ajouter dans le fichier Tutoriel.gwt.xml la ligne <inherits name='com.google.gwt.rpc.RPC' /> et de changer l'interface RemoteService par RpcService ainsi que la classe RemoteServiceServlet par RpcServlet. Cependant, cette fonctionnalité n'est pas, pour l'instant, compatible avec Google App Engine.

ClientBundle

On avait vu que GWT dans ses versions précédantes était capable de réunir plusieurs images en une seule, GWT 2.0 va encore plus loin car il est maintenant capable de réunir différents types de ressources (Images, Texte, Css, Données, etc...) en même temps. Toutes les ressources sont intégrées dans un même bundle encodé en base 64. Rappelons ainsi l'avantage de ce concept: un seul fichier à télécharger, donc, moins de requêtes HTTP et un temps de chargement de la page plus rapide.

Code splitting

Tout le code javascript est compilé en un seul fichier, téléchargé au chargement de la page. Dans le cas de très grosses applications, la taille de celui-ci peut considérablement devenir très importante et ralentir, de ce fait, le temps de chargement de l'application au démarrage. Pour pallier à ce problème, GWT 2.0 offre maintenant la possibilité de couper notre application en plusieurs fichiers qui seront téléchargés quand ceux-ci seront nécessaires. Par exemple, si une partie du code est utilisé uniquement lors du déclenchement d'un événement, son téléchargement n'est pas forcément essentiel lors du premier chargement de l'application. Par ailleurs l'utilisation est très simple:

GWT.runAsync(new RunAsyncCallback() {
    @Override
    public void onSuccess() {
        //Tout le code qui est ici, sera téléchargé plus tard.
    }

    @Override
    public void onFailure(Throwable reason) {
        //Si le téléchargement du nouveau code a échouée
    }
});

Mais attention, si le code, qui se trouve dans le onSuccess, est aussi utilisé par l'application dès son lancement, il sera téléchargé au début. C'est pourquoi GWT génère, tout de même, un rapport qui donne toutes les informations de compilation, informations que l'on peut analyser pour optimiser son application.

De plus, il est important de prévenir l'utilisateur du chargement par une barre de progression, ou par une icône de préchargement, car le changement n'est pas forcement immédiat et peut influer sur l'utilisabilité de l'application. Pour utiliser cette fonctionnalité, ajoutez "-compileReport" dans les paramètres avancés du compilateur GWT. Les rapports de compilation sont alors générés dans le répertoire extras/[moduleName]/soycReport/compileReport/.

Nous avons utilisé cette fonctionnalité pour charger à la demande les écrans principaux de l’application. Nous avons constater que la taille globale des fichiers javascript générés a sensiblement augmentée suite a la mise en place du Code Splitting. Il faut noter que l’utilisation de cette fonctionnalité sur 12 permutations a nécessité dans notre cas d’augmenter la partition /tmp utilisée pour compiler l’application (4Go ne suffisait pas).

mercredi 13 octobre 2010

Retour sur le Paris JUG d'Octobre

Pour cette soirée, marquée sous le signe web, il y aura eu 3 présentations.

Ruby On Rails - Point de vue d'un javaiste

Cette 1ère partie nous est présentée par Christian Blavier de chez Octo.
Java, c'est plein de belles promesses. C'est comme un hamburger plein de beaux ingrédients mais tellement que ça peut en devenir écoeurant : trop de couches, trop d'abstraction, on s'éloigne trop du html, ...
Ruby On Rails quant à lui est optimisé pour le plaisir des développeurs, c'est plein de bonnes idées.
On y retrouve une architecture MVC.
Etant basé sur Ruby, on y retrouve certaines facilités comme la manipulation de dates, les closures, ...
C'est aussi un framework fait pour le web :
  • Quelques lignes suffisent pour y déclarer notre contrôleur. Si on souhaite du JSon, il suffit de l'indiquer, pas besoin de framework.
  • Templating web : des gems comme ham et sass donne des facilités respectives en html et css
  • Routage : Pour avoir des urls type REST, 1 seule ligne suffira dans le fichier de configuration de routage
  • Active Record : Gère la persistance directement dans le modèle.
C'est aussi un framework fait par des agilistes pour les agilistes. Le test est au coeur du développement et il existe un outillage important pour aider dans la testabilité. Parmis eux : Mocha, Girl Factory, Time Cop, Cucumber, ...
Pour le déploiement, Heroku offre une plateforme qui permet de déployer gratuitement son application; et ça en une ligne de commande.

HTML5

Pour la deuxième partie, c'est Alain Duval et Cédric Beurtheret de Objectif Informatique.
La présentation démarre avec un rappel de la Genèse de Html5 puis un rappel de ses principales fonctionnalités.
Ils nous livrent aussi un retour d’expérience projet.
Au début, ils avaient utilisé Gears puis sont passé à html5 quand ce fut possible. Leur application est destinée à gérer la relation clientèle pour des commerciaux équipés de tablet pc possédant une connectivité 3g. Un zoom est fait sur 3 fonctionnalités :
  • Web worker : C'est une API pour lancer une tâche de fond sans bloquer le navigateur.
  • Cross window messaging : permet à deux fenêtres de communiquer entre elles, même si elles ne sont pas sur le même domaine.
  • Web storage : du stockage de données locales dans le navigateur. Il est possible de travailler en mode synchrone ou asynchrone, mais la 2ème option est recommandée car plus performante. Par contre attention à la sécurité de vos données, la base locale ne permet pas l'utilisation d'identifiants.
Et pour finir, nous avons le droit à une belle démo dédiée au JUG montrant ces 3 fonctionnalités : une page qui affiche des message twitter. Les messages sont récupérés par une web worker et stockés en base locale le tout avec une fenetre de log gérée par le cross window messaging. Pour montrer le bon fonctionnement, quelques personnes envoient des tweets qui apparaissent sans avoir à rafraîchir la page.

Play!

Et pour la dernière partie, c'est au tour de Nicolas Martignole et de Guillaume Bort.
Play est un framework qui fait de plus en plus parler de lui que ce soit sur les blog mais aussi dans des conférences comme Jazoon et même JavaOne.
Tout d'abord, il faut savoir que Play! n'est pas basé sur l'API Servlet qui est comme une muti-prise sur laquelle on a branché un tas d'appareils successivement.
L'analogie du McDo est prise comme exemple : là bas, vous faites la queue jusqu'à ce qu'une équipière prenne votre commande puis court dans tous les sens pour vous la préparer et vous l'apporter. Pendant ce temps là, elle ne peut pas prendre d'autre commande
Par contre, Play! c'est comme Starbuck. Les commandes ne sont pas prises par ceux qui préparent ce qui permet de répondre rapidement à une demande client.
La philosophie de Play! est assez proche de celle de Grails avec quelques fonctionnalités en moins comme les finders dynamiques.
On va avoir une url propre (ex : http://www.express-board.fr/user/sfeir/27) fini tout ces paramètres uniquement techniques. Une url se doit d'être lisible, bookmarkable et partageable.
Si on veut pouvoir monter en charge sans devoir mettre en place de réplication de session, le serveur doit être stateless. C'est à sens que les informations de session sont stockées dans le navigateur dans un cookie crypté de seulement 3Ko.
Play! n'est pas client side, c'est pourquoi son ihm se sert des langages du web : html/css/js
C'est comme un couteau suisse, c'est fullstack.
Là où en java, on a l'habitude d'une certaine complexité liée à l'intégration de différents frameworks entre eux, Play! propose de prendre le problème différemment. Un problème compliqué a t il besoin d'une solution compliquée ?
Play aide à la productivité en ayant un serveur sans état : pas besoin de le redémarrer pour ajouter/retirer des fonctionnalités, lorsqu'une erreur se produit en mode dev, elle s'affiche lisiblement dans le navigateur, ...
Et cela s'illustre dans la démo, en quelques minutes et très peu de code Guillaume contruit un formulaire pour ajouter des éléments à une liste de todos, et ce juste avec un éditeur de texte et aucun redémarrage. Tout cela est possible parce que Play ne se base pas les fichiers .class mais sur les fichiers .java.

samedi 9 octobre 2010

Fastcall Android

A Sfeir, nous aimons la technologie, et Android en fait évidement parti.

J'ai développé avec mon collègue Clément Griffoin une application basée sur une idée de celui qui est à la fois notre directeur technique et notre directeur des opérations : Didier Girard. Lorsqu'on a beaucoup de contacts dans son répertoire, il devient fastidieux de retrouver celui que l'on veut : que de mouvements de pouces de bas en haut pour faire défiler sa liste.

Fastcall vous propose d'en écomiser !

Confucius disait qu'une image vaut mille mots, je vous épargne un long discours pour vous en expliquer le principe :

            

Et voilà, en 3 coups de pouces, nous pouvons appeler notre ami Blaise Lucas bien qu'il soit l'un de nos 1000 contacts téléphoniques.

Si vous souhaitez tester notre application :

Puisque nous aimons et croyons en l'open source, cette application est bien sûr soumise à ce principe :

http://code.google.com/p/fastcall-android/

Nous sommes donc à l'écoute de vos propositions, problèmes rencontrés, ...

Et pour les twittos, suivez .

mercredi 29 septembre 2010

Code Konami en GWT

Connaissez vous le code Konami ?
Il s'agit d'un code spécial utilisé dans de nombreux jeux de l'éditeur de jeux video Konami. Pour accéder à des options secrètes, il fallait effectuer la séquence suivante sur sa manette : haut, haut, bas, bas, gauche, droite, gauche, droite, b, a. Pour plus d'infos, je vous laisse lire l'article wikipedia.
Vous ne voyez pas le rapport avec GWT ? J'y viens... Certains développeurs web ayant été bercé par ces jeux dans leur enfance ont eu l'idée d'incoporer ce code dans leur site. C'est ainsi que nous les retrouvons sur certains sites que vous connaissez : free.fr, Google Reader, JQuery, ... (essayez, vous verrez) Parmis eux on retrouve bien sûr http://konamicodesites.com où l'execution de ce code est indispensable pour accéder à son contenu.
Pour déctecter l'exécution de ce code, il faut donc surveiller quelles sont les touches appuyées. C'est en javascript que celà se fait. Nous y voilà, personnellement, je préfère l'utilisation de gwt à celle de javascript. Je vous propose de voir comment nous pourrions l'implémenter. Lorsque l'utilisateur va effectuer la séquence de touches, une action devra être déclenchées. C'est le principe d'un handler. Nous aurons donc une interface de ce type :



Ensuite on va devoir surveiller les touches au niveau de la plage. Le principal problème est qu'il n'existe pas de de handler à rattacher au niveau de la page. comme on peut On en trouver sur un TextBox. On va donc s'intéresser aux évènements natifs du navigateur, les NativePreviewEvent. Pour cela, nous allons implémenter l'interface NativePreviewHandler. Celle ci, va réagir à l'ensemble des événements navigateur : aussi bien les touches clavier et la souris, mais aussi tous les évènements comme le onload, onblur, onchange, ... D'autre part, nous allons devoir si les dernières appuyées pressées correspondent à la séquence. Pour celà, nous allons enregister les dernières saisies sous forme de chaine ascii, la comparer à la séquence voulue et si elle est bonne appeler la méthode de notre handler. Ce qui nous donne : 


Il ne nous reste plus qu'à l'appeler dans depuis où vous voulez dans votre projet gwt en faisant par exemple : 
Bien sûr, cette action n'est pas du tout évoluée, c'est à vous de libérer votre imagination.
Je vous propose cette implémentation avec konami-gwt. Si vous souhaitez l'utiliser dans votre projet gwt, il vous suffit de déclarer son module dans votre fichier *.gwt.xml.

samedi 18 septembre 2010

Mvp avec mvp4g

Depuis la conférence Google I/O 2009, le pattern architectural MVP fait parler de lui à un point où un grand nombre de framework open source s’est répandu, chacun proposant son implémentation plus ou moins élaborée. Tous contiennent au moins la gestion du présenteur et ensuite on y trouve ou non un event bus, une gestion de l’historique, injection de dépendances, pattern action, ....

C’est sur google code que j’ai découvert Mvp4g  qui couvre une bonne partie de ses fonctionnalités.

Pour vous le faire découvrir, nous allons reprendre le code de l’application de gestion de contacts de l’article de google sur le mvp : http://code.google.com/intl/fr/webtoolkit/articles/mvp-architecture.html

Pour cet exercice, nous prendrons la dernière version de mvp4g qui vient l’être releasée ainsi que de ses dépendances :

  • mvp4g-1.2.0

  • commons-lang 2.4

  • commons-configuration 1.6

  • gin 1.0

  • guice 2.0

  • aop-alliance.

Comme pour n’importe quel bibliothèque gwt, il faut la déclarer dans notre fichier gwt.xml :


<inherits name='com.mvp4g.Mvp4gModule' />


Maintenant, faisons un tour d’horizon des modifications que nous allons devoir apporter :

  • La partie Model ne changera pas : Mvp4g ne couvrant pas l’Action pattern, nous ferons appel au méthodes exposée en rpc tout comme dans le code original.

  • La partie Presenter, elle sera une des parties qui changera le plus : nous devrons étendre une classe du framework et répondre à son contrat.

  • La partie View ne changera que très peu : nous n’aurons qu’à changer l’interface implémentée.

  • L’Event Bus : un des autres gros changement. Mvp4g propose en effet son propre modèle avec un système d’évènements haut niveau assez souple.

  • La gestion de l’historique, là aussi tout est à refaire.

  • L’Entry Point : Avec mvp4g, nous n’avons pas forcément besoin d’en écrire un ...

A cela, nous rajouterons une injection de dépendances via gin qui n’est pas dans le code original. C’est aussi dans cet esprit que se configure mvp4g. Que ce soit pour lier les différents composants ou spécifier le comportement dus bus ou de l’historique. Elle peut être soit réalisée en xml ou par annotations. J’ai préféré l’utilisation des annotations.

J’imagine que le point sur l’entry point, vous a laissé perplexe mais attention ce n’est pas parce que nous n’allons pas en écrire un que nous n’en aurons pas. C’est justement parce que l’application s’assemble en fonction des annotations que cette partie peut devenir générique. Nous utiliserons donc celui que nous propose mvp4g en incluant cette ligne dans le gwt.xml :


<entry-point class='com.mvp4g.client.Mvp4gEntryPoint' />


Pour les plus curieux, voici le code qui y correspond :


Mvp4gModule module = (Mvp4gModule)GWT.create( Mvp4gModule.class );

module.createAndStartModule();

RootPanel.get().add( (Widget)module.getStartView() );

Et c’est tout. Le code étant assez simple, il se comprend de lui même. Tant que nous n’aurons qu’un seul module gwt, il nous sera suffisant.

Passons maintenant au centre névralgique de notre application : le bus évènementiel. Dans une application mvp4g, il est indispensable. C’est bien sûr par lui va passer la distribution des événements haut niveau mais aussi quelques autres notions liées comme la gestion de l’historique et surtout l’action “start”.

A 3ème ligne de l’entry point, vous aviez sans doute remarque le getStartView. Il n’y a rien de magique, c’est par la configuration du bus que nous la connaîtrons.

Allez, mettons nous en route , pour coder notre bus. En fait, nous n’aurions rien à implémenter puisqu’il s’agit d’une interface. Elle devra néanmoins étendre l’interface EventBus de mvp4g et être annotée :


@Events( startView = RootView.class)

public interface   ContactsEventBus extends EventBus {

}


C’est sur l’annotation @Events que nous définissons RootView en tant que vue chargée s’affichant au démarrage. Fidèle à son nom, elle sera notre vue racine. Mvp4g conseille de disposer d’une telle vue qui servira de container aux autres vues. Comme toute vue dans un modèle MPV, elle sera statique et sera associée à un présenteur. L’interface qui servira de contart avec le présenteur sera celle ci :

public interface RootViewInterface {

     Panel getBody();

}

et l’implémentation sera la suivante :


public class RootView extends  Composite  implements  RootViewInterface {

private  SimplePanel body = new SimplePanel();

      public RootView() {

            VerticalPanel mainPanel = new VerticalPanel();

            mainPanel.add(body);

            initWidget(mainPanel);

      }

      @Override

      public Panel getBody() {

            return body;

      }

}


Le présenteur quant à lui sera de la forme suivante :


@Presenter( view = RootView.class )

public class RootPresenter  extends  BasePresenter<RootViewInterface, ContactsEventBus>


Là encore, nous avons du code assez simple à comprendre . Nous annotons le présenteur pour qu’il puisse être détecté par mvp4g et étendons la classe BasePresenter<V, E extends EventBus> pour nous permettre d’interagir avec la vue mais aussi avec notre bus. De la même manière, nous aurons : un ContactsPresenter et un EditContactPresenter avec des vue telle qu’on les a dans le code original.

Nous avons déjà la base de notre application mais pour l’instant, elle ne fait rien. Dans notre cas, nous voudrions qu’au lancement, la liste des contacts soit affiché. Tiens, nous tenons un évènement haut niveau et nous voudrions qu’il soit lancé au lancement... C’est encore une fois le bus qui va s’en occuper, encore un fois par une simple annotation.

Puisque nous voulons lister, nous allons créer un évènement “list” tout simplement. A la différence du mvp proposé par google dans l’article, nous n’aurons pas à créer une classe héritant de GwtEvent et de lui associer une handler. Nous allons seulement ajouter une méthode qui porte le nom de cet évènement à l’interface de notre bus :


@Start

@Event(handlers = ContactsPresenter.class)

void list();


C’est grâce à l’annotation @Start que l’évènement sera lancé au lancement client de l’application et sera délégué à notre ContactPresenter. A noter que dans notre cas, nous n’avons qu’un seul handler. Il aurait été tout à fait possible d’en avoir plusieurs en les séparant par une virgule.

Il nous reste à implémenter la réaction du présenteur face à cet évènement. Le modèle évènementiel de mvp4g repose sur une convention simple : pour un méthode XXX du bus, le présenteur devra implémenter une méthode onXXX tout simplement. Bien qu’il ne soit pas possible à s’assurer de la concordance entre l’évènement est sa méthode réceptrice lors de la compilation java, ce contrôle est réalisé lors de la phase de compilation gwt.

Revenons à notre cas, nous aurons donc une méthode onList() dans notre ContactsPresenter :


public void  onList(){

      contactsService.getContactDetails(newAsyncCallback<ArrayList<ContactDetails>>() {

            @Override

            public void  onSuccess(ArrayList<ContactDetails> result) {

                  doList(result);

            }

            @Override

            public void  onFailure(Throwable caught) {

                  eventBus.errorMessage(ErrorMessages.RPC_CALL_FAILED);

            }

      });

      eventBus.changeBody(view.asWidget());

}

Pour déclencher un évènement haut niveau, il suffit d’appeler la méthode associée du bus.C’est ce que nous faisons par exemple avec le changeBody qui permet à la vue ContactView de devenir active.

Nous pouvons aussi transmettre une valeur ou plusieurs valeurs lors du déclenchement de l’évènement. Nous rencontrons ce cas pour l’édition :


@Event(handlers = EditContactPresenter.class)

void  edit(String id);

Vous avez aussi sûrement remarqué, l’appel au service rpc. Sa déclaration est très simple, nous utiliserons l’injection GIN par une simple annotation sur son setter :


@Inject

public void setContactsService(ContactsServiceAsync contactsService) {

      this.contactsService = contactsService;

}

Quant à la phase de binding des événements bas niveau par les présenteurs, elle est est analogue à celle du code original : nous n’aurons qu’à implémenter la méthode bind().

Nous aurons par exemple :


view.getAddButton().addClickHandler(new ClickHandler() {

      public void  onClick(ClickEvent event) {

            eventBus.add();

      }

});

Si vous avez peur de ne pas savoir où vont vos évènements, l’annotation @Debug sur le bus vous aidera en loggant en console ce qui se passe autour du bus. Il existe 2 modes de LogLevel : SIMPLE et DETAILLED, par défaut ce sera la première valeur. Regardons ce qui se passe dans les 2 modes sur un évènement edit .

Tout d’abord en mode simple :

16:47:05.355 [INFO] [contactsmvp4g] Module: Mvp4gModule || event: edit || param(s): 18

16:47:06.551 [INFO] [contactsmvp4g] Module: Mvp4gModule || event: changeBody || param(s): [...plein de html...]

et en mode détaillé :

16:50:53.187 [INFO] [contactsmvp4g] Module: Mvp4gModule || event: edit || param(s): 18

16:50:53.751 [INFO] [contactsmvp4g] com.sfeir.contacts.client.presenter.EditContactPresenter@640782 handles edit

16:50:54.052 [INFO] [contactsmvp4g] Module: Mvp4gModule || event: changeBody || param(s): [...plein de html...]

16:50:54.058 [INFO] [contactsmvp4g] com.sfeir.contacts.client.presenter.RootPresenter@16e4f00 handles changeBody

Intéressons nous maintenant à l’historique. Mvp4g utilise la notion de convertisseur d’historique. Ce qui signifie que nous devrons implémenter la façon dont les évènements seront convertis en url et vice et versa. Nous allons devoir implémenter l’interface HistoryConverter<E extends EventBus> et comme toujours l’annoter. Notre application étant relativement simple, nous n’en aurons qu’un seul mais sachez qu’il est tout à faire possible d’en avoir plusieurs. Pour répondre à son contrat, nous devrons implémenter les 2 méthodes suivantes :

  • convertFromToken( String eventType, String param, E eventBus ) : réaction lors d’une url tokenisée.
  • isCrawlable() : indique si le l’url sera crawlable en renvoyant un booléen.

La gestion de l’historique viendra se greffer sur le bus événementiel. Pour gérer l’historique, nous rajouterons :


@Events( startView = RootView.class, historyOnStart = true)


Réfléchissons d’abord un peu. Sur quoi aimerions nous avoir une historique avec des urls identifiables ? J’en vois 3 :

  • La liste des contact

  • Ajout d’un contact

  • Edition d’un contact. Idéalement, il serait intéressant d’avoir un paramètre permettant d’identifier le contact que nous éditons.

Le modèle d’historique de mvp4g utilise un token situé après un "#" dans l’url. Ce token correspond tout simplement au nom de l’évènement. Nous avons aussi la possibilité d’assigner un valeur à ce dernière en le placant derrière un "?".

Exemple : #token?value

Cette solution convient à notre exigeance de mieux marquer l’url lors de l’édition d’un contact. Pour lier, un évènement à notre convertisseur d’historique, il suffit de lui assigner sur le bus :


@Start

@InitHistory

@Event(handlers = ContactsPresenter.class, historyConverter=ContactHistoryConverter.class)

void  list();


L’annotation @InitHistory sert à marquer quand l’historique doit être initialisé. Celle est obligatoire si vous voulez utiliser l’historique. Revenons à notre convertisseur :


@History

public class  ContactHistoryConverter implements HistoryConverter<ContactsEventBus> {

      @Override

      public void convertFromToken(String  eventType, String param, ContactsEventBus eventBus) {

            if(“edit”.equals(eventType)){

                  eventBus.edit(param);

            } else if(“add”.ADD.equals(eventType)){

                  eventBus.add();

            } else if(“list”.equals(eventType)){

                  eventBus.list();

            }

      }

      @Override

      public boolean isCrawlable() {

            return  true;

      }

}


La première méthode est assez claire : nous appelons la méthode correspondante au token avec une particularité pour edit où nous nous servirons de la valeur en paramètre du token comme paramètre edit.

Même si l’IDE ne signale aucune erreur, la compilation gwt en provoquera. Il nous faut la réciproque de la méthode convertFromToken : c’est à dire indiquer quelle sera la valeur du paramètre d’un token. Ainsi, pour chaque évènement XXX historisé,il nous faudra une méthode onXXX qui renvoi une valeur sous forme de chaîne de caractères. Nous aurons ainsi par exemple :


public String  onEdit(String id){

      return id;

}

public String  onList(){

      return "";

}


Un reproche possible à faire à mvp4g est

Un des reproches possible à faire est l’assemble dynamique et instanciation des composants. Nous n’avons pas toujours besoin d’en disposer au moment au l’ont accède à l’application. Dans notre cas, nous n’avons pas besoin d’édition d’un contact tant que n’a pas cliqué sur un contact. Rassurez vous, la construction paresseuse est là. Pour cela, nous allons changer la super classe pour LazyPresenter<V extends LazyView, E extends EventBus>. En plus de cela, notre interface de vue devra étendre l’interface LazyView. Cela va perturber un peu notre code et des erreurs de compilation vont s’inviter. Avant de les corriger, intéressons nous à la classe LazyPresenter et en particulier sa méthode bind()


final public void bind() {

      view.createView();

      createPresenter();

      bindView();

}


Voilà la clé de nos corrections :

  • Les constucteurs de vue et de présenteur deviendront respectivement createView() et createPresenter()

  • La méthode bind() du présenteur deviendra bindView()

Personnellement, j’ai remarqué qu’après ses modifications, le lancement était plus rapide.

Le principal avantage d’un découpage mvp est la testabilité. Notre présenteur étant décorélé de problématique d’affichage, il sera alors plus simple de le tester. En effet, en mockant la vue, les services et le bus, nous pourrons nous concentrer nos tests sur la logique métier. De plus avec le système évènementiel haut niveau de mvp4g, il est simple de tester la réception d’un événement.

Un aspect qui peu rebuter les plus intégriste du modèle mvp est la gestion évènement de haut niveau spécifique de mvp4g et principalement ses performances. Quelles sont elles ? Le wiki du projet répond à cette question :

http://code.google.com/p/mvp4g/wiki/Mvp4gPerformances

Les performances semble honorables. Bien sûr, tout produit vante ses performances, mais bon joueur les tests sont disponibles.

La question à se poser avant tout est de savoir ce que l’on teste exactement. En observant le code, on voit qu’un évènement est lancé est attrapé par son réceptacle. Rien de plus. Pour éviter un conflit entre les deux, un test se fait en temps : d’abord mgp4g puis gwtEvent.

Pour la partie benchmarking, j’ai réalisé depuis le lien appspot les même tests sous 3 navigateurs dans leur version courante :

  • Chrome 6

  • Internet Explorer 8

  • Firefox 3.6

Mes résultats diffèrent, mais attention cela ne signifie pas les résultats sur la page mvp4g sont faux. En effet, pour avoir reproduit ces même tests sur différentes machines possédant le même navigateur, j’obtenais des résultats avec une différence assez importante (jusqu’à x 10 entre un bon pc fixe et un simple netbook) :

Voici mes résultats pour 1000 évènements lancé :



mvp4g

gwt

Chrome 6

31

122

Internet Explorer 8

477

3609

Firefox 3.6

206

312

Ces résultats vont dans le même sens, c’est à dire que le modèle événementiel de mvp4g est plus rapide que celui de gwt.

Bien sûr, GWT 2.1 va arriver avec son modèle MVP intégré. Quand il sortira, on pourra re-comparer les temps de réaction événementielle. En attendant, sa sortie vous pouvez vous amuser au mpv avec mpv4g, qui propose bien d’autres fonctionnalités encore comme les filtres d’évènements, la gestion du multi-module, ....


Le code source complet est joint à cet article.

lundi 13 septembre 2010

Faire une application JavaScript crawlable

Le problème des applications JavaScript est qu'elles ne sont pas crawlable, il est impossible pour un moteur de recherche d'avoir accès à toutes ses informations. Or les interfaces ajax se sont développé énormément depuis quelques années avec la mode du Web 2.0 et la technologie Ajax.

Google a fait une proposition et la mise en oeuvre dans son indexeur pour résoudre ce problème. Il en a parlé initialement sur le blog : http://googlewebmastercentral.blogspot.com/2009/10/proposal-for-making-ajax-crawlable.html, un site dédié à cette technique est maintenant disponible : http://code.google.com/web/ajaxcrawling/

Nous allons développé cette technique dans cet article que nous venons de mettre en place sur le site mobile de SFEIR qui a été réalisé en GWT avec le framework Modding.

Principe d'utilisation

Google part du constat que s’il rencontre un lien contenant une ancre commençant par le signe "!", ce lien ajax doit être indexé. Il va alors rappeler la page en prenant le nom de l'ancre et en la passant en paramètre de la page _escaped_fragment_=

Seulement les liens avec une ancre commençant par un "!" seront indexé, cela permet de contrôler l'indexation et ne pas se retrouver avec tous le contenu de notre application indexé par Google.

Exemple :
monsite.com/page.html#!url_ajax
Deviendra :
monsite.com/page.html?_escaped_fragment_=url_ajax

La première URL est l'URL publique, ce que l'on doit mettre en lien depuis tous les sites où l'on veux avoir un lien, l'application JavaScript doit donc ouvrir automatiquement la page url_ajax dans l'application à son ouverture.

La deuxième URL est utilisé uniquement par Google, et ne doit pas être mis en lien, elle doit retourner la version HTML de la page, c'est à dire ce qu'on aurait si on exportait l'HTML généré par notre application JavaScript si on va sur la première URL. Il est donc nécessaire que le serveur analyse l'URL et si il détecte le paramètre _escaped_fragment_, le serveur génére la version HTML de la page.

Google indexera ainsi le contenus de cette page HTML, mais dans les résultats du moteur de recherche, il affichera la première URL à l'utilisateur.

Mise en place dans une application JavaScript


Pour cela, il faut commencer par modifier son application JavaScript pour utiliser la gestion de l'Historique :
Pour les projets GWT, une api permet de le faire simplement en utilisant History : http://code.google.com/webtoolkit/doc/latest/DevGuideCodingBasicsHistory.html

History.getToken(); //Permet de récupérer le token
History.addValueChangeHandler(new ValueChangeHandler<String>() {
public onValueChange(ValueChangeEvent<String> event) {
event.getValue();
//Permet de récupérer les changements du token
}
};
History.newToken("!url_ajax"); //Ajoute un nouveau token



Notre framework Modding depuis sa version 0.3 est maintenant capable de démarrer une autre activité que l'activité principale lors du chargement de la page avec l'information dans l'url :
http://m.sfeir.com/#!com.sfeir.client.activity.PageActivity?hash=4&url=page%252Fjava.html


Ce liens lancera directement la page "Notre savoir faire / Expertise Java" sans afficher le premier menu disponible normalement :
http://m.sfeir.com


Un autre projet créé par Alexandre Thiel également chez SFEIR, permet de créer une application GWT en utilisant la notation Rest : http://code.google.com/p/restful-gwt/

Mise en place sur le serveur de la version HTML

Il faut maintenant modifier le serveur pour détecter que Google demande la version HTML ou pas, pour cela il faut détecter si le paramètre _escaped_fragment_ existe bien.
http://m.sfeir.com/?_escaped_fragment_=com.sfeir.client.activity.PageActivity?hash=4%26url=page%252Fjava.html

Pour cela, on peux facilement le faire en utilisant un filtre, qui vous permettre de choisir entre afficher la page habituelle ou la version HTML.

Pour cela, il est nécessaire de renommer le fichier html en jsp car il n'est pas possible en Java d'ajouter un filtre sur une page HTML. Ensuite, la configuration du fichier de configuration est très simple dans le fichier web.xml, en ajoutant simplement les balises filter et filter-mapping :
  <filter>
<filter-name>crawler</filter-name>
<filter-class>com.sfeir.server.CrawlerFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>crawler</filter-name>
<url-pattern>*</url-pattern>
</filter-mapping>

Voyons maintenant le fonctionnement de la classe :




Le filtre pour chaque requête, vérifie si le paramètre _escaped_fragment est présent, sinon il lance la chaine suivante des filtres avec le chain.doFilter(request, response);

Dans le cas où la requête est une requête de google pour indexer la page Ajax, on reconstruit l'url originale avec l'ancre ajax.

Il reste donc à produire le code HTML, pour cela, il y a deux stratégies, soit en fonction de l'url, produire la page html en utilisant les technologies habituelles de java : jsp, spring, velocity, etc ...
Soit utiliser un navigateur internet léger comme HtmlUnit pour construire la page directement en utilisant l'application JavaScript.

La deuxième approche conseillée par Google a le mérite d'être simple à mettre en place car on utilise le code de notre application. Voici un exemple d'utilisation avec HTMLUnit :
WebClient webClient = new WebClient(BrowserVersion.FIREFOX_3);
webClient.setThrowExceptionOnScriptError(false);
webClient.setJavaScriptEnabled(true);
HtmlPage page = webClient.getPage(pageName);
webClient.setTimeout(10000);
out.println(page.asXml());
webClient.closeAllWindows();


Le timeout dépend de votre application, et du temps nécessaires pour que le code javascript s'exécute, que les services soient appeler, et que le rendu final soit fait !
Cela peux donc prendre du temps et des ressources.

Pour le site mobile de sfeir, nous avons utilisé la première approche, car le site est hébergé sur Google App Engine, et la bibliothèque n'est pour l'instant pas compatible avec App Engine, le portage est en cours. De plus, nous sommes limité par le temps d'exécution des pages à 30 secondes.
Comme le site mobile est statique, réaliser la version HTML était assez facile, simplement à lire les fichiers HTML normalement chargés par la partie GWT et les écrirent dans la réponse du serveur.

Testons l'indexation

Cette article explique le fonctionnement préconisé par Google pour rendre ses applications indexable, et plus particulièrement les application GWT en Java.
C'est pour Sfeir, l'occasion de mettre en pratique et de lancer un test grandeur nature de ce référencement.

Merci de nous y aider en ajoutant des liens depuis vos sites vers une page particulière de notre site mobile pour tester l'indexation dans Google.

Cette expérimentation nous permettra ensuite de valider la technologie pour l'utiliser chez nos clients et vos clients.

- page 2 de 14 -