Wicket: Creating AJAX-enabled @Entity-based CRUD Pages
Posted by ozizka in Ondrej Zizka's Blog on Jan 21, 2013 6:04:12 PMThis is my way of decomposing an @Entity editing Page in Apache Wicket framework (applies for 1.4, 1.5, 6.x+).
Basic principle
Use just one page for all CRUD (create, read, update, delete). It may have drawbacks, but it's the fastest way.
Create: create a new entity object (not persistent yet) with default values and use it as Page's model.
Read: load the entity from DB
Update: use ajax-based editable labels to edit values in place.
Delete: Button with ajax-based confirmation somewhere on the page. User can see what he actually deletes. Redirect to some listing of objects of that kind.
Tips & Rules
1) Use fully loaded entity as a model. Use LEFT JOIN FETCH to load collections and other LAZY_LOADed parts.
Do not resort to Open Session in View.
SELECT p FROM Product p LEFT JOIN FETCH p.customFields
Use PropertyModel. That will keep page object's property and the model the same.
this.setDefaultModel( new PropertyModel(this, "product") );
Instead of Page's default model, you can use your own, to avoid type casting.
2) Use PropertyModel for subcomponents.
Maybe CompoundPropertyModel could be used? (Not sure if limited to Form component or usable for any.)
3) Use EditableLabel. Having a "show" page and "edit" page for everything sucks.
I have created an EditableLabel and EditableLink components which appear as labels, but are editable when clicked or activated by a JavaScript call.
4) Use AJAX to update the page. Reloading or clickink a save button (often) sucks.
5) If practical, have only one call to dao.update() - in a special method in the Page object.
/** * Called when some of sub-components were updated. * * @param target Ajax target, or null if not a result of AJAX. */ private void onProductUpdate( AjaxRequestTarget target ) { if( target != null ) target.add( this.feedbackPanel ); try { product = productDao.update( product ); modelChanged(); this.feedbackPanel.info("Product saved."); if( target != null ) target.appendJavaScript("window.notifyFlash('Product saved.')"); } catch( Exception ex ){ this.feedbackPanel.info("Saving product failed: " + ex.toString()); } }
Call to this method from other methods in Page.
Such methods may be overriden onChange-like methods of your components, used as a mechanism to propagate changes to parent components up to the Page.
Don't forget to call modelChanged() wherever you change Page's model or it's object.
Comments