Google Web Toolkit

       David Herviou
         @herviou
       +david herviou
  david.herviou@gmail.com
Présentation
●   Généralités
●   Construire son UI en GWT
●   Internationalisation | i18n
●   Communication Evènementielle
●   Communication Client / Serveur
●   Pour aller plus loin
●   Bibliographie
Généralités
GWT
Qu'est ce que c'est ?
    Une boite à outil pour construire des
             applications WEB

             Codez en Java
    Obtenez une application Javascript

    Vise le développement industrialisé
     d'applications WEB performantes
GWT
Qu'est ce que c'est ?
Open source

          Maintenu par Google et la communité

Utilisé par des milliers de personnes

                       Ecosystème actif et riche
Grand Principes
● Les freins aux applications performantes
  ○ le nombre de requêtes
  ○ le volume des requêtes


● Approche GWT
  ○ Une application Javascript optimisée par Navigateur
  ○ Un bundle unique JS
  ○ Chacun son boulot :
    ■ Le Client s'occupe du rendu
    ■ le Serveur des données
         Approche orientée "client lourd"
Principes / Objectifs
● Les freins à la productivité
   ○ les spécificités d'implémentations des différents
     navigateurs
   ○ le debuggage du Javascript
   ○ la gestion des css spécifiques

● Approche GWT :
   ○ "don't care about web browser"
   ○ Code Java : debuggage Java
   ○ Librairie de widgets disponibles
Principes / Objectifs

Industrialisation
                      Java
     Abstraction
                               Performance

              Productivité
                             internationalisation
     Modularité
Organisation Projet GWT
la page Host
./war/MyApp.html
  ○ la page d'entrée de l'application GWT

  ○ contient un script MyApp.nocache.js

  ○ charge en dynamique l'application (code javascript)
    spécifique au navigateur


             Principe des permutations
Organisation Projet
Partie client/serveur
./src/my.package.myapp.client
  ○ Code de l'UI graphique et de son comportement
  ○ Code Java à transcrire en Javascript


./src/my.package.myapp.server
  ○ Code de la partie serveur gestion des données et
    des échanges avec les clients
  ○ Code Java à compiler en Bytecode
Module GWT
./src/my.package.myapp.MyApp.gwt.xml

  ○   configuration des librairies GWT du projet
  ○   configuration de l'internationalisation
  ○   configuration des navigateurs cibles
  ○   configuration des points d'entrées de l'applications
  ○   ...

  C'est l'élément de base pour le compilateur GWT
Module GWT
<?xml version="1.0" encoding="UTF-8"?>
<module rename-to='fr_dhu_gwt_slides'>             ● <module> : déclaration
 <!-- Inherit the core Web Toolkit stuff.-->
 <inherits name='com.google.gwt.user.User'/>       ● <inherits> : utilisation
 <!-- Inherit the default GWT style sheet.-->          d'autres modules
 <inherits name='com.google.gwt.user.theme.
clean.Clean'/>                                     ●   <entry-point> : classe
 <!-- Specify the app entry point class.-->            Java-démarrage de
 <entry-point class='fr.dhu.gwt.slides.client.         l'application
Slides'/>

 <!-- Specify the paths for translatable code-->
                                                   ●   <source> : code java à
 <source path='client'/>                               transcrire
 <!-- manage english-->
 <extend-property name="locale" value="en"/>
                                                   ●   <extend-property> :
</module>                                              extension d'une
                                                       propriété existante
Compilateur GWT
2 modes de fonctionnement
● Production
  ○ Code Java vers Code Javascript avec permutations
  ○ JRE Emulation:
        Classes et méthodes translatables ou ayant une implementation
                                  Javascript
  ○ Pour les curieux: com.google.gwt.dev.Compiler
● Développement ou DevMode
  ○ Accélérer la boucle coder/compiler/vérifier
  ○ Plugin GWT branché sur le navigateur
● Se base sur le contenu du module GWT
Compilateur GWT
permutations
    L'utilisateur final ne veut "payer" que ce qu'il
                        consomme
●   Améliorer le volume/nombre d'éléments
    ramener à l'utilisateur
●   Tenir compte des spécificités des
    implémentations/bugs des navigateurs
●   Tenir compte de l'internationalisation des
    applications
●   Offrir un mécanisme d'extension des
    spécificités applicatives
Compilateur GWT
permutations

● user.agent
  ○ par défaut : "ie6, ie8, safari, gecko1_8, safari, opera"
● locale
  ○ par défaut : "default"
● Nombres d'applications résultantes
      {user.agent}x{locale} = 6 permutations par défaut
● Possibilité de définir ses axes de permutations
    {user.agent}x{locale}x{deferred-binding properties}
Rapatriement d'une permutation
                        myapp_safari_es.js



  Code Java
    Client
              GWTC

                                 myapp_opera_de.js

                      WebApp




              JAVAC
  Code Java
   Serveur




                       myapp_gecko_fr.js
Compilateur GWT
Permutations et deferred Binding
 Comment cela fonctionne-t-il concrètement ?
               New Object()
            résolution statique

        MyObject.class.newInstance()
           résolution au runtime

        GWT.create(MyObject.class)
     résolution différée / classes clientes
Compilateur GWT
Permutations et deferred Binding
    Exemple de l'implementation d'une Popup
● l'utilisateur effectue
          PopupPanel myPopup = new PopupPanel();
● cette classe contient / wrap / ~pattern proxy
         PopupImpl p = GWT.create(PopupImpl.class)
● et le compilateur GWT à la règle :
<!-- Mozilla needs a different implementation due to issue #410 -->
<replace-with class="com.google.gwt.user.client.ui.impl.PopupImplMozilla">
    <when-type-is class="com.google.gwt.user.client.ui.impl.PopupImpl"/>
    <when-property-is name="user.agent" value="gecko1_8"/>
</replace-with>
Compilateur GWT
● Substitution d'implémentation

● Génération automatique de code

● Améliore la flexibilité du compilateur

● Facilite le développement du code spécifique
Construire son UI
    en GWT
Layout et Positionnement
● Organisation de l'affichage de l'UI
● RootPanel
   ○ RootPanel.get() : accès à l'élément <body> de l'arbre
     DOM
   ○ RootPanel.getId(String id) : accès à l'élément id du
     DOM
● FlowPanel, FormPanel, HTMLPanel,
  ScrollPanel, PopupPanel
   ○ Elements classique HTML
● {Dock, Split, Stack, Tab}LayoutPanel
   ○ Element HTML complexe
   ○ Structure globale de l'application
   ○ Structure d'éléments conséquents de l'application
Layout exemple
TabLayoutPanel tabPanel = new TabLayoutPanel(2.5, Unit.EM);

// Add a home tab
HTML homeText = new HTML(constants.cwTabPanelTab0());
tabPanel.add(homeText, "Accueil");

// Add a tab with an image
SimplePanel imageContainer = new SimplePanel();
imageContainer.setWidget(new Image(Showcase.images.gwtLogo()));
tabPanel.add(imageContainer, "Logo GWT");

// Add a tab
HTML moreInfo = new HTML(constants.cwTabPanelTab2());
tabPanel.add(moreInfo, "Plus d'info");

// Return the content
tabPanel.selectTab(0);
Widgets
● Tous ce que l'on peut trouver en HTML
  ○ button, radiobox, checkbox, listbox, textbox,
    textarea...
● Des éléments plus complexes
  ○   Datepicker
  ○   Tree
  ○   MenuBar
  ○   DisclosurePanel


 Les layouts et les widgets sont la base de la
construction d'une Application Web Riche GWT
Widgets exemples
 // Create a DateBox
DateTimeFormat dateFormat =
                DateTimeFormat.getLongDateFormat();
DateBox dateBox = new DateBox();
dateBox.setFormat(new DateBox.DefaultFormat(dateFormat));




// Create the text area and toolbar
RichTextArea area = new RichTextArea();
area.ensureDebugId("cwRichText-area");
area.setSize("100%", "14em");
RichTextToolbar toolbar =
                  new RichTextToolbar(area);
toolbar.setWidth("100%");
Créer ces propres composants
● En créant des Composite
  ○ aggrégation de widgets et autres Composites
● "from scratch"
  ○ Définition d'un nouveau Widget
● En wrappant du javascript existant
  ○ Encapsulation en Java d'un composant d'une
    bibliothèque tierce
  ○ Pas d'optimisation possible dans ce cas


La méthode la plus courante est l'utilisation des
                 Composite
Composite exemple
public class MyFirstComposite extends Composite {

     public MyFirstComposite(String label) {
         // create the widget container
          VerticalPanel fp = new VerticalPanel();

          // add a checkbox for choice
          fp.add(new CheckBox(label));

          // add a multiple choice box
          ListBox lb = new ListBox();
          lb.addItem("item 1");
          lb.addItem("item 2");
          fp.add(lb);

          // initialize widget
          initWidget(fp);
     }
}
                 somewhere else...

                 MyFirstComposite fc = new MyFirstComposite("Je prends l'option");
Comment du code Java donne de
l'HTML
Comment le code Java        /** Class FlowPanel **/
                            public FlowPanel() {
donne un arbre DOM ?          setElement(DOM.createDiv());
                            }
● Rappel :                  /** Class DOM **/
  ○ Java transcrit en       public static Element createDiv() {
                              return Document.get().createDivElement().cast();
    Javascript              }
● Javascript                /** Class Document **/
  ○ manipule et interagit   public final DivElement createDivElement() {
                              return (DivElement) DOMImpl.impl.createElement
    avec les noeuds DOM     (this, DivElement.TAG);
                            }
● Exemple avec le           /** Class DivElement **/
  FlowPanel                 static final String TAG = "div";
Interaction avec l'utilisateur
● Permettre à l'utilisateur d'interagir avec
  l'application
● Programmation principalement
  Evènementielle
● Notions principales à retenir :
   ○ Event : l'objet représentant l'évènement déclenché
   ○ Handler: definition du comportement à déclencher
     sur évènement
   ○ HandlerRegistration : gère l'abonnement d'un
     Handler à un Event
Interaction avec l'utilisateur
exemple
// create the widget container
VerticalPanel fp = new VerticalPanel();
..
..
..
// add a button
Button b = new Button("Send");
fp.add(b);

HandlerRegistration hr = b.addClickHandler(new ClickHandler() {
     @Override
     public void onClick(ClickEvent event) {
          Window.alert("OK");
     }
});

// initialize widget
initWidget(fp);
Construction déclarative d'interface
avec UIBinder
● Partie statique de l'UI :
   ○ Déclaratif
   ○ XML


● Séparation des responsabilités
   ○ UIBinder : UI statique
   ○ Java : UI dynamique et gestion des données


● Meilleure structuration de l'interface

● Interaction XML / Java
UIBinder exemple
               Notre Composite précédent devient
public class MyComposite extends Composite {
                                                         <ui:UiBinder xmlns:ui="urn:ui:com.google.gwt.
    private static MyCompositeUiBinder uiBinder =        uibinder"
        GWT.create(MyCompositeUiBinder.class);           xmlns:g="urn:import:com.google.gwt.user.client.ui">

    interface MyCompositeUiBinder                             <g:VerticalPanel>
        extends UiBinder<Widget, MyComposite> {}               <g:CheckBox text="Je prends l'option"/>
                                                               <g:ListBox>
     @UiField Button b;                                          <g:item>Item 1</g:item>
                                                                 <g:item>Item 2</g:item>
     public MyComposite() {                                    </g:ListBox>
           initWidget(uiBinder.createAndBindUi(this));         <g:Button text="Send" ui:field="b"/>
     }                                                        </g:VerticalPanel>

     @UiHandler({"b"})
                                                         </ui:UiBinder>
     public void onSend(ClickEvent event) {
           Window.alert("OK");
     }
}
Mise en forme avec CSS
● CSS : mise en forme du contenu HTML
  ○ implémentation spécifique suivant les navigateurs
  ○ peut représenter une part conséquente et complexe
    de la partie UI d'une application
● Approche classique :
  ○ 1 gros fichier CSS
  ○ peut modulaire
  ○ simple à mettre en oeuvre
● Approche GWT :
  ○   compatible avec l'approche classique MAIS
  ○   propose une approche modulaire
  ○   possibilité de manipuler le CSS depuis le code Java
  ○   L'idéal est d'éviter le CSS spécifique !
UI: CSS exemple "classique"
// create the widget container            .MyComposite {
VerticalPanel fp = new VerticalPanel();       border-style : solid;
fp.setStylePrimaryName("MyComposite");        border-width : thin;
...                                           override : hidden;
                                              padding : 0.5em;
// add a multiple choice box                  min-width : 200px;
ListBox lb = new ListBox();               }
...
fp.add(lb);                               .MyComposite-List {
lb.addStyleName("MyComposite-List");          margin-bottom : 1em;
                                              margin-top : 1em;
// add a button                           }
Button b = new Button("Send");
fp.add(b);                                .MyComposite-Button {
b.addStyleName("MyComposite-Button");         float : right;
                                          }
// initialize widget
initWidget(fp);
UI: CSS exemple GWT
<ui:UiBinder xmlns:ui="urn:ui:com.google.gwt.uibinder"
      xmlns:g="urn:import:com.google.gwt.user.client.ui">
      <ui:style field="style" >
             .MyComposite {
                     border-style : solid;
                     border-width : thin;
                     override : hidden;
                     padding : 0.5em;
                     min-width : 200px;
             }
             .MyComposite-List {
                     margin-bottom : 1em;
                     margin-top : 1em;
             }
             .MyComposite-Button {
                     float : right;
             }
      </ui:style>

       <g:VerticalPanel stylePrimaryName="{style.MyComposite}">
             <g:CheckBox text="Je prends l'option" />
             <g:ListBox styleName="{style.MyComposite-List}">
                    <g:item>Item 1</g:item>
                    <g:item>Item 2</g:item>
             </g:ListBox>
             <g:Button text="Send" ui:field="b" styleName="{style.MyComposite-Button}"/>
       </g:VerticalPanel>
</ui:UiBinder>
UI : CSS interaction Java
                                                     <ui:UiBinder
public class MyComposite extends Composite {         xmlns:ui="urn:ui:com.google.gwt.uibinder"
                                                     xmlns:g="urn:import:com.google.gwt.user.client.ui">
      ....
                                                           <ui:style field="myStyle"
      public interface Css extends CssResource {                    type="fr.dhu.gwt.client.MyComposite.Css">
             String dynamic();                                       .MyComposite {
      }                                                          ...
                                                               }
      @UiField Css myStyle;                                          .dynamic { border-color: red }
      @UiField VerticalPanel rootPanel;                    </ui:style>
      @UiField Button b;
                                                            <g:VerticalPanel
      ...                                                          stylePrimaryName="{myStyle.MyComposite}"
                                                                   ui:field="rootPanel">
      @UiHandler({"b"})                                           ...
      public void onSend(ClickEvent event) {                </g:VerticalPanel>
        Window.alert("OK");                          </ui:UiBinder>
        rootPanel.addStyleName(myStyle.dynamic());
      }
}
Gestion des images
● Souvent goulot d'étranglement des
  échanges client/serveur
● beaucoup d'overhead pour peu de contenu
● norme HTTP 1.1 : deux requêtes
  simultanées vers le même domain/port !
● Approche GWT
  ○ packager les images avec l'application
  ○ images encodées en base64
  ○ gestion native de l'internationalisation
Gestion des images exemple
public class MyComposite extends Composite {
     ...
     public interface MyImages extends ClientBundle {
           @Source("gwt-logo.png")
           ImageResource logoGWT();
     }


<ui:with type="fr.dhu.gwt.client.MyComposite.MyImages" field="images"></ui:with>
<ui:style field="myStyle" type="fr.dhu.gwt.client.MyComposite.Css">
         ...
      .MyComposite-Image {
             float : left;
       }
</ui:style>

<g:VerticalPanel ... >
      ...
     <g:FlowPanel>
       <g:Image resource="{images.logoGWT}" styleName="{myStyle.MyComposite-Image}"/>
      <g:Button ... ui:field="b" styleName="{myStyle.MyComposite-Button}"/>
     </g:FlowPanel>
</g:VerticalPanel>
Internationalisation
Internationalisation
● Gérer les langues est aujourd'hui
                Indispensable
● Gérer nativement dans GWT
  ○ basé sur la valeur 'locale' du navigateur
  ○ intégré au mécanisme des permutations
● Comment ?
  ○ Déclarer une interface XXXMessages qui étend
    Messages
  ○ Déclarer autant de XXXMessages_lg.properties que
    de langage gérer
  ○ Déclarer au compilateur GWT la gestion de ses
    nouvelles 'locale'
Internationalisation exemple
@DefaultLocale("fr")
public interface MyCompositeMessages extends           //File MyCompositeMessages_en.properties
Messages {                                             send=Send
                                                       selectOption=I select the option
     @DefaultMessage("Envoi")
     String send();

     @DefaultMessage("Je prends l''option")
     String selectOption();
}

<ui:UiBinder xmlns:ui="urn:ui:com.google.gwt.uibinder"
      xmlns:g="urn:import:com.google.gwt.user.client.ui">
      <ui:with type="fr.dhu.gwt.client.MyCompositeMessages" field="
messages"/>

     <ui:style .../>

      <g:VerticalPanel ... >
           <g:CheckBox text="{messages.selectOption}" />
           <g:ListBox ...>
           <g:Button text="{messages.send}" .../>
      </g:VerticalPanel>
</ui:UiBinder>                          //Module GWT
                                        <extend-property name="locale" values="fr,en"/>
Internationalisation des images
             Too simple !
                   ajouter
              logo-gwt_en.png
            au même endroit que
                logo-gwt.png
Communication
Evenènementielle
Gestion de l'historique
● Contexte : Une application GWT
  ○ pas de génération de plusieurs pages HTML
  ○ pas d'historique de navigation à priori


● Comment obtenir :
  ○ différents points d'entrées dans l'application
    ■ Application "Bookmarkable"
  ○ obtenir une simulation de navigation aller/retour
    ■ Simuler une application à plusieurs pages HTML
Gestion de l'historique
sur les application Javascript
    https://www.google.fr/#hl=fr&q=test

● partie de l'URL qui ne part pas jusqu'au
  serveur
● Interprétable par la partie cliente
● Sa modification inclu l'empilement dans
  l'historique du navigateur :
     https://www.google.fr/#hl=fr&q=test
    https://www.google.fr/#hl=fr&q=test1
Gestion de l'historique en GWT
● Une API dédiée : Classe History
● Notion d'évènements
    ○ History.newItem
● Notion d'Handler
   History.addValueChangeHandler(ValueChangeHandler<String> handler)

myValueChangeHandler = new ValueChangeHandler<String>() {
   @Override
   public void onValueChange(ValueChangeEvent<String> event) {
     // get URL fragment
      String token = event.getValue();
      // do what you want with the token
     ...
   }
}
Bus d'évènements
● Une application
  ○ un ensemble de widgets
  ○ un ensemble d'interaction
    ■ homme/UI
    ■ UI/serveur
    ■ UI/UI
● Interaction UI/UI : Bus logiciel d'évènement
    dispatcher un évènement depuis un widget vers un
             ensemble de cibles non connues
● Intérêts
  ○ Mettre à jour l'UI en réaction à un changement
  ○ Couplage lâche entre composant
Bus d'évènements exemple
// declare an Handler for our event
public interface MyEventHandler
  extends EventHandler {
                                                         ● Déclarer un Handler
  void onEvent(MyEvent event);                             ○ Contrat d'interface
}
                                                             pour les
// declare an Event for EventBus communication
public class MyEvent                                         consommateurs
  extends GwtEvent<MyEventHandler> {                     ● Déclarer un Event
    /** TYPE of this event */                              ○ Donnée d'échange
     public static final Type<MyEventHandler> TYPE =
        new Type<MyEventHandler>();                          entre le producteur et
     @Override
                                                             les consommateurs
     public Type<MyEventHandler> getAssociatedType() {     ○ Un Event = Un TYPE
       return TYPE;
    }

    @Override
    protected void dispatch(MyEventHandler handler) {
      handler.onEvent(this);
    }
}
Bus d'évènements exemple
// One single Bus (Singleton pattern)
// to fire event onto and triggering Handler s
SimpleEventBus singleBus = new SimpleEventBus();

// One widget is registering on application EventBus   ●   Déclarer un Bus unique
HandlerRegistration hr =
 singleBus.addHandler(                                     pour l'applicatoin
    MyEvent.TYPE,
    new MyEventHandler() {
                                                       ●   Les widgets s'abonnent
        @Override                                          au TYPE d'évènements
        public void onEvent(MyEvent event) {
              // do what you want with event               souhaité
        }
});                                                    ●   Les widgets producteurs
// Another one is triggering event
                                                           émettent les évènements
singleBus.fireEvent(new MyEvent());                    ●   Les évènements sont
                                                           propagés par le bus
                                                           jusqu'aux abonnés
Communication
Client / Serveur
Communication Client/Serveur
Principe d'Asynchronisme
● Permettre d'échanger des données entre l'UI
  et le serveur
● Deux possibilités :
  ○ Synchrone : fonctionnement sur les applications non
    Javascript
  ○ Asynchrone : peut être un fonctionnement sur les
    applications Javascript
● Asynchronisme
  ○ Avoir une application réactive
  ○ Peut être complexe à mettre en oeuvre :
   multiples navigateurs - multiples implémentation de XHR
Echange Client/Serveur:
GWT-RPC
● Framework de communication Asynchrone

● Masque les complexités d'implémentation
  des navigateurs

● Permet l'échange de données complexes

● Facile à mettre en oeuvre
GWT-RPC exemple
● une interface Synchrone extends
  RemoteService
● une interface Asynchrone pour le côté client
      ○ Nomenclature : SynchronousInterfaceAsync
// declare synchronous version of service
@RemoteServiceRelativePath("greet")
public interface GreetingService extends RemoteService {
      String greetServer(String name) throws IllegalArgumentException;
}



// declare asynchronous version of service
public interface GreetingServiceAsync {
      void greetServer(String input, AsyncCallback<String> callback) throws llegalArgumentException;
}
GWT-RPC
exemple Côté Client
● Instanciation du service
      ○ 1 fois pour toute l'application
      ○ Utiliser un pattern Factory
GreetingServiceAsync service = GWT.create(GreetingService.class);


● Utilisation du service
service.greetServer("David", new AsyncCallback<String>() {

      @Override
      public void onSuccess(String result) {
           Window.alert(result);
      }

      @Override
      public void onFailure(Throwable caught) {
           Window.alert("error while calling Greeting Service");
      }
});
GWT-RPC
exemple Côté Serveur
● Implementation du service côté serveur
     ○ extends RemoteServiceServlet
     ○ implemente la version synchrone du service
// Service Implementation server side
public class GreetingServiceImpl extends RemoteServiceServlet implements GreetingService {
      public String greetServer(String input) throws IllegalArgumentException {
        return "hello "+input;
    }
}

● Modification du descripteur de déploiement
<servlet>
 <servlet-name>greetServlet</servlet-name>
 <servlet-class>fr.dhu.gwt.server.GreetingServiceImpl</servlet-class>
</servlet>

<servlet-mapping>
 <servlet-name>greetServlet</servlet-name>
 <url-pattern>/dhu/greet</url-pattern>
</servlet-mapping>
Pour aller plus loin
Framework GWT depuis la v2.x
● Gestion des Places et Activities
  ○ controle de la navigation dans l'application
  ○ formalise l'utilisation du fragment d'URL
● Gestion du Pattern MVP
  ○ Model/Vue/Controller
  ○ Améliore les tests de l'application cliente
● Gestion des applications orientées données
  ○ Ramener les actions sur les bases de données côté
    navigateur
  ○ RequestFactory
Sécurité
● La sécurité se prend en compte à partir du
  développement
● XSS:
  ○ Attaque par injection de données dans l'application
  ○ SafeHTML
  ○ escaping des données pouvant être injectées
● XSRF:
  ○ Attaque depuis un site tiers vers le site cible utilisant
    votre précédente authentification sur le site cible
  ○ GWT-RPC avec Token
    ■ génération unique d'Id pour une appel RPC
    ■ deux appels séquentiels pour forger une requête
framework Editor
● Souvent fastidieux de faire du code "passe
  plat"
            myModel.setField1Value(myUI.getField1Value());
            myModel.setField2Value(myUI.getField2Value());
                                 ...
            myModel.setField3Value(myUI.getField3Value());

            ou faire l'inverse !
● Framework GWT Editor
  ○ Automatise le mapping modèle de donnée vers UI et
    vice versa
  ○ Basé sur des conventions de nommages des
    champs
Compatiblité HTML5
● N'oubliez pas Java -> Javascript donc
  génération d'HTML
● Possibilité également d'écrire de l'HTML en
  GWT
  ○ avec des méthodes type setInnerHTML
  ○ avec l'HTMLPanel
  ○ Attention à la compatibilité multi-navigateur
● Des éléments HTML5 sont déjà gérés en
  GWT
  ○ ClientSideStorage
  ○ Audio, Video
  ○ Canvas
Optimisations
● Amélioration par observation statique
  ○ soycReport : Rapport de compilation
  ○ permet d'identifier les gros volumes de code
  ○ permet d'identifier éventuellement les points de
    modularisation
● Amélioration par observation dynamique
  ○ Lancer l'application et utiliser SpeedTracer ou
    Firebug
● Modulariser l'application
  ○ Casser le monolythe applicatif
  ○ Télécharger des bouts de code Javascript à la
    demande
Ecosystème
Quelques projets qui pourront vous intérresser
● m-gwt : portage pour tablette et mobile
● sencha gxt : framework et librairie de
  composant GWT
● gwt-platform : framework autour de GWT
● vaadin : framework autour de GWT
● playN : cross-platform pour jeux basé sur
  GWT
● Gin : framework d'injections de dépendance
● ... et pleins d'autres encore
Bibliographie
La bible sur GWT
https://developers.google.com/web-toolkit/doc/latest/DevGuide

Gwt fast overview_v1

  • 1.
    Google Web Toolkit David Herviou @herviou +david herviou david.herviou@gmail.com
  • 2.
    Présentation ● Généralités ● Construire son UI en GWT ● Internationalisation | i18n ● Communication Evènementielle ● Communication Client / Serveur ● Pour aller plus loin ● Bibliographie
  • 3.
  • 4.
    GWT Qu'est ce quec'est ? Une boite à outil pour construire des applications WEB Codez en Java Obtenez une application Javascript Vise le développement industrialisé d'applications WEB performantes
  • 5.
    GWT Qu'est ce quec'est ? Open source Maintenu par Google et la communité Utilisé par des milliers de personnes Ecosystème actif et riche
  • 6.
    Grand Principes ● Lesfreins aux applications performantes ○ le nombre de requêtes ○ le volume des requêtes ● Approche GWT ○ Une application Javascript optimisée par Navigateur ○ Un bundle unique JS ○ Chacun son boulot : ■ Le Client s'occupe du rendu ■ le Serveur des données Approche orientée "client lourd"
  • 7.
    Principes / Objectifs ●Les freins à la productivité ○ les spécificités d'implémentations des différents navigateurs ○ le debuggage du Javascript ○ la gestion des css spécifiques ● Approche GWT : ○ "don't care about web browser" ○ Code Java : debuggage Java ○ Librairie de widgets disponibles
  • 8.
    Principes / Objectifs Industrialisation Java Abstraction Performance Productivité internationalisation Modularité
  • 9.
    Organisation Projet GWT lapage Host ./war/MyApp.html ○ la page d'entrée de l'application GWT ○ contient un script MyApp.nocache.js ○ charge en dynamique l'application (code javascript) spécifique au navigateur Principe des permutations
  • 10.
    Organisation Projet Partie client/serveur ./src/my.package.myapp.client ○ Code de l'UI graphique et de son comportement ○ Code Java à transcrire en Javascript ./src/my.package.myapp.server ○ Code de la partie serveur gestion des données et des échanges avec les clients ○ Code Java à compiler en Bytecode
  • 11.
    Module GWT ./src/my.package.myapp.MyApp.gwt.xml ○ configuration des librairies GWT du projet ○ configuration de l'internationalisation ○ configuration des navigateurs cibles ○ configuration des points d'entrées de l'applications ○ ... C'est l'élément de base pour le compilateur GWT
  • 12.
    Module GWT <?xml version="1.0"encoding="UTF-8"?> <module rename-to='fr_dhu_gwt_slides'> ● <module> : déclaration <!-- Inherit the core Web Toolkit stuff.--> <inherits name='com.google.gwt.user.User'/> ● <inherits> : utilisation <!-- Inherit the default GWT style sheet.--> d'autres modules <inherits name='com.google.gwt.user.theme. clean.Clean'/> ● <entry-point> : classe <!-- Specify the app entry point class.--> Java-démarrage de <entry-point class='fr.dhu.gwt.slides.client. l'application Slides'/> <!-- Specify the paths for translatable code--> ● <source> : code java à <source path='client'/> transcrire <!-- manage english--> <extend-property name="locale" value="en"/> ● <extend-property> : </module> extension d'une propriété existante
  • 13.
    Compilateur GWT 2 modesde fonctionnement ● Production ○ Code Java vers Code Javascript avec permutations ○ JRE Emulation: Classes et méthodes translatables ou ayant une implementation Javascript ○ Pour les curieux: com.google.gwt.dev.Compiler ● Développement ou DevMode ○ Accélérer la boucle coder/compiler/vérifier ○ Plugin GWT branché sur le navigateur ● Se base sur le contenu du module GWT
  • 14.
    Compilateur GWT permutations L'utilisateur final ne veut "payer" que ce qu'il consomme ● Améliorer le volume/nombre d'éléments ramener à l'utilisateur ● Tenir compte des spécificités des implémentations/bugs des navigateurs ● Tenir compte de l'internationalisation des applications ● Offrir un mécanisme d'extension des spécificités applicatives
  • 15.
    Compilateur GWT permutations ● user.agent ○ par défaut : "ie6, ie8, safari, gecko1_8, safari, opera" ● locale ○ par défaut : "default" ● Nombres d'applications résultantes {user.agent}x{locale} = 6 permutations par défaut ● Possibilité de définir ses axes de permutations {user.agent}x{locale}x{deferred-binding properties}
  • 16.
    Rapatriement d'une permutation myapp_safari_es.js Code Java Client GWTC myapp_opera_de.js WebApp JAVAC Code Java Serveur myapp_gecko_fr.js
  • 17.
    Compilateur GWT Permutations etdeferred Binding Comment cela fonctionne-t-il concrètement ? New Object() résolution statique MyObject.class.newInstance() résolution au runtime GWT.create(MyObject.class) résolution différée / classes clientes
  • 18.
    Compilateur GWT Permutations etdeferred Binding Exemple de l'implementation d'une Popup ● l'utilisateur effectue PopupPanel myPopup = new PopupPanel(); ● cette classe contient / wrap / ~pattern proxy PopupImpl p = GWT.create(PopupImpl.class) ● et le compilateur GWT à la règle : <!-- Mozilla needs a different implementation due to issue #410 --> <replace-with class="com.google.gwt.user.client.ui.impl.PopupImplMozilla"> <when-type-is class="com.google.gwt.user.client.ui.impl.PopupImpl"/> <when-property-is name="user.agent" value="gecko1_8"/> </replace-with>
  • 19.
    Compilateur GWT ● Substitutiond'implémentation ● Génération automatique de code ● Améliore la flexibilité du compilateur ● Facilite le développement du code spécifique
  • 20.
  • 21.
    Layout et Positionnement ●Organisation de l'affichage de l'UI ● RootPanel ○ RootPanel.get() : accès à l'élément <body> de l'arbre DOM ○ RootPanel.getId(String id) : accès à l'élément id du DOM ● FlowPanel, FormPanel, HTMLPanel, ScrollPanel, PopupPanel ○ Elements classique HTML ● {Dock, Split, Stack, Tab}LayoutPanel ○ Element HTML complexe ○ Structure globale de l'application ○ Structure d'éléments conséquents de l'application
  • 22.
    Layout exemple TabLayoutPanel tabPanel= new TabLayoutPanel(2.5, Unit.EM); // Add a home tab HTML homeText = new HTML(constants.cwTabPanelTab0()); tabPanel.add(homeText, "Accueil"); // Add a tab with an image SimplePanel imageContainer = new SimplePanel(); imageContainer.setWidget(new Image(Showcase.images.gwtLogo())); tabPanel.add(imageContainer, "Logo GWT"); // Add a tab HTML moreInfo = new HTML(constants.cwTabPanelTab2()); tabPanel.add(moreInfo, "Plus d'info"); // Return the content tabPanel.selectTab(0);
  • 23.
    Widgets ● Tous ceque l'on peut trouver en HTML ○ button, radiobox, checkbox, listbox, textbox, textarea... ● Des éléments plus complexes ○ Datepicker ○ Tree ○ MenuBar ○ DisclosurePanel Les layouts et les widgets sont la base de la construction d'une Application Web Riche GWT
  • 24.
    Widgets exemples //Create a DateBox DateTimeFormat dateFormat = DateTimeFormat.getLongDateFormat(); DateBox dateBox = new DateBox(); dateBox.setFormat(new DateBox.DefaultFormat(dateFormat)); // Create the text area and toolbar RichTextArea area = new RichTextArea(); area.ensureDebugId("cwRichText-area"); area.setSize("100%", "14em"); RichTextToolbar toolbar = new RichTextToolbar(area); toolbar.setWidth("100%");
  • 25.
    Créer ces proprescomposants ● En créant des Composite ○ aggrégation de widgets et autres Composites ● "from scratch" ○ Définition d'un nouveau Widget ● En wrappant du javascript existant ○ Encapsulation en Java d'un composant d'une bibliothèque tierce ○ Pas d'optimisation possible dans ce cas La méthode la plus courante est l'utilisation des Composite
  • 26.
    Composite exemple public classMyFirstComposite extends Composite { public MyFirstComposite(String label) { // create the widget container VerticalPanel fp = new VerticalPanel(); // add a checkbox for choice fp.add(new CheckBox(label)); // add a multiple choice box ListBox lb = new ListBox(); lb.addItem("item 1"); lb.addItem("item 2"); fp.add(lb); // initialize widget initWidget(fp); } } somewhere else... MyFirstComposite fc = new MyFirstComposite("Je prends l'option");
  • 27.
    Comment du codeJava donne de l'HTML Comment le code Java /** Class FlowPanel **/ public FlowPanel() { donne un arbre DOM ? setElement(DOM.createDiv()); } ● Rappel : /** Class DOM **/ ○ Java transcrit en public static Element createDiv() { return Document.get().createDivElement().cast(); Javascript } ● Javascript /** Class Document **/ ○ manipule et interagit public final DivElement createDivElement() { return (DivElement) DOMImpl.impl.createElement avec les noeuds DOM (this, DivElement.TAG); } ● Exemple avec le /** Class DivElement **/ FlowPanel static final String TAG = "div";
  • 28.
    Interaction avec l'utilisateur ●Permettre à l'utilisateur d'interagir avec l'application ● Programmation principalement Evènementielle ● Notions principales à retenir : ○ Event : l'objet représentant l'évènement déclenché ○ Handler: definition du comportement à déclencher sur évènement ○ HandlerRegistration : gère l'abonnement d'un Handler à un Event
  • 29.
    Interaction avec l'utilisateur exemple //create the widget container VerticalPanel fp = new VerticalPanel(); .. .. .. // add a button Button b = new Button("Send"); fp.add(b); HandlerRegistration hr = b.addClickHandler(new ClickHandler() { @Override public void onClick(ClickEvent event) { Window.alert("OK"); } }); // initialize widget initWidget(fp);
  • 30.
    Construction déclarative d'interface avecUIBinder ● Partie statique de l'UI : ○ Déclaratif ○ XML ● Séparation des responsabilités ○ UIBinder : UI statique ○ Java : UI dynamique et gestion des données ● Meilleure structuration de l'interface ● Interaction XML / Java
  • 31.
    UIBinder exemple Notre Composite précédent devient public class MyComposite extends Composite { <ui:UiBinder xmlns:ui="urn:ui:com.google.gwt. private static MyCompositeUiBinder uiBinder = uibinder" GWT.create(MyCompositeUiBinder.class); xmlns:g="urn:import:com.google.gwt.user.client.ui"> interface MyCompositeUiBinder <g:VerticalPanel> extends UiBinder<Widget, MyComposite> {} <g:CheckBox text="Je prends l'option"/> <g:ListBox> @UiField Button b; <g:item>Item 1</g:item> <g:item>Item 2</g:item> public MyComposite() { </g:ListBox> initWidget(uiBinder.createAndBindUi(this)); <g:Button text="Send" ui:field="b"/> } </g:VerticalPanel> @UiHandler({"b"}) </ui:UiBinder> public void onSend(ClickEvent event) { Window.alert("OK"); } }
  • 32.
    Mise en formeavec CSS ● CSS : mise en forme du contenu HTML ○ implémentation spécifique suivant les navigateurs ○ peut représenter une part conséquente et complexe de la partie UI d'une application ● Approche classique : ○ 1 gros fichier CSS ○ peut modulaire ○ simple à mettre en oeuvre ● Approche GWT : ○ compatible avec l'approche classique MAIS ○ propose une approche modulaire ○ possibilité de manipuler le CSS depuis le code Java ○ L'idéal est d'éviter le CSS spécifique !
  • 33.
    UI: CSS exemple"classique" // create the widget container .MyComposite { VerticalPanel fp = new VerticalPanel(); border-style : solid; fp.setStylePrimaryName("MyComposite"); border-width : thin; ... override : hidden; padding : 0.5em; // add a multiple choice box min-width : 200px; ListBox lb = new ListBox(); } ... fp.add(lb); .MyComposite-List { lb.addStyleName("MyComposite-List"); margin-bottom : 1em; margin-top : 1em; // add a button } Button b = new Button("Send"); fp.add(b); .MyComposite-Button { b.addStyleName("MyComposite-Button"); float : right; } // initialize widget initWidget(fp);
  • 34.
    UI: CSS exempleGWT <ui:UiBinder xmlns:ui="urn:ui:com.google.gwt.uibinder" xmlns:g="urn:import:com.google.gwt.user.client.ui"> <ui:style field="style" > .MyComposite { border-style : solid; border-width : thin; override : hidden; padding : 0.5em; min-width : 200px; } .MyComposite-List { margin-bottom : 1em; margin-top : 1em; } .MyComposite-Button { float : right; } </ui:style> <g:VerticalPanel stylePrimaryName="{style.MyComposite}"> <g:CheckBox text="Je prends l'option" /> <g:ListBox styleName="{style.MyComposite-List}"> <g:item>Item 1</g:item> <g:item>Item 2</g:item> </g:ListBox> <g:Button text="Send" ui:field="b" styleName="{style.MyComposite-Button}"/> </g:VerticalPanel> </ui:UiBinder>
  • 35.
    UI : CSSinteraction Java <ui:UiBinder public class MyComposite extends Composite { xmlns:ui="urn:ui:com.google.gwt.uibinder" xmlns:g="urn:import:com.google.gwt.user.client.ui"> .... <ui:style field="myStyle" public interface Css extends CssResource { type="fr.dhu.gwt.client.MyComposite.Css"> String dynamic(); .MyComposite { } ... } @UiField Css myStyle; .dynamic { border-color: red } @UiField VerticalPanel rootPanel; </ui:style> @UiField Button b; <g:VerticalPanel ... stylePrimaryName="{myStyle.MyComposite}" ui:field="rootPanel"> @UiHandler({"b"}) ... public void onSend(ClickEvent event) { </g:VerticalPanel> Window.alert("OK"); </ui:UiBinder> rootPanel.addStyleName(myStyle.dynamic()); } }
  • 36.
    Gestion des images ●Souvent goulot d'étranglement des échanges client/serveur ● beaucoup d'overhead pour peu de contenu ● norme HTTP 1.1 : deux requêtes simultanées vers le même domain/port ! ● Approche GWT ○ packager les images avec l'application ○ images encodées en base64 ○ gestion native de l'internationalisation
  • 37.
    Gestion des imagesexemple public class MyComposite extends Composite { ... public interface MyImages extends ClientBundle { @Source("gwt-logo.png") ImageResource logoGWT(); } <ui:with type="fr.dhu.gwt.client.MyComposite.MyImages" field="images"></ui:with> <ui:style field="myStyle" type="fr.dhu.gwt.client.MyComposite.Css"> ... .MyComposite-Image { float : left; } </ui:style> <g:VerticalPanel ... > ... <g:FlowPanel> <g:Image resource="{images.logoGWT}" styleName="{myStyle.MyComposite-Image}"/> <g:Button ... ui:field="b" styleName="{myStyle.MyComposite-Button}"/> </g:FlowPanel> </g:VerticalPanel>
  • 38.
  • 39.
    Internationalisation ● Gérer leslangues est aujourd'hui Indispensable ● Gérer nativement dans GWT ○ basé sur la valeur 'locale' du navigateur ○ intégré au mécanisme des permutations ● Comment ? ○ Déclarer une interface XXXMessages qui étend Messages ○ Déclarer autant de XXXMessages_lg.properties que de langage gérer ○ Déclarer au compilateur GWT la gestion de ses nouvelles 'locale'
  • 40.
    Internationalisation exemple @DefaultLocale("fr") public interfaceMyCompositeMessages extends //File MyCompositeMessages_en.properties Messages { send=Send selectOption=I select the option @DefaultMessage("Envoi") String send(); @DefaultMessage("Je prends l''option") String selectOption(); } <ui:UiBinder xmlns:ui="urn:ui:com.google.gwt.uibinder" xmlns:g="urn:import:com.google.gwt.user.client.ui"> <ui:with type="fr.dhu.gwt.client.MyCompositeMessages" field=" messages"/> <ui:style .../> <g:VerticalPanel ... > <g:CheckBox text="{messages.selectOption}" /> <g:ListBox ...> <g:Button text="{messages.send}" .../> </g:VerticalPanel> </ui:UiBinder> //Module GWT <extend-property name="locale" values="fr,en"/>
  • 41.
    Internationalisation des images Too simple ! ajouter logo-gwt_en.png au même endroit que logo-gwt.png
  • 42.
  • 43.
    Gestion de l'historique ●Contexte : Une application GWT ○ pas de génération de plusieurs pages HTML ○ pas d'historique de navigation à priori ● Comment obtenir : ○ différents points d'entrées dans l'application ■ Application "Bookmarkable" ○ obtenir une simulation de navigation aller/retour ■ Simuler une application à plusieurs pages HTML
  • 44.
    Gestion de l'historique surles application Javascript https://www.google.fr/#hl=fr&q=test ● partie de l'URL qui ne part pas jusqu'au serveur ● Interprétable par la partie cliente ● Sa modification inclu l'empilement dans l'historique du navigateur : https://www.google.fr/#hl=fr&q=test https://www.google.fr/#hl=fr&q=test1
  • 45.
    Gestion de l'historiqueen GWT ● Une API dédiée : Classe History ● Notion d'évènements ○ History.newItem ● Notion d'Handler History.addValueChangeHandler(ValueChangeHandler<String> handler) myValueChangeHandler = new ValueChangeHandler<String>() { @Override public void onValueChange(ValueChangeEvent<String> event) { // get URL fragment String token = event.getValue(); // do what you want with the token ... } }
  • 46.
    Bus d'évènements ● Uneapplication ○ un ensemble de widgets ○ un ensemble d'interaction ■ homme/UI ■ UI/serveur ■ UI/UI ● Interaction UI/UI : Bus logiciel d'évènement dispatcher un évènement depuis un widget vers un ensemble de cibles non connues ● Intérêts ○ Mettre à jour l'UI en réaction à un changement ○ Couplage lâche entre composant
  • 47.
    Bus d'évènements exemple //declare an Handler for our event public interface MyEventHandler extends EventHandler { ● Déclarer un Handler void onEvent(MyEvent event); ○ Contrat d'interface } pour les // declare an Event for EventBus communication public class MyEvent consommateurs extends GwtEvent<MyEventHandler> { ● Déclarer un Event /** TYPE of this event */ ○ Donnée d'échange public static final Type<MyEventHandler> TYPE = new Type<MyEventHandler>(); entre le producteur et @Override les consommateurs public Type<MyEventHandler> getAssociatedType() { ○ Un Event = Un TYPE return TYPE; } @Override protected void dispatch(MyEventHandler handler) { handler.onEvent(this); } }
  • 48.
    Bus d'évènements exemple //One single Bus (Singleton pattern) // to fire event onto and triggering Handler s SimpleEventBus singleBus = new SimpleEventBus(); // One widget is registering on application EventBus ● Déclarer un Bus unique HandlerRegistration hr = singleBus.addHandler( pour l'applicatoin MyEvent.TYPE, new MyEventHandler() { ● Les widgets s'abonnent @Override au TYPE d'évènements public void onEvent(MyEvent event) { // do what you want with event souhaité } }); ● Les widgets producteurs // Another one is triggering event émettent les évènements singleBus.fireEvent(new MyEvent()); ● Les évènements sont propagés par le bus jusqu'aux abonnés
  • 49.
  • 50.
    Communication Client/Serveur Principe d'Asynchronisme ●Permettre d'échanger des données entre l'UI et le serveur ● Deux possibilités : ○ Synchrone : fonctionnement sur les applications non Javascript ○ Asynchrone : peut être un fonctionnement sur les applications Javascript ● Asynchronisme ○ Avoir une application réactive ○ Peut être complexe à mettre en oeuvre : multiples navigateurs - multiples implémentation de XHR
  • 51.
    Echange Client/Serveur: GWT-RPC ● Frameworkde communication Asynchrone ● Masque les complexités d'implémentation des navigateurs ● Permet l'échange de données complexes ● Facile à mettre en oeuvre
  • 52.
    GWT-RPC exemple ● uneinterface Synchrone extends RemoteService ● une interface Asynchrone pour le côté client ○ Nomenclature : SynchronousInterfaceAsync // declare synchronous version of service @RemoteServiceRelativePath("greet") public interface GreetingService extends RemoteService { String greetServer(String name) throws IllegalArgumentException; } // declare asynchronous version of service public interface GreetingServiceAsync { void greetServer(String input, AsyncCallback<String> callback) throws llegalArgumentException; }
  • 53.
    GWT-RPC exemple Côté Client ●Instanciation du service ○ 1 fois pour toute l'application ○ Utiliser un pattern Factory GreetingServiceAsync service = GWT.create(GreetingService.class); ● Utilisation du service service.greetServer("David", new AsyncCallback<String>() { @Override public void onSuccess(String result) { Window.alert(result); } @Override public void onFailure(Throwable caught) { Window.alert("error while calling Greeting Service"); } });
  • 54.
    GWT-RPC exemple Côté Serveur ●Implementation du service côté serveur ○ extends RemoteServiceServlet ○ implemente la version synchrone du service // Service Implementation server side public class GreetingServiceImpl extends RemoteServiceServlet implements GreetingService { public String greetServer(String input) throws IllegalArgumentException { return "hello "+input; } } ● Modification du descripteur de déploiement <servlet> <servlet-name>greetServlet</servlet-name> <servlet-class>fr.dhu.gwt.server.GreetingServiceImpl</servlet-class> </servlet> <servlet-mapping> <servlet-name>greetServlet</servlet-name> <url-pattern>/dhu/greet</url-pattern> </servlet-mapping>
  • 55.
  • 56.
    Framework GWT depuisla v2.x ● Gestion des Places et Activities ○ controle de la navigation dans l'application ○ formalise l'utilisation du fragment d'URL ● Gestion du Pattern MVP ○ Model/Vue/Controller ○ Améliore les tests de l'application cliente ● Gestion des applications orientées données ○ Ramener les actions sur les bases de données côté navigateur ○ RequestFactory
  • 57.
    Sécurité ● La sécuritése prend en compte à partir du développement ● XSS: ○ Attaque par injection de données dans l'application ○ SafeHTML ○ escaping des données pouvant être injectées ● XSRF: ○ Attaque depuis un site tiers vers le site cible utilisant votre précédente authentification sur le site cible ○ GWT-RPC avec Token ■ génération unique d'Id pour une appel RPC ■ deux appels séquentiels pour forger une requête
  • 58.
    framework Editor ● Souventfastidieux de faire du code "passe plat" myModel.setField1Value(myUI.getField1Value()); myModel.setField2Value(myUI.getField2Value()); ... myModel.setField3Value(myUI.getField3Value()); ou faire l'inverse ! ● Framework GWT Editor ○ Automatise le mapping modèle de donnée vers UI et vice versa ○ Basé sur des conventions de nommages des champs
  • 59.
    Compatiblité HTML5 ● N'oubliezpas Java -> Javascript donc génération d'HTML ● Possibilité également d'écrire de l'HTML en GWT ○ avec des méthodes type setInnerHTML ○ avec l'HTMLPanel ○ Attention à la compatibilité multi-navigateur ● Des éléments HTML5 sont déjà gérés en GWT ○ ClientSideStorage ○ Audio, Video ○ Canvas
  • 60.
    Optimisations ● Amélioration parobservation statique ○ soycReport : Rapport de compilation ○ permet d'identifier les gros volumes de code ○ permet d'identifier éventuellement les points de modularisation ● Amélioration par observation dynamique ○ Lancer l'application et utiliser SpeedTracer ou Firebug ● Modulariser l'application ○ Casser le monolythe applicatif ○ Télécharger des bouts de code Javascript à la demande
  • 61.
    Ecosystème Quelques projets quipourront vous intérresser ● m-gwt : portage pour tablette et mobile ● sencha gxt : framework et librairie de composant GWT ● gwt-platform : framework autour de GWT ● vaadin : framework autour de GWT ● playN : cross-platform pour jeux basé sur GWT ● Gin : framework d'injections de dépendance ● ... et pleins d'autres encore
  • 62.
    Bibliographie La bible surGWT https://developers.google.com/web-toolkit/doc/latest/DevGuide