Forms make it possible to collect all sorts of information from users for various purposes including contact information, user feedback, information for an online application and so on. An application can have simple to very complicated forms, Liferay Forms makes creating and managing these forms much easier than building them from scratch. In Liferay 7.1 and 7.2 there have been a lot of useful additions to Liferay Forms making it more flexible and easier to create complicated forms with intricate business rules.
The intent of this blog is to look into how we can programmatically add custom validation to Liferay Form fields. The current out of the box validation feature should be able to handle most of the necessary form field validations. However, there are cases where validation is not straightforward and it requires more flexibility. That is the edge case I am addressing with this blog.
You can download the complete sample code project for the Liferay form field custom validation implementation here. The sections below detail how to set up a Liferay Form with the sample project and also discuss some of the implementation details within the code snippets.
Step 1. Create a Liferay Form
Let’s go through the steps for creating the Liferay form and add form fields.
- From the Control Panel, choose the Liferay site where you would like to create the form.
- In Control panel, click the Content section and then click on the Forms link.
- Add a new Form, give it the title “Test Liferay Form”.
- Add the following Text Fields; “First Name”, “Last Name” and “Age”
- Click on Publish Form button
2. Prepare Form for Rendering
In Liferay DXP 7.0, a form is programmatically represented by DDLRecordSet and a form entry is DDLRecord. That changed in Liferay DXP 7.1, a form is now represented as DDMFormInstance and an entry is a DDMFormInstanceRecord. The forms DDMFormInstanceId will be required to render it on the front-end using the liferay-form tag. There are more details on liferay-form tag in the next section.
In the code example below we get the DDMFormInstance using the Form name with the help of Liferay Dynamic Queries. This could equally be achieved with custom queries. The DDMFormInstanceId is retrieved and set as a request attribute during the render phase of the portlet.
public void doView(RenderRequest renderRequest, RenderResponse renderResponse) throws IOException, PortletException {
String formName = "Test Liferay Form";
DynamicQuery dynamicQuery = _ddmStructureLocalService.dynamicQuery();
dynamicQuery.add(
PropertyFactoryUtil.forName(
"name").like("%<Name language-id=\"" + LocaleUtil.getDefault() + "\">" + formName + "</Name>%"));
List<DDMStructure> ddmStructureList = _ddmStructureLocalService.dynamicQuery(dynamicQuery);
DDMStructure ddmStructure = ddmStructureList.get(0);
dynamicQuery = _ddmFormInstanceLocalService.dynamicQuery();
dynamicQuery.add(PropertyFactoryUtil.forName("structureId").eq(ddmStructure.getStructureId()));
List<DDMFormInstance> formInstances = _ddmFormInstanceLocalService.dynamicQuery(dynamicQuery);
DDMFormInstance ddmFormInstance = formInstances.get(0);
renderRequest.setAttribute("formInstanceId", ddmFormInstance.getFormInstanceId());
super.doView(renderRequest, renderResponse);
}
3. Render Form
As mentioned above, a liferay-form tag is used to render the form embedded with your portlet. An alternative route would be to use the Liferay Forms portlet to render the form but with that approach in other to include custom validation you will need to add some customization around the Liferay Forms implementation including custom jsp, js and action commands. liferay-form tag is a bit easier to manage and less intrusive.
With the liferay-form tag ddmFormInstanceId attribute is required and the rest of the attributes are optional. In this example, the Forms default submit button is hidden by setting showSubmitButton to false. Instead, a custom submit button is added for more flexibility.
<portlet:actionURL name="addOrUpdateRecord" var="addOrUpdateRecordActionURL" />
<aui:form action="${addOrUpdateRecordActionURL}" method="post" name="fm">
<aui:input type="hidden" name="recordId" value="${recordId}" />
<liferay-form:ddm-form-renderer
ddmFormInstanceId="${formInstanceId}"
ddmFormInstanceRecordId="${recordId}"
showFormBasicInfo="${true}"
showSubmitButton="${false}"
/>
<aui:button-row>
<aui:button cssClass="btn btn-primary" id="submit-button" value="Submit" />
</aui:button-row>
</aui:form>
4. Form Field Custom Validation
The sample project contains more details on the custom javascript implementation to handle the custom validation. Below are some code snippets that gives a sneak peak into the implementation. I recommend you download the full set of examples. There are inline comments within the code describing the flow and process.
// validate is a function called to validate a form field based on custom validation
// formField is a json object representing the form field
validate = function(formField, e) {
//prevent Liferay validations from triggering
e.stopImmediatePropagation();
//get Liferay form component and field
var liferayFormComponent= getFormComponent(formField.field);
var liferayFormField = liferayFormComponent.getField(formField.name);
//call custom validation function on field
var validationResults = formField.validateCallback.call(formField.field);
//check if validation passed or not
//if failed set and show custom error message if passed
//if passed unset and hide custom error message
if(!validationResults.passed){
liferayFormField.set('errorMessage', validationResults.errorMessage);
liferayFormField.hasErrors = function(){return true};
liferayFormField.showErrorMessage();
} else{
liferayFormField.set('errorMessage', '');
liferayFormField.hasErrors = function(){return (formField.field.val() ? false : true)};
liferayFormField.hideErrorMessage();
}
}
//form field custom validation function for validating name
//returns json, validationResults, which includes error message if any
function validateName(nameField){
var name = nameField.val();
var validationResults = {};
if (name.length < 5) {
validationResults.passed = false;
validationResults.errorMessage = "Name must be more than 5 characters.";
} else {
validationResults.passed = true;
}
return validationResults;
}
5. Save/Update the Form Entry
The advantage of using a custom submit button for the Liferay Form is that it allows you to call your own custom action method. Within this method, apart from saving the form data as a record, you can also do other stuff with data. This is where we’ll add our custom form validation logic. This is the “secret sauce” that allows you to invoke your custom form validation methods. The code snippet below shows how to update an existing or add a new DDMFormInstanceRecord.
long groupId = ParamUtil.getLong(actionRequest, "groupId");
long formInstanceId = ParamUtil.getLong(actionRequest, "formInstanceId");
long recordId = ParamUtil.getLong(actionRequest, "recordId");
DDMFormInstance ddmFormInstance = _ddmFormInstanceLocalService.getFormInstance(formInstanceId);
DDMStructure ddmStructure = ddmFormInstance.getStructure();
DDMForm ddmForm = ddmStructure.getDDMForm();
DDMFormValues ddmFormValues = _ddmFormValuesFactory.create(actionRequest, ddmForm);
ServiceContext serviceContext = ServiceContextFactory.getInstance(DDLRecord.class.getName(), actionRequest);
DDMFormInstanceRecord ddmFormInstanceRecord = null;
if(recordId != 0){
ddmFormInstanceRecord = _ddmFormInstanceRecordService.updateFormInstanceRecord(recordId, true, ddmFormValues, serviceContext);
} else {
ddmFormInstanceRecord = _ddmFormInstanceRecordService.addFormInstanceRecord(
groupId, ddmFormInstance.getFormInstanceId(), ddmFormValues, serviceContext);
}
Summary
For a quick recap, there are the steps you need to perform:
- Create your form definition
- Create your custom portlet to render the form with custom form validation
- Develop the code to prepare and render the form
- Develop your custom form validation logic
- Implement the custom submit button within your Liferay Form
Liferay Forms allows for easy creation and managing of forms with basic to complex business rules. With Liferay Forms, a developer can focus more on business rules and allow Liferay to handle all the underlying form implementations. Liferay 7.1 introduces some really cool changes including form validation, which now covers a broader validation scope. Although form validation now captures a lot more validation scenarios, there will likely still be edge cases that require you to introduce custom form field validation.
If you have questions and/or need any help, please engage with us via comments on this blog post, or reach out to us.
Additional Reading
You can explore more on Liferay Forms by going to Forms User and Admin, Top 10 Features of Liferay Forms. From a developers perspective go to Liferay Forms Developer . And from a general Liferay DXP perspective you can take a peak at Top 5 Reasons to Choose Liferay DXP.