Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Comment: Migrated to Confluence 5.3

...

  • Wicket provides IModel implementations you can use with any component. These models can do things such as retrieve the value from a resource file, or read and write the value from a Java Bean property.
  • Wicket also provides IModel implementations that defer retrieving the value until it is actually needed, and remove it from the servlet Session when the request is complete. This reduces session memory consumption , and it is particularly useful with large values such as lists.
  • Unlike Swing, you do not have to implement an extra interface or helper class for each different component. Especially for the most often used components such as Labels and TextFields you can easily bind to a bean property.
  • In many cases you can provide the required value directly to the component and it will wrap a default model implementation around it for you.
  • And while you do not have to use beans as your models as you must with Struts, you may still easily use beans if you wish. Wicket provides the appropriate model implementations.

...

The corresponding input field in the html must have a wicket id of "address.city". This works, but it does expose the internal structure of the model data in the html. CompoundPropertyModel has a subclass, BoundCompoundPropertyModel, method that can be used to rectify this.

BoundCompoundPropertyModel adds three new methods. They associate a child component of the model with a specific property expression and/or type conversionThe model associates a different property expression with the component being bound.

Code Block
public Component bind(final Component component, final String propertyExpression)
public Component bind(final Component component, final Class type)
public Component bind(final Component component, final String propertyExpression, final Class type<S> IModel<S> bind(String property)

With this association in place the child component can have whatever name we like, rather than having the match the property expression.

To use BoundCompoundPropertyModel CompoundPropertyModel.bind for the city field discussed above we might do something like this:

Code Block
BoundCompoundPropertyModelCompoundPropertyModel personModel = new BoundCompoundPropertyModelCompoundPropertyModel(person);
Form personForm = new Form("aPersonForm", personModel);
TextField cityField = new RequiredTextField("city");
personForm.add(cityField);
, personModel.bind(cityField, "address.city");

Note that the bind methods return the child component, thus the above can be more compactly written:

Code Block

BoundCompoundPropertyModel personModel = new BoundCompoundPropertyModel(person);
Form personForm = new Form("aPersonForm", personModel);
personForm.add(personModel.bind(new RequiredTextField("city"), "address.city"))cityField);

Also, note that if you are using a component that you do not want to reference the compound property model, but is a child of the form, that you define a model for that component. For example:

...

Wicket provides the detachability hooks and will automatically detatch detach the default model of each component. However its common to use more than a single IModel implementation per component especially when using nested or chaining models. In this case the developer is responsible for registering their custom models to participate in the detachment process.

...

Code Block

public class MyPanel extends Panel {

   private final DetatchableModelA detachableModelA;


   public MyPanel (String id) {
       super(id);

       this.detachableModelA = new DetachableModelA();

       add (new CustomFormPanel ("formPanel", new CListModelFromA (this.detachableModelA)));

   }

   @Override
   public void detach() {

     // remember to call the parent detach logic
     super.detatchdetach();

     this.detachableModelA.detatchdetach();

   }
}

Here you see that the DetachableModelA instance is created in the MyPanel constructor and then passed in as the inner model to the CListModelFromA model which will load a List<C> instances based on the details of the A instance contained in the model. Because the this.detachableModelA instance could possibly be shared like this in many sub component models its important that the CListModelFromA implementation does not try and call detach() on it but rather leave the detachment to occur in the same scope that the model was instantiated. In this case the detachment is done in the MyPanel.detach.

...

Code Block
class LoadablePersonModel extends LoadableDetachableModelLoadableDetachableModel<Person> {

	Long id;

	LoadablePersonModel(Long id)	 {
		this.id = id;
	}

	@Override
	protected ObjectPerson load() {
		return DataManager.getPersonFromDatabase(id);
	}
}

...

(here LoadablePersonModel is a subclass of LoadableDetachableModel CompoundPropertyModel, as described earlier)

The model classes in the core Wicket distribution support chaining where it make sense.

...

Panel

The IModel interface was simplified in Wicket 2.0:

Code Block
public interface IModel<T> extends IDetachable
{
  T getObject();
  void setObject(final T object);
}

The get and set methods do not take a component argument anymore. Instead, Wicket 2.0 has specialized model interfaces to do with specific issues like recording the 'owning' component of a model. See IAssignmentAwareModel and IInheritableModel (though you typically don't need to know these interfaces directly).

Another change is that IModel does now support generics. This is especially interesting when authoring custom components where you allow only models (compile time) that produce a certain type. ListView for instance only accepts models that produces instances of java.util.List.

Refactor Safe Property Models

Annotation processors

There are a number of annotation processors that generate a meta data that can be used to build safe property models. Examples of such processors:

LambdaJ

With a little bit of help from the LambdaJ project we can stop using fragile PropertyModels.

Code Block

/* www.starjar.com
 * Copyright (c) 2011 Peter Henderson. All Rights Reserved.
 * Licensed under the Apache License, Version 2.0
 */
package com.starjar.wicket;

import ch.lambdaj.function.argument.Argument;
import ch.lambdaj.function.argument.ArgumentsFactory;
import org.apache.wicket.model.PropertyModel;

public class ModelFactory {

  /**
   * Use with the on() function from Lambdaj to have refactor safe property models.
   *
   * e.g.
   * <pre>
   * import static com.starjar.wicket.ModelFactory.*;
   * import static ch.lambdaj.Lambda.*;
   *
   * Contact contact = getContactFromDB();
   *
   * Label l = new Label("id", model(contact, on(Contact.class).getFirstName()));
   *
   *
   * </pre>
   *
   * OR
   *
   * <pre>
   * import static com.starjar.wicket.ModelFactory.*;
   * import static ch.lambdaj.Lambda.*;
   *
   * ContactLDM contactLDM = new ContactLDM(contactId);
   *
   * Label l = new Label("id", model(contactLDM, on(Contact.class).getFirstName()));
   * </pre>
   *
   * Works as expected for nested objects
   *
   * <pre>
   * Label l = new Label("address", model(contactLDM, on(Contact.class).getAddress().getLine1()));
   * </pre>
   *
   *
   * @param <T> Type of the model value
   * @param value
   * @param proxiedValue
   * @return
   */
  public static <T> PropertyModel<T> model(Object value, T proxiedValue) {
    Argument<T> a = ArgumentsFactory.actualArgument(proxiedValue);
    String invokedPN = a.getInkvokedPropertyName();
    PropertyModel<T> m = new PropertyModel<T>(value, invokedPN);
    return m;
  }
}

Which can then be used

Code Block

import static ch.lambdaj.Lambda.*;
import static com.starjar.wicket.ModelFactory.*;


public class MyPanel extends Panel {
  public MyPanel(String id) {
    Label l = new Label("firstName", model(contact, on(Contact.class).getFirstName());
    add(l);
    Label addr = new Label("address", model(contact, on(Contact.class).getAddress().getLine1());
    add(addr);
  }
}