Versions Compared

Key

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

Read the page on Detachable Models first.

Edit Pages

If you have atomic (single-step) edit pages, you can use detachable models to hold your business objects. This reduces memory consumption, and helps prevent displaying stale data to your users.

Step 1: Design your edit pages to accept a Model, not a business object

By accepting a Model in the constructor instead of a business object instance, you give more flexibility to the pages which instantiate your edit page.

The object being edited is either persistent or transient. If it's persistent, your detachable model can look it up in the database by its primary key value. If it's transient, your detachable model needs some way to create a new business object instance. By accepting a model as a constructor parameter, this becomes the responsibility of whatever page instantiates the edit page.

Here's a sketch of a hypothetical Person edit page:

Code Block
/** @author Sam Barnum */
public class PersonEditPage {
	private WebPage returnPage;

	/**
	 * Create an edit page
	 * @param businessObject Model which contains the business object to edit.
	 * @param returnPage The page to direct the user to after a successful save operation
	 */
	public PersonEditPage(IModel businessObject, WebPage returnPage) {
		setModel(new CompoundPropertyModel(businessObject));
		this.returnPage = returnPage;
		initComponents();
	}

	void initComponents() {
		// create your form here
	}

	public Person getPerson() {
		return (Person)getModelObject();
	}

	/** Saves the business object to the database */
	void save() {
		EntityManager em = getEntityManager();
		Person person = getPerson();
		if (person.getId() == null) {
			// this is a transient person
			em.persist(person);
		} else {
			// this is a persistent person. There should be no need to call em.merge(),
			// because the person should be in a detachableModel which was loaded at the beginning of the request.
			// just in case, we'll call merge
			person = em.merge(person);
		}
		em.flush(); // if the person was transient, this will assign the primary key value
		// now we set a new Model containing the saved person
		setModelObject(new DetachableEntityModel(person));
		// and take the user to the successful save page
		setResponsePage(returnPage);
	}

	/** Loads the entity manager from the session or other location */
	EntityManager getEntityManager() {
		// implementation omitted
	}



}

Below is the source code for DetachableEntityModel.

Code Block
/**
 * Model which knows how to fetch a business object by its class and primary key value (id).
 * @author sbarnum
 */
public class DetachableEntityModel extends LoadableDetachableModel implements IModel {
	private final Class entityClass;
	private final Serializable id;

	/**
	 * Create a detatchableEntityModel for a persistent object. The id of object must not be null.
	 * @param object the business object
	 */
	public DetachableEntityModel(MyBusinessObject object) {
		super(object);
		if (object == null) throw new NullPointerException("object must not be null.");
		if (object.getId() == null) throw new NullPointerException("object.getId() must not be null.  " +
			"To create a model for a transient instance, use a LoadableDetachableModel whose " +
			"load() method instantiates a new object.");
		this.entityClass = object.getClass();
		this.id = object.getId();
	}

	/** Loads the business object from the database */	
	protected MyBusinessObject load() {
		return ((Session)Session.get()).getEntityManagerForRequest().find(entityClass, id);
	}
}

If you want to edit a persistent business object (from a list page, for example), you should give the PersonEditPage a DetachableEntityModel containing your persistent business object.

If you want to edit a newly created transient object (as a result of clicking a 'new' button, for example), you should pass in a LoadableDetachableModel whose load() method creates and initializes a new business object. For example, suppose you have a Person list view. You can filter the list using a dropdown menu of person type. There's a link to create a new person. You'd like to set the new person's type to whatever is currently selected in the dropdownlist. If the PersonEditPage were responsible for instantiating the Person object, this would get tricky. Instead, you could use the following link on your list page:

Code Block
add (new Link("addPerson") {
	public void onClick() {
		LoadableDetachableModel businessObjectModel = new LoadableDetachableModel() {
			protected Object load() {
				Person result = new Person();
				result.setType("Administrator"); // or whatever type is being searched on
				return result;
			}
		};
		setResponsePage(new PersonEditPage(businessObjectModel, getWebPage()));
	}
});

Validation failures

What if the user submits the form and there's a validation error? If form validation fails, wicket doesn't update your business object. The model is detached, then reattached. Then the user gets another shot at submitting the form.