Form Submission in JandalHow to process a form POST in Jandal |
I n this article I'll show you how to process the contents of a submitted form in Jandal, and when the form contains errors, how to render the form back to the client with an error message for each erroneous field. Example Application: AddressBookAddressBook is a Jandal example application that , you guessed it, allows you to create, edit and delete addresses. AddressBookController.javaThe snippet below shows part of the AddressBookController, which controls the query, delete, update and create functions within the AddressBook example application. I've chopped out everything except for the creating state, which accepts details for a new address via a form submission. Lets jump in and take a look. As soon as the State is entered, it outputs an empty name, URL and E-mail address to be initial content for the fields of the form within its createAddress.ftl template (shown further down the page). It also outputs an empty error message for each field; the form has not yet been submitted, so it reports no errors yet. When the State gets a save event, the EventProcessor extracts the submitted form fields from the event parameters. See how the EventProcessor's handy getStringParam method converts them to String objects, while asserting that they are supplied. Next, the EventProcessor validates the name field, setting the corresponding error message if that fails. I have omitted to validate the other fields for brevity. If the EventProcessor found no erroneous fields, it creates the new address and transitions the Controller to the listing State. Otherwise, it outputs the submitted fields and error messages, and expires without transitioning out of the creating State, so that the Controller can re-render the form with erroneous content and error messages. public class AddressBookController extends Controller { ... protected void onStart() throws JandalCoreException { ... addState(new State("creating") { protected void onEntry() throws JandalCoreException { setOutput("template", "createAddress.ftl"); setOutput("name", ""); setOutput("nameError", ""); setOutput("email", ""); setOutput("emailError", ""); setOutput("url", ""); setOutput("urlError", ""); addViewEventProcessor(new EventProcessor("save") { protected void onEvent() throws JandalCoreException { final String name = getStringParam("name").trim(); final String email = getStringParam("email").trim(); final String url = getStringParam("url").trim(); boolean errors = false; if (name.length() == 0) { setOutput("nameError", "Name cannot be empty"); errors = true; } // TODO: Validate URL and E-mail fields here and // possibly generate error messages for them if (!errors) { AddressStore addressStore = (AddressStore) getService( AddressStore.class.getName()); try { addressStore.addAddress(name, email, url); } catch (Exception e) { throw new RuntimeException(e); } doTransition("listing"); } else { setOutput("name", name); setOutput("email", email); setOutput("url", url); } } }); addViewEventProcessor(new EventProcessor("cancel") { protected void onEvent() throws JandalCoreException { doTransition("listing"); } }); } }); } } createAddress.ftlHere is the form from the creating State's FreeMarker template, createAddress.ftl. I know, it's a fairly rugged bit of old-school HTML, but it suffices to demonstrate a Jandal form. The JandalFreeMarkerServlet serving the application merges a model (implemented by the framework class ControllerTemplateModel) with the template, which the template accesses under the name "context". The model contains everything that the template needs in order to pull data from its Controller and post back submissions and actions. For the onSubmit attribute of the form tag, the template pulls from the model the URI of the servlet. Next, the template uses the model's FormTool to render special form tags and fields that the servlet will require in the form. These fields include a session ID and a workflow synchronization token that prevents back-button confusion. The template populates each of the name, E-mail and URL fields with the corresponding AddressBookController output. The template also pulls the error message outputs from and renders them alongside the fields. Finally, the template uses the RequestTool to obtain a value for the onClick attribute of the form's submission button, so that submission will post a "save" view event to AddressBookController. Likewise the template uses the tool to cause the Cancel button to post a "cancel" event to the controller. ... ${context.getFormTool().getFormOpenTag("save")} <table cellspacing="3px" cellpadding="5px"> <tr> <td align="right">Name:</td> <td> <input name="name" type="text" value="${context.getOutput('name')}"/> <font face="helvetica" color="red"><b>${context.getOutput("nameError")}</b></font> </td> </tr> <tr> <td align="right"> E-Mail: </td> <td> <input name="email" type="text" value="${context.getOutput('email')}"/> <font face="helvetica" color="red"><b>${context.getOutput("emailError")}</b></font> </td> </tr> <tr> <td align="right"> URL: </td> <td> <input name="url" type="text" value="${context.getOutput('url')}"/> <font face="helvetica" color="red"><b>${context.getOutput("urlError")}</b></font> </td> </tr> </table> </br> <input type="submit" value="Save"/> <input type="button" value="Cancel" onclick="${context.getRequestTool('cancel').getAction()}"/> ${context.getFormTool().getFormCloseTag()} |
