AutoFields is an AlloyUI component which can be used inside a form to dynamically add multiple elements.

Liferay uses it in many points: have you ever tried to enter a phone number or an address into your user profile? Have you noticed the + and - buttons? By pressing these buttons whole portions of HTML are added (or removed) to the form, allowing you to add multiple items; and all is integrated with the history management.

So let's see how to use this component in a custom plugin.

Frontend side

The first thing to do is the JSP page which contains the form and all the items to clone:

<liferay-portlet:actionURL name="save" var="saveURL" />
<aui:form action="<%=saveURL %>" method="post" name="fm">
	<div id="auto-fields-container">
		<div class="lfr-form-row lfr-form-row-inline">
			<aui:input label="first-name" name="firstName1" />
			<aui:input label="last-name" name="lastName1" required="true" />
		</div>
	</div>

	<aui:button-row>
		<aui:button type="submit" value="save" />
    </aui:button-row>
</aui:form>

<aui:script use="liferay-auto-fields">
new Liferay.AutoFields({
	contentBox: '#auto-fields-container',
	fieldIndexes: '<portlet:namespace />rowIndexes',
	on: {
		'clone': function(event) {
			console.log('clone');
			console.log(event);
		},
		'delete': function(event) {
			console.log('delete');
			console.log(event);
		}
	},
	sortable: true,
	sortableHandle: '.lfr-form-row'
}).render();
</aui:script>

Let's try to understand JSP content: first of all there is the HTML form with its action.

Then we define the AutoFields container, namely the element which will contain all the cloned code parts; this element is represented by div#auto-fields-container.

Inside the container we then define the base block to clone; this block must be enclosed inside a div with the following CSS classes: lfr-form-row lfr-form-row-inline.

Now switch to the Javascript part, the most important one! We'll instantiate a new Liferay.AutoFields object with its configuration:

  • contentBox, this is the container element seen above;
  • fieldIndexes, this is the name of the hidden field which contains the active AutoFields indexes (you do not need to add it because the component does it for us);
  • on clone, this is the event triggered on block clone (optional);
  • on deletethis is the event triggered on block delete (optional);
  • sortable, it must true if cloned block can be moved by the mouse (optional);
  • sortableHandle, this is the CSS selector for the element to be moved by the mouse (optional).

But how does the component work exactly? And what is the fieldIndexes attribute?

As you may have noticed, the cloneable form elements are suffixed by 1 (firstName1 and lastName1); each time you press the + button, the HTML block is cloned and the suffixes of the new elements are incremented by 1 each time (firstName2 and lastName2, firstname3 and lastName3, ...). Instead when you press the - button the block is simply hidden.

For example, if you clone 3 times the HTML block, you'll get items whose names end with the suffix 1, 2, 3 and 4; then if you remove the blocks 2 and 3, only the blocks 1 and 4 will be shown. The fieldIndexes attribute represents exactly the indexes of the visible blocks, with a comma-separated string: 1,4.

Backend side

Let's see now what happens to the backend side after the submit of the form; it's all about the fieldIndexes attribute.

public void save(ActionRequest actionRequest, ActionResponse actionResponse) throws Exception {
	int[] rowIndexes = ParamUtil.getIntegerValues(actionRequest, "rowIndexes", new int[0]);

	for (int i : rowIndexes) {
		String firstName = ParamUtil.getString(actionRequest, "firstName" + i);
		String lastName = ParamUtil.getString(actionRequest, "lastName" + i);

		System.out.println(firstName + StringPool.SPACE + lastName);
	}
}

The first step is to retrieve the active blocks indexes (don't forget that fieldIndexes is only the configuration attribute name but the hidden field is called rowIndexes).

Now you can simply loop through the indexes and retrieve all the HTML form fields with the right suffix.

 

Only one thing is left to do, but I'll leave it to you as an exercise: once recovered the form values, these will be probably saved on database and so the JSP page must init all the cloned HTML blocks.

How to do that? Just retrieve the list of items to be displayed and make a loop that add as many div.lfr-form-row.lfr-form-row-inline as the items are, taking care to increase by 1 each time the suffix of the names; all the remaining part will be covered by the AutoFields component.