gwt-mpv has support for building flexible forms with as little boilerplate as possible.
Most form fields should require just 1 line of setup code–simple is easy
Custom form fields are still doable–complex is possible
Ensure consistent look & feel of all forms in an app
Reusable implementation of standard AJAX form UX:
Enable low-level customization of the form’s HTML code to match a mockup’s HTML (whether using divs with floats or tables)
Remain unit testable/MVP-compliant
The focus is on getting all of the aspects of an AJAX form’s UX right and consistent, without having to repeat all of this logic in every presenter in your app.
Similarly, each form and each form field in an application shouldn’t require copy/pasting 4-5 lines of code in the ui.xml file per form element.
gwt-mpv’s FormPresenter decouples several aspects of forms to ensure a clean separation of concerns. Specifically, decoupling layout from declaration.
For example, a FormPresenter-enabled form might look like:
EmployeeModel ee = new EmployeeModel();
FormPresenter form = new FormPresenter("formId");
form.add(new TextBoxFormLine(ee.name));
form.add(new TextBoxFormLine(ee.title));
form.add(new ListBoxFormLine(ee.status, allPossibleStatuses));
form.add(new ButtonFormAction(saveCommand, "Save"));
view.flowPanel().add(form.getView());
Notice how the presenter can focus on only declaring the form’s structure. No layout details are needed (either here in the presenter or the presenter’s ui.xml file).
When the form is rendered to the DOM, the ee.name property will be wrapped in the boilerplate div, input, etc. tags to lay out it’s row. Same thing with the other properties.
By default, the provided FormLine implementations will also render errors, so if ee.name is required, and the user leaves it blank, the error “Name is required” will be shown.
FormPresenter is based on the notion of forms compromising of two basic things:
Then lines themselves are typically composed of:
(Note that FormLine is a generic interface, so is not required to have labels/values/errors, which is convenient for custom form lines, say if you needed a DashedFormLine to draw a solid hr between two sections of your form.)
With these set of assumptions about form structure, FormPresenter then provides interfaces and some default implementations to generic tie them together in a coherent way.
While FormPresenter provides sensible HTML out-of-the-box, it was designed with the realization that it is often desirable to conform to a project’s/mockup’s existing HTML/form structure.
To achieve that, FormPresenter defers all HTML logic to the FormLayout interface. It has generic “begin/end” methods that allow a layout implementation to drop in the necessary HTML.
For example, DefaultFormLayout look something like:
public class DefaultFormLayout implements FormLayout {
@Override
public void formBegin(FormPresenter p, HTMLPanelBuilder hb) {
hb.add("<div class=\"" + style.form() + "\">");
}
@Override
public void formEnd(FormPresenter p, HTMLPanelBuilder hb) {
hb.add("</div>");
}
@Override
public void lineBegin(FormPresenter p, HTMLPanelBuilder hb) {
hb.add("<li>");
}
@Override
public void lineEnd(FormPresenter p, HTMLPanelBuilder hb) {
hb.add("</li>");
}
// ... more methods ...
Where it uses div, ol, and li tags to layout the form elements (based on the markup from fancy form design).
While FormPresenter is easy to use directly, it also works well to create a subclass specifically for your application with standardized/application-specific helper methods that can be reused across your pages.
For example, you might have something like:
public class AppFormPresenter extends FormPresenter {
public AppFormPresenter(String id) {
// if using custom layout
super(id, new AppFormLayout(id));
}
public void addTextLine(Property<String> p) {
// if using tweaked text box form lines
add(new CustomTextBoxFormLine(p));
}
// if your app has a custom `Day` user type
public void addDateLine(Property<Day> p) {
// implementation might use DatePicker/whatever
add(new CustomDayFormLine(p));
}
}
Note again that this subclass is not required to use FormPresenter, but is a helpful way of centralizing the form layout decisions and making consuming page presenters that much more simple.
In keeping with gwt-mpv’s goal for easy DOM-less unit testing, presenters that use FormPresenter can still be unit tested.
Note that because form elements are specified only in the presenter (which is more succinct, and doesn’t involve boilerplate elements in the ui.xml file), it does mean the presenter’s view (which is generated from the ui.xml file) won’t have methods to access each individual form element for the test to manipulate/assert against (see tests for the typical way of testing in gwt-mpv).
Instead, we have to grab the form elements by their id from FormPresenter’s backing HTMLPanel. Although if your form ids match your model property names, you can use some helper methods to do the lookup, e.g.:
public class FooPresenterTest {
FooPresenter p;
StubFooView v;
@Test
public void testRequiredFields() {
FooModel model = setupPresenterFor(new FooDto(...));
// click without filling anything in
save().click();
// ensure no request was sent
assertThat(async.getOutstanding().size(), is(0));
// ensure error message was shown
assertThat(
errors(model.name).getList(),
contains("Required"));
}
// creates/binds the presenter to a model for the dto
private FooModel setupPresenterFor(FooDto dto) {
FooModel model = new FooModel(dto);
p = bind(new FooPresenter(model));
v = (StubFooView) p.getView();
}
// uses p to find the form element by id
private StubTextList errors(final Property<?> p) {
String propertyId = Inflector.camelize(p.getName());
String fullId = "formId-" + propertyId + "-errors";
return (StubTextList) v.findById(fullId);
}
// returns the stub button rendered in the form
private StubButton save() {
return (StubButton) v.findById("formId-save");
}