Changing Form Fields based on user choices

Previously, I’ve created a somewhat dynamic form so that when a user checks a box, or chooses something from a NativeSelect, I can make areas appear/disappear that make sense based on those selections. It works fine as long as the fields themselves don’t need to move or change their input type.

I have a new requirement for a Form that has a NativeSelect that defines the object’s “type”. In this case, it’s not enough to just make other fields appear/disappear. Instead, I want to actually have a changing layout, such as something like this (where [Field]
is used to represent a form field):

+++
[Type NativeSelect]

[Name TextField]
[Align NativeSelect]

[InitialValue TextField]
[Tooltip TextField]

[InputFormat TextField]
[OutputFormat TextField]

+++

Then, when the user uses the Type select box to change to another type, I need to relayout the form like:

+++
[Type NativeSelect]

[Name TextField]
[Align NativeSelect]

[OutputFormat NativeSelect]
[Tooltip TextField]

+++

So, I have removed the [InitialValue TextField]
as well as the [InputFormat TextField]
, and the [OutputFormat TextField]
is now a NativeSelect (still sets a String value in my item, one is just free form, while the other is constrained by the select box’s choices) and has moved up a row.

Can a Form be made to do this? I’ve been trying to trick the Form by rebuilding it entirely through setItemDataSource(), but it’s not really working, even after I beat it from getting stuck in loops. I think I am going about this the wrong way, and wondered if there is a right way, or whether this cannot be done. I prefer not to have a popup “type-specific” editor to handle this as it puts the type-specific fields out of initial view – something I only want to do for “detail configuration” that most users can ignore.

Thanks for any ideas!

No ideas?

Any way for a Form to allow itself to be rebuilt based on action taken on the Form? It seems unlikely I can react to a Form event and in the listener rebuild a replacement Form.

I’ll give you my 2 cents on how I did this, but don’t know if this is the right approach or not.

Basically, I took the fields that vary based on our ‘type’ select box field and put them in a sub-Form – a new Form that I just embed in the main form. So the main form doesn’t create those fields anymore, just the sub-Form field is created:

@Override
public Field createField(Item item, Object propertyId, Component uiContext) {
	if (propertyId.equals("typeBean")) {
		typeBeanTabIndex = tabIndex;
		TypeSubForm form = new TypeSubForm(vaadinApp,thisForm,tabIndex++);
		return form;
	}...

Then the sub-Form’s setItemDataSource() can check the type from the parent form and set the ‘orderedProperties’ for its call to

super.setItemDataSource(newDataSource, orderedProperties);

So the subform will only create the fields needed for that type, in the right order for that type. It only has to concern itself with laying out the fields needed based on the parent form’s ‘type’ field.

Then in the main form, I do things as before, but don’t have to create those variable fields or deal with their variable layout, just create sub-Form and attach it to the layout.

When the user selects a different type from ‘immediate’ NativeSelect, the ValueChangeListener calls a “rebuildSubForm” method that does this:

Field typeBeanField = getField("typeBean");
thisForm.detachField(typeBeanField);
TypeSubForm typeSubFormField = new TypeSubForm(vaadinApp,thisForm,typeBeanTabIndex);
typeSubFormField.setPropertyDataSource(this.getItemDataSource().getItemProperty("typeBean"));
thisForm.addField("typeBean",typeSubFormField);

First, get the sub-Form field (typeBeanField), detach it from my Form, create a new sub-Form (I have extra code to set the tab indexes nicely), set it’s data source again and then add it back to my form.

Also, in the attachField() handler, I do this:

if (propertyId.equals("typeBean")) {
       	Component prev = layout.getComponent(0, 3);
       	if ( prev == null )
       		layout.addComponent(field, 0, 3, 2, 3);
       	else
       		layout.replaceComponent(prev, field);
 }

This basically checks if I have the sub-form in my layout, replace it with the new field, else just add as normal.

It appears to work. Don’t know if it’s the best way, the recommended way, or anything like that, but figured I’d share it should it help others (or put me in the right direction if I don’t really have it right) who need to have entire sections of a form be dynamic based on changed values in the form itself.