vendredi 26 septembre 2008

Wicket: Checkbox, AbstractCheckBoxModel et enum

Voici maintenant 3 semaines que je travaille avec Wicket et je dois dire que je suis vraiment impressionné par la vitesse de développement avec ce framework.
Ça prend un petit peu de temps pour se familiariser avec l'approche "component oriented" mais une fois que l'on a compris... ça dépote :-)

Seul petit défaut à mon avis : la documentation!
Quand on vient du monde merveilleusement documenté de Spring, Wicket est un peu dur...

Dans le billet d'aujourd'hui, je présenterais comment afficher une liste de checkbox pour toutes les valeurs d'un enum.
Je n'utilise pas de radio car je veux pouvoir désélectionner tous les checkbox (ie la valeur nulle est permise).
En fait c'est exactement le fonctionnement d'une liste de radio (un seul choix possible) mais je peux tout désélectionner , ce qui n'est pas possible avec les radios.




Évidemment dans l'exemple ci-dessus, le choix n'est pas unique mais c'est juste pour montrer à quoi ça va ressembler à la fin... ;-)

Pour commencer, mes enum implémentent l'interface suivante qui permet de connaître le label correspondant à chaque enum.

public interface LabeledEnum {
  String getLabel();
} 
public enum Browser implements LabeledEnum {

  IE, FIREFOX, OPERA, CHROME;

  public String getLabel() {
    return "browser." + name();
  }
  
}
browser.IE=Internet Explorer
browser.FIREFOX=Firefox
browser.OPERA=Opera
browser.CHROME=Chrome

Le composant Checkbox ne travaille qu'avec un Model de type boolean. Donc je ne peux pas lui passer directement mon enum.
Il faut utiliser par la classe AbstractCheckBoxModel pour transformer mon enum en boolean.

public class EnumCheckBoxModel extends AbstractCheckBoxModel {

  private static final long serialVersionUID = 1L;

  private final IModel model;
  private final LabeledEnum enumValue;

  public EnumCheckBoxModel(IModel model, LabeledEnum enumValue) {
    this.model = model;
    this.enumValue = enumValue;
  }

  @Override
  public boolean isSelected() {
    return model.getObject() == enumValue;
  }

  @Override
  public void select() {
    model.setObject(enumValue);
  }

  @Override
  public void unselect() {
    model.setObject(null);
  }

}

Et voici enfin le code de mon composant :

public class EnumCheckBoxes<TEnum extends Enum<?> & LabeledEnum> extends Panel {

  private static final long serialVersionUID = 1L;

  TEnum[] enumList;
  IModel checkBoxModel;

  public EnumCheckBoxes(String id, IModel checkBoxModel, Class<TEnum> enumClass) {
    this(id, checkBoxModel, enumClass.getEnumConstants());
  }

  public EnumCheckBoxes(String id, IModel checkBoxModel, TEnum... enumList) {
    super(id);
    this.checkBoxModel = checkBoxModel;
    this.enumList = enumList;
    createComponent();
  }

  private void createComponent() {

    // div that will be refreshed via ajax when user select a checkbox
    final WebMarkupContainer listViewContainer = new WebMarkupContainer("listContainer");
    listViewContainer.setOutputMarkupId(true);

    final ListView listView = new ListView("list", new Model((Serializable) Arrays.asList(enumList))) {
      private static final long serialVersionUID = 1L;

      @Override
      protected void populateItem(ListItem item) {
        TEnum labeledEnum = (TEnum) item.getModelObject();

        CheckBox checkBox = new CheckBox("input", new EnumCheckBoxModel(checkBoxModel, labeledEnum));
        checkBox.setLabel(new ResourceModel(labeledEnum.getLabel()));

        // don't use OnChangeAjaxBehavior because of IE event propagation for checkbox onchange
        checkBox.add(new AjaxFormComponentUpdatingBehavior("onclick") {
          private static final long serialVersionUID = 1L;

          @Override
          protected void onUpdate(AjaxRequestTarget target) {
            target.addComponent(listViewContainer);
          }
        });
        item.add(checkBox);
        item.add(new SimpleFormComponentLabel("label", checkBox));
      }

    };
    listViewContainer.add(listView);
    add(listViewContainer);
  }

}
<html xmlns:wicket>
  <wicket:panel>

    <div wicket:id="listContainer">
      <div wicket:id="list">
        <div class="checkboxWithLabel">
          <input type="checkbox" wicket:id="input" />
          <label wicket:id="label"> </label>
        </div>
      </div>
    </div>

  </wicket:panel>
</html>

jeudi 11 septembre 2008

Wicket & Maven packaging (fichiers html, css, js...)

Si vous utilisez Maven pour packager votre appplication Wicket, n'oubliez pas de modifier le POM.xml pour inclure les fichiers html, css, js, etc. dans votre war (ou jar).
A moins que vous placiez tous ces fichiers non-java sous src/main/resources au lieu de src/main/java (ça fait quand même bizarre de voir tous ces fichiers non-java à coté des classes).

<build>
 <resources>
  <resource>
   <directory>src/main/java</directory>
   <includes>
    <include>**/*</include>
   </includes>
   <excludes>
    <exclude>**/*.java</exclude>
   </excludes>
  </resource>
 </resources>
</build>

mercredi 27 août 2008

Wicket & Spring

Depuis le temps que j'entends parler de Wicket, je me lance enfin dans l'aventure :-)
J'essayerais ici de décrire les différentes étapes de la migration d'une application Spring MVC 2.5 / Weblow 2.0 / Hibernate vers Wicket 1.3.4.

Pour démarrer, je suis parti des articles du blog Xebia France qui sont vraiment bien fait... Merci à eux!

Premier problème : Spring!

Après avoir ajouté mes dépendances dans le POM.xml, Spring en peut loader les contextes :

ERROR org.springframework.web.context.ContextLoader.initWebApplicationContext:205 - Context initialization failed
org.springframework.beans.factory.BeanDefinitionStoreException: Unexpected exception parsing XML document from class path resource [applicationContext-service.xml]; nested exception is java.lang.NoSuchMethodError: org.springframework.util.ClassUtils.isPresent(Ljava/lang/String;Ljava/lang/ClassLoader;)Z
Caused by: 
java.lang.NoSuchMethodError: org.springframework.util.ClassUtils.isPresent(Ljava/lang/String;Ljava/lang/ClassLoader;)Z
 at org.springframework.context.annotation.AnnotationConfigUtils.<clinit>(AnnotationConfigUtils.java:75)
 at org.springframework.context.annotation.AnnotationConfigBeanDefinitionParser.parse(AnnotationConfigBeanDefinitionParser.java:45)

La librairie wicket-spring dépend de spring 2.0 et non de la dernière version 2.5.x.
Il faut donc juste exclure le Spring de Wicket comme suit dans le pom.xml du projet :

<dependency>
 <groupId>org.apache.wicket</groupId>
 <artifactId>wicket-spring</artifactId>
 <version>${wicket.version}</version>
 <exclusions>
  <exclusion>
   <groupId>org.springframework</groupId>
   <artifactId>spring</artifactId>
  </exclusion>
 </exclusions>
</dependency>

Internationalisation

Tous mes traductions sont dans un seul fichier properties et je ne veux pas les séparer par page ou déplacer et renommer ce fichier à la racine de l'application Wicket.

En cherchant un peu, je suis tombé sur ce blog :
http://www.jroller.com/eyallupu/entry/spring_as_a_message_provider
Il propose ici d'utiliser le MessageSource de Spring directement dans Wicket.
Ça marche parfaitement bien :-)

mardi 27 novembre 2007

Intégration native de Maven à Eclipse

Enfin un support natif de Maven dans Eclipse!
The Eclipse Integration for Apache Maven

Mais a priori il va falloir s'armer de patience : première release en septembre 2008.

vendredi 22 juin 2007

Hibernate Annotations - @CollectionOfElements

Depuis la version 3.1 de Hibernate Annotations (il me semble), on peut enfin avoir une collection de types primitifs (String par exemple) grâce à @CollectionOfElements.
Avant ça, on était un peu bloqué avec des affaires comme un champ texte qui contient la liste séparée par des virgules (ou un autre caractère), ou alors une entité gérée par Hibernate qui ne contient qu'une ID et la primitive à stocker :-(

Attention cependant, l'annotation @org.hibernate.annotations.CollectionOfElements est spécifique à Hibernate. Elle ne fait pas partie des spécifications JPA...

@Entity
public class User {
  @CollectionOfElements
  private Set<String> nicknames;
}

mercredi 6 juin 2007

Inversion de contrôle et injection de dépendances

Excellent article sur la différence entre l'inversion de contrôle et l'injection de dépendances. La confusion est souvent faites entre les deux: on pense, à tort, que c'est la même chose...
S'mythology - A Little Clarity - Inversion of Control and Dependency Injection

mardi 5 juin 2007

NoClassDefFoundError: ReflectionManager

NoClassDefFoundError: org/hibernate/annotations/common/reflection/ReflectionManager

En mettant à jour Hibernate Annotations à la dernière version (3.3.0.ga) via Maven2 (qui semble enfin avoir les dernières versions de Hibernate), j'avais cette exception :
NoClassDefFoundError: org/hibernate/annotations/common/reflection/ReflectionManager

C'est que le POM de Hibernate Annotations est incomplet.... Il manque la dépendance à hibernate-commons-annotations.
Donc en attendant que le POM de Hibernate Annotations soit corrigé ( http://jira.codehaus.org/browse/MAVENUPLOAD-1532), ajoutez dans le POM de votre projet la dépendance à hibernate-commons-annotations 3.3.0.ga.

<dependency>
  <groupId>org.hibernate</groupId>
  <artifactId>hibernate-commons-annotations</artifactId>
  <version>3.3.0.ga</version>
</dependency>