Accueil > Code > Astuces GWT et GWTQuery

Astuces GWT et GWTQuery

jeudi 23 février 2012, par Nicolas

Introduction

Après utilisation assez intensive de GWT et GWTQuery sur un projet, voici quelques trucs et astuces.

De façon générale je trouve GWTQuery assez bien fait (cela rend le code assez concis), mais la documentation est parfois lacunaire...

Appel de code GWT depuis du JavaScript natif

Il est à travers le mot-clé native assez simple d’appeler du JavaScript natif depuis GWT.

Par contre, dans l’autre sens, appeler du GWT depuis le JavaScript natif n’est pas très bien documenté.

Un scénario typique est l’appel d’une fonction d’un SDK en JavaScript qui attend une fonction de rappel à la fin d’une opération. Tant qu’à faire, si cette fonction peut aussi être en GWT, c’est mieux !

Heureusement, il y a des solutions, par exemple celle exposée dans ce fil de discussion.

La première étape est bien sûr de définir la fonction que l’on souhaite exposer :

package org.weeger;
class External {
(...)
  /**
   * Callback used when coming back from the SDK.
   * @param reply reply from the SDK.
   */

  protected static void callback(String reply) {
  (do something)
  }
(...)

Cette fonction est statique puisqu’appelée hors contexte.

La seconde étape est d’exposer cette fonction au JavaScript natif [1] :

  /**
   * Expose the callback to the native JavaScript.
   */

  protected native void expose()/*-{
    $wnd.gwtcallback = function(param){
         @org.weeger::callback(*)(param);
    }
  }-*/
;

Il est possible qu’il faille remplacer le (*) par autre chose, mais cela n’a pas semblé déranger dans mes exemples. Je me demande notamment comment cela se passe avec des tableaux, etc. Peut-être via un JavaScriptObject.

Enfin la dernière étape est d’appeler la fonction du SDK, en utilisant la fonction définie précédemment comme rappel :

  /**
   * Call the native SDK.
   * @param text what the user entered.
   */

  protected native void callNative(String text) /*-{
     $wnd.NativeSDK.method(text, $wnd.gwtcallback);
   }-*/
;

Cet exemple suppose que l’appel natif soit NativeSDK.method(text, callback).

Zones d’édition riche

De façon plutôt surprenante, le RichTextArea de GWT n’a pas de fonction permettant d’encapsuler un textarea existant.

Cette fonctionnalité est cependant assez simple à émuler via GWTQuery :

(...)
    $("textarea.rich").each(new Function() {
      @Override
      public void f(Element e) {
        final Element origin = e;
        final RichTextArea area = new RichTextArea();
        RootPanel.get().add(area);
        DOM.insertBefore(DOM.getParent(e), area.getElement(), e);
        area.setHTML(e.getInnerText());
        area.addBlurHandler(new BlurHandler() {
          @Override
          public void onBlur(BlurEvent event) {
            $(origin).val(area.getHTML());
          }
        });
        $(e).hide();
      }
    });

Ce bloc de code va, pour chaque textarea avec la classe rich, insérer une zone de texte riche au même endroit et cacher la zone initiale.

Les modifications de la zone d’édition riche sont répercutées sur la zone initiale dès perte du focus.

À noter que ce code n’a pas de barre d’outils de mise en forme, qu’il convient d’ajouter par ailleurs [2].

Les sélecteurs et $.live()

La documentation de GWTQuery suggère d’utiliser des implémentations de Selectors afin de réduire le code généré.

Cette méthode fonctionne correctement, cependant le comportement avec live() n’est pas celui attendu.

Soit par exemple :

interface MySelectors extends Selectors {
  @Selector("a")
  GQuery links();
}
final MySelectors _selectors = GWT.create(MySelectors.class);
(...)
  _selectors.links().live(Event.ONCLICK, new Function() {
    (...)
  });

Le comportement ne sera pas celui attendu, les liens rajoutés dynamiquement ne sont pas pris en compte.

Il faut utiliser de façon explicite $(filtres).live(), par exemple :

  $("a").live(Event.ONCLICK, new Function() {
    (...)
  });

Utiliser les bonnes versions de f()

Un des reproches que je fais à GWTQuery est de ne pas toujours être clair sur la version de Function.f() qu’il convient de surcharger pour des événements précis.

De ce que j’en ai compris :

  • pour un click() sur des éléments normalement cliquables (liens, boutons), il faut utiliser boolean f(Event e)
  • pour des éléments non cliquables, genre <p>, il faut utiliser public void f(com.google.gwt.dom.client.Element e)

Enfin, en cas de difficulté, utiliser le mode debug de GWT [3] permet de gagner énormément de temps pour tester, d’autant plus que le plugin remonte une exception « vous devez surcharger la bonne version de f() » quand on n’a pas utilisé la bonne.

Différences de comportement entre mode debug et version finale

J’ai eu quelques soucis de divergence de comportement entre le code tel qu’exécuté par le plugin GWT et le JavaScript final.

Dans mon cas, le problème était un $(filters).val() qui retournait un null qui fonctionnait en mode debug avec le plugin mais pas en JavaScript compilé en final.

Dans ce genre de situation, la meilleure façon de cerner le problème me semble-t-il est de compiler le code en mode « detailed » voire « pretty », et d’utiliser eg Firebug pour voir où se situe l’erreur.

Les noms de fonctions étant lisibles, il est assez simple de voir à quel endroit le code plante, et de remonter à la source, ou tout au moins de savoir à quel endroit se situe l’anomalie.


[1Bien sûr il convient d’appeler expose() dans le point d’entrée onModuleLoad().

[2Voir par exemple la classe RichTextToolbar dans les exemples de GWT, dans le « showcase ».

[3Voir NetBeans et le mode debug GWT pour un exemple avec NetBeans.