Wednesday, May 16, 2007

Creating a CRUD interface using widgets in tapestry

One thing that has now become possible is to create, update and delete items in your app model, all while never leaving the same page. A create can invoke a css-styled div, made to look like a dialog, which asks for a few field values, saves it and invokes an ajax response to update the master page. Same for edit, and for delete. The old way would have been to take you to a new page to do the create, or to pop up a full window and do the job with the aid of a bit of javascript.

I've been experimenting with the dojo dialog provided in tapestry 4.1.2 to do just this. It works very well. Now I want to add a little bit more intelligence to the form (in the dialog - let's call it the crud form). So for instance, if you choose an item from a selection (lets call it billableItem), other fields can react. In tapestry, the simplest way to do this is with the EventListener annotation. Rather than dreaming up some custom javascript to do it, you can do a little ajax request/response to update your form for you. Very cool.

There are a few challenges though. Since the crud form is itself asynchronous, and is told to update some components of the master page, we have a few problems when we try to get the information in our crud form to our app, so that the secondary fields can react. Obviously the billableItem selection needs to get to our app so that we can react, and this is typically done by submitting the form in question, while turning validation off and making sure it is asynchronous.

<component id="addBillableItemEventForm" type="Form">
<binding name="delegate" value="bean:delegate"/>
<binding name="clientValidationEnabled" value="true"/>
<binding name="success"
value="listener:addBillableItemEvent"/>
<binding name="async" value="ognl:true"/>
<binding name="updateComponents"
value="{'weekScheduler_billable_row'}"/>
</component>

<component id="billableItem" type="PropertySelection">
<binding name="displayName" value="literal:Billable Item"/>
<binding name="model"
value="ognl:billableItemSelectionModel"/>
<binding name="value" value="ognl:billableItem"/>
<binding name="validators" value="validators:required"/>
</component>

--

@EventListener(elements = "billableItem", events = "onchange",
submitForm = "addBillableItemEventForm",
async=true, validateForm = false)

The problem is that when our EventListener is invoked, telling that crud form to submit itself so that we can react, it goes and updates the master page and validates on the server side (the crud form is configured to validate on the client side). So then we have validation errors on required fields and such, which is not what we want. So we can't really have our EventListener submit this form, because that form needs to be configured the way it is.

@EventListener(elements = "billableItem", events = "onchange")

So what can we do to add our intelligence, short of developing some custom javascript as per usual?