Dynamic Field Addition in Enterprise Forms HST Component at Runtime
Introduction
Sometimes, you may just want to add some extra field(s)/fieldgroups/pages when rendering/storing the form based on user/session state information or whatever, without having to define them explicitly in the form document.
It is very easy to override some methods of the Enterprise Forms HST Component to add extra fields at runtime.
Examples in the Demo Project
Dynamic Field Creation
In the Enterprise Forms Demo projects, there is an example to show this scenario.
Here's how to test it out the example scenario:
- Build and run the Enterprise Forms Demo (access to Bloomreach Experience Manager Maven repository required).
- Visit http://localhost:8080/site/dynamicfields
- Enter all the fields and save.
- Now, see the data stored in the repository in the Forms Data Perspective in CMS.
- Now, visit http://localhost:8080/site/dynamicfields?region=us
Note the region parameter this time. - See if there's an extra field named 'State' inserted before the postal code field in the form.
- Enter all the fields and save.
- Now, see the data stored in the repository in the Forms Data Perspective in CMS again.
- See if you have the state field value for the second posting.
As you can check in the associated form document, there's no 'State' field definition in the form itself.
However, you can add an extra field very easily in your HST Component.
The following example code ( site/components/src/main/java/com/onehippo/cms7/eforms/demo/components/DynamicFieldsEformComponent.java in the demo) shows how to achieve this kind of scenario easily by overriding the #parse(final FormBean bean, ...) method:
/** * DynamicFieldsEformComponent to demonstrate how to add an extra field at runtime even if there's no field definition * in the associated form document. * * <P> * An example hst component which adds a runtime field ('State') when the URL has a query parameter, "region=US". * </P> */ public class DynamicFieldsEformComponent extends FormStoringEformComponent { @Override protected Form parse(final FormBean bean, final FormContext formContext, final HstRequest request) { Form form = FormFieldFactory.createForm(bean, formContext, null, request); if (isStateFieldNecessary(request)) { final TextField stateField = createStateTextField(form); // First, register the field in the form so that the form becomes aware of what to store/retrieve. form.registerField(stateField); // Second, insert the state field into the specific position of a page in the form form.getPages().get(0).addField(3, stateField); } return form; } /** * Let's suppose this method is reponsible for collection user state information and determine whether * an extra field should be shown or not based on the underlyig business logic which might invoke a backend system as well. * <P> * For simplicity, this method simply determines it based on query string parameter. * If there's a query string parameter, "region=US", then it returns true. * </P> * @param request * @return */ private boolean isStateFieldNecessary(final HstRequest request) { String region = getPublicRequestParameter(request, "region"); return (StringUtils.equalsIgnoreCase("US", region)); } /** * Creates an extra required field at runtime for 'State' input text field. * * @param form * @return */ private TextField createStateTextField(final Form form) { TextField stateTextField = TextField.builder(form, "state") .label("State") .length(40) .mandatory(true) .minLength(1) .maxLength(40).build(); return stateTextField; } }
Whenever the form is rendered (in #doBeforeRender()) or the form is submitted (in #doAction()), the #parse(final FormBean bean, ...) method is invoked to translate the form bean to runtime form/field instances.
So, the example component above is simply adding an extra field in an overridden method for #parse(final FormBean bean, ...) method. In this example, it simply checks the existence of a specific region request parameter to determine to show the extra field (see #isStateFieldNecessary() method), but you can replace it by something more meaningful/sophisticated for your business needs.
Also, note that you can create a field instance like #createStateTextField() does above, but also you can extend the existing field implementation to fulfil more complex scenarios.
Dynamic Field Group and Page creations
You can also add dynamic field groups and pages at runtime. They are demonstrated in the example form http://localhost:8080/site/dynamicregistration.
- Visit http://localhost:8080/site/dynamicregistration
- Enter all the fields and save.
- Now, see the data stored in the repository in the Forms Data Perspective in CMS.
- Now, visit localhost:8080/site/dynamicregistration?people=4
Note the number of registering peole this time. - See if there are multiple pages, each for a registering person.
- Enter all the fields and save.
- Now, see the data stored in the repository in the Forms Data Perspective in CMS again.
- See if you have registering people information for the second posting
The following example code ( site/components/src/main/java/com/onehippo/cms7/eforms/demo/components/DynamicRegistrationEformComponent .java in the demo) shows how to achieve this kind of scenario easily by overriding the #parse( FormBean, FormContext, HstRequest ) method:
/** * DynamicRegistrationEformComponent to demonstrate how to add extra fields/groups/pages at runtime even if there's no * field definition in the associated form document. * * <P> * An example hst component which adds multiple pages, each to enter a person's information when the URL has a * query parameter, "people=$number", e.g. "people=3" * </P> */ public class DynamicRegistrationEformComponent extends FormStoringEformComponent { private static Logger log = LoggerFactory.getLogger(DynamicRegistrationEformComponent.class); @Override protected Form parse(final FormBean bean, final FormContext formContext, final HstRequest request) { Form form = super.parse(bean, formContext, request); long numberOfPeople = getRegisteringPeople(request); if (numberOfPeople > 0) { for(int i = 0; i < numberOfPeople; i++) { final String name = String.format("person-%d", i + 1); final String label = String.format("Person %d", i + 1); FieldGroup contactFieldGroup = createContactFieldGroup(form, name, label); Page page = new Page(label); page.addField(contactFieldGroup); form.getPages().add(page); } } return form; } private long getRegisteringPeople(final HstRequest request) { String numberOfPeopleValue = getPublicRequestParameter(request, "people"); return StringUtils.isNotEmpty(numberOfPeopleValue) ? Long.parseLong(numberOfPeopleValue) : 0; } private FieldGroup createContactFieldGroup(final Form form, final String name, final String label) { FieldGroup fieldGroup = FieldGroup.builder(form, name, name).label(label).build(); form.registerFieldGroup(fieldGroup); String[][] fieldNames = { {"name", "Name"}, {"email", "Email"}, {"street-addr", "Street Address"}, {"city", "City"}, {"postal-code", "Postal code"} }; for(String[] fieldName : fieldNames) { TextField field = TextField.builder(form, fieldName[0]) .label(fieldName[1]) .length(40) .minLength(1) .maxLength(40) .build(); // Always add new field to the group to set the proper field group prefix before registering to the form. fieldGroup.addField(field); form.registerField(field); } DateField dobField = DateField.builder(form, "dob") .label("Date of Birth") .dateFormat("dd/MM/yyyy") .build(); fieldGroup.addField(2, dobField); form.registerField(dobField); String[] genderValues = {"male", "female", "other"}; String[] genderDisplayValues = {"Male", "Female", "Unspecified"}; AbstractField genderField = RadioGroup.builder(form, "gender", Arrays.asList(genderValues)) .displayValues(Arrays.asList(genderDisplayValues)) .label("Gender") .hint("Select your gender") .build(); fieldGroup.addField(3, genderField); form.registerField(genderField); return fieldGroup; } }