issues with bean-fields when saving form

Hi everybody,

Ihave some issue going one with a large project that I have to do.

It’s about a form of a class Measure. This class has properties:
id [long]

name [String]

measureType [MeasureType]

sourceField [String]

ordinal [int]

measureGroup [MeasureGroup]

datType [DataType]

zeroSignificant [boolean]

The database is Oracle 10, I talk to it with Hibernate, via DAO and Service layer, and everything is Spring managed in a multi-module Maven project. The presentation layer is off course Vaadin.
Because it will be a very large GUI app, I try to scale everything up, though until now, I managed to work without add-ons.
I have a view with a combobox and a form. Each time you change the value of the combobox,the value of the form changes with. You choose a Measure, and it’s props are shown. Until there, everything works fine.
But when I save Measure (i.e. commit():wink: then the fields like String and int are nicely changed, but not the fields that represent another type. These fields are in my form an Optiongroup (MeasureType: 2 values), and 2 Comboboxes (DataType: 6 values and MeasureGroup: 750 values). The Measures are managed by MeasureContainer (with an inject of MeasureService), and all the 3 other types have a container as well; with each time the service-class injected (e.g.: DataTypeContainer with DataTtpeService).

Here is the code:

MeasureForm


 @SuppressWarnings("serial")
@Configurable(preConstruction = true)
public class MeasureForm extends Form implements ClickListener {

	// private static final long serialVersionUID = 1L;

	@Autowired
	private transient MeasureContainer measureContainer;
	@Autowired
	private transient MeasureTypeContainer measureTypeContainer;
	@Autowired
	private transient DataTypeContainer dataTypeContainer;
	@Autowired
	private transient MeasureGroupContainer measureGroupContainer;

	private static final String NULL_STRING = "";
	private static final String CAPTION_MEASURE_FORM = "Measure form";
	private static final String CAPTION_MEASURE_TYPE = "Measure type";
	private static final String CAPTION_SOURCE_FIELD = "Source field";
	private static final String CAPTION_MEASURE_GROUP = "Measure group";
	private static final String CAPTION_DATATYPE = "Datatype";
	private static final String CAPTION_ZERO_SIGNIFICANT = "Zero significant";
	private static final String BUTTON_SAVE = "Save";
	private static final String BUTTON_EDIT = "Edit";
	private static final String BUTTON_RESET = "Reset";
	private static final String BUTTON_CANCEL = "Cancel";
	private static final String ERROR_REQUIRED_NAME = "Name of the measure is required!";
	private static final String ERROR_REQUIRED_MEASURE_TYPE = "Measure type is required!";
	private static final String STYLE_OPTIONGROUP_HORIZONTAL = "horizontal";
	private static final String OPTION_PHYSICAL = "physical";

	private static final float COMMON_TEXT_FIELD_WIDTH = 20f;
	private static final float COMMON_NUM_FIELD_WIDTH = 5f;

	BeanItem<Measure> measureItem;

	private Long measureId = null;

	private Button saveButton = new Button(BUTTON_SAVE);
	private Button editButton = new Button(BUTTON_EDIT);
	private Button resetButton = new Button(BUTTON_RESET);
	private Button cancelButton = new Button(BUTTON_CANCEL);

	private TextField inputName;
	private TextField inputSourceField;
	private TextField inputOrdinal;
	private ComboBox selectOrInputMeasureGroup;
	private CheckBox checkZeroSignificant;
	private OptionGroup selectMeasureType;
	private ComboBox selectDataType;

	// to edit measures
	public MeasureForm(long measureId) {
		this.measureId = Long.valueOf(measureId);
		initializeForm();
		setupFields();
	}

	// for new measures
	public MeasureForm() {
		this.measureId = null;
		initializeForm();
		setupFields();
	}

	private void initializeForm() {
		setCaption(CAPTION_MEASURE_FORM);
		setWriteThrough(false); 
		HorizontalLayout buttonFooter = new HorizontalLayout();
		buttonFooter.setSpacing(true);
		buttonFooter.addComponent(editButton);
		buttonFooter.addComponent(saveButton);
		buttonFooter.addComponent(resetButton);
		buttonFooter.addComponent(cancelButton);
		saveButton.addListener((ClickListener)this);
		setFooter(buttonFooter);
		getFooter().setMargin(false, false, true, true);
	}

	public void changeMeasure(long newMeasureId) {
		setMeasureId(newMeasureId);
		setupFields();
	}

	private void setupFields() {
		Measure measure;
		if (measureId != null) {
			measureItem = measureContainer.getItem(measureId);
		} else {
			measure = new Measure();
			measureItem = new BeanItem<Measure>(measure);     //********** 1 regel omhoog geplaatst (binnen else) *******
		}

		setItemDataSource(measureItem);
		setVisibleItemProperties(MeasureContainer.NATURAL_COL_ORDER);

		inputName = ((TextField) getField("name"));
		inputName.setRequired(true);
		inputName.setRequiredError(ERROR_REQUIRED_NAME);
		inputName.setWidth(COMMON_TEXT_FIELD_WIDTH, UNITS_EM);
		inputName.setNullRepresentation(NULL_STRING);

		setupSelectMeasureType();
		removeItemProperty("measureType");
		addField("measureType", selectMeasureType);
		selectMeasureType.setRequired(true);
		selectMeasureType.setRequiredError(ERROR_REQUIRED_MEASURE_TYPE);
		selectMeasureType.setCaption(CAPTION_MEASURE_TYPE);
		//selectMeasureType.addStyleName(STYLE_OPTIONGROUP_HORIZONTAL); // ??? op 1 lijn zetten ???     !!! no stylesheet definded yet !!!
		selectMeasureType.setItemCaptionMode(AbstractSelect.ITEM_CAPTION_MODE_PROPERTY);
		selectMeasureType.setItemCaptionPropertyId("name");
		selectMeasureType.setNewItemsAllowed(false);
		selectMeasureType.setWriteThrough(false);         
		selectMeasureType.setImmediate(true);             
		selectMeasureType.setReadThrough(true);        

		inputSourceField = ((TextField) getField("sourceField"));
		inputSourceField.setCaption(CAPTION_SOURCE_FIELD);
		inputSourceField.setWidth(COMMON_TEXT_FIELD_WIDTH, UNITS_EM);
		inputSourceField.setNullRepresentation(NULL_STRING);

		inputOrdinal = ((TextField) getField("ordinal"));
		inputOrdinal.setWidth(COMMON_NUM_FIELD_WIDTH, UNITS_EM);
		inputOrdinal.setNullRepresentation(NULL_STRING);
//*
		setupSelectOrInputMeasureGroup();
		removeItemProperty("measureGroup");
		addField("measuerGroup", selectOrInputMeasureGroup);
		selectOrInputMeasureGroup.setCaption(CAPTION_MEASURE_GROUP);
		selectOrInputMeasureGroup.setItemCaptionMode(AbstractSelect.ITEM_CAPTION_MODE_PROPERTY);
		selectOrInputMeasureGroup.setItemCaptionPropertyId("name");
		selectOrInputMeasureGroup.setWidth(COMMON_TEXT_FIELD_WIDTH, UNITS_EM);
		selectOrInputMeasureGroup.setImmediate(true);       
		selectOrInputMeasureGroup.setReadThrough(true);    
//*/	
		setupSelectDataType();
		removeItemProperty("dataType");
		addField("dataType", selectDataType);		
		selectDataType.setCaption(CAPTION_DATATYPE);
		selectDataType.setItemCaptionMode(AbstractSelect.ITEM_CAPTION_MODE_PROPERTY);
		selectDataType.setItemCaptionPropertyId("name");
		selectDataType.setNewItemsAllowed(false);
		selectDataType.setNullSelectionAllowed(true);
		selectDataType.setImmediate(true);         
		selectDataType.setWriteThrough(false);     
		selectDataType.setReadThrough(true);       
		
		checkZeroSignificant = (CheckBox)getField("zeroSignificant");
		checkZeroSignificant.setCaption(CAPTION_ZERO_SIGNIFICANT);

		if (this.getMeasureId() == null) {
			System.out.println("In select physical method");
			selectMeasureType.select(1l);
		} else {
			selectMeasureType.setValue(measureItem.getBean().getMeasureType().getId());
			if(measureItem.getBean().getDataType()!=null){
				selectDataType.setValue(measureItem.getBean().getDataType().getId());
			}
			if(measureItem.getBean().getMeasureGroup()!=null){
				selectOrInputMeasureGroup.setValue(measureItem.getBean().getMeasureGroup().getId());
			}
		}
		
		//Some console output do do some provisional logging
		
		System.out.println(getItemPropertyIds());
		for(Object id:getItemPropertyIds()){
			System.out.println("["+id+"]
 "+getItemProperty(id));
		}
		for(long id:new long[]{689l, measureId}){
			BeanItem<Measure> item = measureContainer.getItem(id);
			Measure m = item.getBean();
			System.out.println(m.getId() + " - " + m.getMeasureType() + " - " + m.getDataType()+ " - " + m.getName() + " - "+m.getSourceField());
		}
	}

	private void setupSelectMeasureType() {
		selectMeasureType = new OptionGroup();
		selectMeasureType.setContainerDataSource(measureTypeContainer);
	}

	private void setupSelectDataType() {
		selectDataType = new ComboBox();
		selectDataType.setContainerDataSource(dataTypeContainer);
	}

	private void setupSelectOrInputMeasureGroup () {
		selectOrInputMeasureGroup = new ComboBox();
		selectOrInputMeasureGroup.setContainerDataSource(measureGroupContainer);
	} 
	
	public Long getMeasureId() {
		return measureId;
	}

	private void setMeasureId(Long measureId) {
		this.measureId = measureId;
	}

	private void setMeasureId(long measureId) {
		Long measureIdAsLong = Long.valueOf(measureId);
		setMeasureId(measureIdAsLong);
	}

	public Button getSaveButton() {
		return saveButton;
	}

	public Button getResetButton() {
		return resetButton;
	}

	public Button getCancelButton() {
		return cancelButton;
	}

	public void buttonClick(ClickEvent event) {
		final Button source = event.getButton();
		if (source==this.saveButton){
			saveMeasure();
		}else if(source==this.resetButton){
			
		}else if(source==this.cancelButton){
			
		}
	}

		private void saveMeasure() {
			commit();
			
			//Again some logging in the console to check the progam while dubugging
			System.out.println("In save method!");
			BeanItem<Measure> item = measureContainer.getItem(689l);
			Measure m = item.getBean();
			System.out.println(m.getId() + " - " + m.getMeasureType() + " - " + m.getDataType()+ " - " + m.getName() + " - "+m.getSourceField());
		}
}

MeasureContainer

@SuppressWarnings("serial")
@Configurable(preConstruction = true)
@Component
public class MeasureContainer extends BeanContainer<Long,Measure> implements
		Serializable {

	// private static final long serialVersionUID = 1L;

	@Autowired
	private transient MeasureService measureService;
	
	public static final Object[] NATURAL_COL_ORDER = {"id", "name", "measureType", "sourceField", "ordinal", "measureGroup", "dataType", "zeroSignificant"};

	public MeasureContainer() {
		super(Measure.class);
		initializeMeasureContainer();
	}
	
	private void initializeMeasureContainer(){
		//set BeanIdReslover
		setBeanIdProperty("id");
		
		//populate container
		List<Measure>measures=measureService.findAll();
		addAll(measures);		
	}
       //Hibernate storing code stiil has to be placed, I first talk with the container
}	

DataTypeContainer

@SuppressWarnings("serial")
@Configurable(preConstruction = true)
@Component
public class DataTypeContainer extends BeanContainer<Long, DataType> implements
		Serializable {

	// private static final long serialVersionUID = 1L;

	private static final String[] NATURAL_COL_ORDER = { "id", "name" };

	@Autowired
	private transient DataTypeService dataTypeService;

	public DataTypeContainer() {
		super(DataType.class);
		initDataTypeContainer();
	}

	private void initDataTypeContainer() {
		setBeanIdProperty("id");
		List<DataType> dataTypes = dataTypeService.find();
		addAll(dataTypes);
	}
}

When I preform a commit(), the code runs, I get no errors at all, and all my single (i.e. String, int, …) fields are well fixed in the container. But when I click to another measure and then click back to the measure I changed, only the single fields are changed.
I think (or better hope) this can be fixed by overridng a setValue() method somewhere, but untill now I didn’t manage to find which one …
After some days now and the I become desparous.
Thank for trying to help me out.
I’ll be very gracefull to the person that gives me the hint int the right direction!

I now implemented some functionality in the Reset-button, i.e. the discard() method, and when I use it, it resets the values all the fields, but not from the 3 fields that have a bean as property (the optiongroup and 2 selectboxes I talked about in previous post).
So it seems that the form is well build qua layout, all the components are nicely filled with the bean properties like they have to be, but in some way the fields, which are build by myself, are not attatched to the beanitem.
I also tried to do [code]
selectDataType.setPropertyDataSource(measureItem.getItemProperty(“dataType”));[code]
, but then I get an error while trying to save.
I also added a screenshot of the form to make my problem more clear.
11620.png

Ok,

No answer yet, but also no solution.
May be there is no answer because I have putted the problem to difficult, or let the app at its full size.

That’s way I have decided to extract the problem into a smaller app. Hope that this make’s my point more clear.

I have putteed a screenshot of what the app is like. With the upper Combobox, I populate the form (by clicking the name of a biker in the combobox, his data are been set into the form). That all goes well, exept that the Combox in the form shows as value an empty string, but he is populated . By the way, I populate the BikerForm and the upper Combobox with a BikerContainer and the FietsCombobox with a FietsContainer, both containers extend BeanContainer.
The biggest problem rises when I try to submit the form, by clicking Save, which calls commit();. I have taken the error that I get than into the screenshot of the Form.
I think I should write an own property for Fietsd, with a setValue() method, but I don’t know how to assign that property to the container without losing the Fiets in the BikerContainer.
Here’s the code of my Form:

@SuppressWarnings("serial")
public class BikerFormWithFieldFactory extends Form implements ClickListener {

	private BikerContainer bikerContainer;
	private FietsContainer fietsContainer = FietsContainer.returnFietsen();
	private Integer bikerId = null;
	BeanItem<Biker> bikerItem;

	private Button saveButton = new Button("Save");
	private Button resetButton = new Button("Reset");
	private Button cancelButton = new Button("Cancel");

	private TextField inputName;
	private TextField inputPlace;
	private ComboBox selectFiets;

	public BikerFormWithFieldFactory(BikerContainer bikerContainer) {
		System.out.println("Enter constructor");
		this.bikerContainer = bikerContainer;
		initializeForm();       // TE VANGEN
		setupFields();          // OMGEWISSELD MET VORIGE REGEL OM NULLPOINTERex
		System.out.println("Leave constructor");
	}

	public BikerFormWithFieldFactory(BikerContainer bikerContainer, int bikerId) {
		System.out.println("Enter constructor with id");
		setBikerId(bikerId);
		this.bikerContainer = bikerContainer;
		initializeForm();
		setupFields();             // ZIE VORIGE CONSTRUCTOR
		System.out.println("Leave constructor with id");
	}

	private void initializeForm() {
		System.out.println("Enter initializeForm");
		setCaption("Biker Form");
		setWriteThrough(false);
		HorizontalLayout buttonFooter = new HorizontalLayout();
		buttonFooter.setSpacing(true);
		buttonFooter.addComponent(saveButton);
		buttonFooter.addComponent(resetButton);
		buttonFooter.addComponent(cancelButton);
		saveButton.addListener((ClickListener) this);
		resetButton.addListener((ClickListener) this);
		setFooter(buttonFooter);
		getFooter().setMargin(true, true, true, true);
		setFormFieldFactory(new BikerFieldFactory());
		List<Object> orderedProperties = Arrays
		.asList(bikerContainer.NATURAL_COL_ORDER);
		setVisibleItemProperties(orderedProperties);
		System.out.println("Leave initializeForm");
	}

	private void setupFields() {
		System.out.println("Enter setupFields");
		if (bikerId != null) {
			bikerItem = bikerContainer.getItem(bikerId);
		} else {
			Biker biker = new Biker();
			bikerItem = new BeanItem<Biker>(biker);
		}
		setItemDataSource(bikerItem);
		/*
		if (getBikerId() == null) {
			System.out
					.println("Normally this should appear when try to add new biker");
		} else {
			selectFiets.setValue(bikerItem.getBean().getFiets().getId());
		}
*/

		System.out.println("Leave setupFields");
	}

	public Integer getBikerId() {
		return bikerId;
	}

	private void setBikerId(Integer bikerId) {
		this.bikerId = bikerId;
	}

	private void setBikerId(int bikerId) {
		Integer bikerIdAsInteger = Integer.valueOf(bikerId);
		setBikerId(bikerIdAsInteger);
	}

	public Button getSaveButton() {
		return saveButton;
	}

	public Button getResetButton() {
		return resetButton;
	}

	public Button getCancelButton() {
		return cancelButton;
	}

	public void changeBiker(int bikerId) {
		setBikerId(bikerId);
		setupFields();
	}

	private void saveBiker() {
		System.out.println("  ->\"Save\"");
		commit();
	}

	private void resetBikerForm() {
		System.out.println("  ->\"Reset\"");
		discard();
	}

	@Override
	public void buttonClick(ClickEvent event) {
		final Button source = event.getButton();
		System.out.println("Click");
		if (source == getSaveButton()) {
			saveBiker();
		} else if (source == getResetButton()) {
			resetBikerForm();
		}
	}
	
	private class BikerFieldFactory extends DefaultFieldFactory{
		
		public BikerFieldFactory() {
			selectFiets = new ComboBox();
			selectFiets.setContainerDataSource(fietsContainer);
			selectFiets.setCaption("Fiets");
			selectFiets.setItemCaptionMode(ComboBox.ITEM_CAPTION_MODE_PROPERTY);
			selectFiets.setItemCaptionPropertyId("name");
			selectFiets.setNewItemsAllowed(false);
			selectFiets.setNullSelectionAllowed(true);
			selectFiets.setImmediate(true); // MSS KIJKEN OF DEZE NIET BETER FALSE IS
			selectFiets.setWriteThrough(false);
			selectFiets.setReadThrough(true); // ZEKER DAT DIT true MOET ZIJN ????

		}
		
		@Override
		public Field createField(Item item, Object propertyId,
				Component uiContext) {
			Field field;
			if("fiets".equals(propertyId)){
				return selectFiets;
			}else{
				field = super.createField(item, propertyId, uiContext);
			}
			if("name".equals(propertyId)){
				inputName = (TextField) field;
				inputName.setWidth(20F, UNITS_EM);
				inputName.setNullRepresentation("");
			}else if("place".equals(propertyId)){
				inputPlace = (TextField) field;
				inputPlace.setWidth(15f, UNITS_EM);
			}
			return super.createField(item, propertyId, uiContext);
		}
	}
}

I really hope I have made the poblem better understandable so that somebody can help me out.
Tnaks in advance.

Greets,

    Benoit

11633.png

Hi,

Yes, concise questions beget answers.

Your problem looks that your bean has a property of type Fiets, but the class has no constructor “Fiets(String)”.

The problem can be caused by a situation where you have a class of which value you edit in a TextField, which edits Strings. You can either 1) provide the said conversion constructor, or 2) provide a type-specific editor, such as a Form bound to BeanItem, for the property in the FieldFactory.

If you’re a Pro Account subscriber, please see article
#295: Why do I get a ConversionException on commit?

Uh, the question was still too long I guess, as I just read the error and made too many assumptions…

The ComboBox is another case where this can occur. In your case, its property value is an ID, but a Fiets object is expected, so an error occurs. I suppose you could solve that in a number of ways. Probably the simplest solution would be to use Fiets objects as IDs in the container bound to the ComboBox. That would be the case automatically if you use a BeanItemContainer. …I think.

This is an interesting case and related to subforms, which use I’ve been documenting a bit lately. I’ll try to make an example.

I have some helper classes (based on the FieldWrapper in the
CustomField add-on
) that convert between an id and a value, or a set of ids and a set of values. If you use a BeanItemContainer, they should not be needed, but with many other containers the ids are always something else than the entity instances.

They are currently used by the Vaadin Spring Roo add-on, but I’m trying to make a separate Vaadin add-on out of them and some other helpers.

I finally added some of these helper classes in the CustomField add-on version 0.9.0 - check out BeanFieldWrapper, BeanSetFieldWrapper and the sample application for them (demo also running
here
, see the last tab).

Note that many package names changed in this add-on version!

Hey guys,

First of all, thank you very much for your help!

I have tried to use a BeanItemContainer for Fiets (btw, fiets is dutch for bike, but I didn’t wanted to used bike because that looked too much on biker). Than I get The error no more when I save, but the Combobox isn’t connected to the Item. When I start the app, the selected Combobox value is a blank field, then I can select the fiets that I want, press save, no error, but when I change the biker, the fiets value that I have comitted for the prevoious biker stays.
So the combobox is connected to the from I guess, but not to the item that is shown in the form.
You also can see that if you change the place name and fiets and then push Reset (->discard()), the place and name fields are restored to their previous value, but not the fiets.

I have added a zip file with the cleaned project to this post. If you want you can check it once, because I am not always sure how to explain things that other understand what I mean.

In the meanwhile I will study the code of CustomField and FieldWrapper, because in the sample app that is on line, there happens exactly what I try to do.

Marko & Henri, thanks once more for helping.
11634.zip (3.59 MB)

There’s a
new example
about binding a subproperty to a selection component (ComboBox). I think it’s very close to your case.

Sorry I can not try out your advices for the moment, but we are busy with a move-to-production in another problem, and some issues related with that came up.
That doesn’t mean I’m no longer interested in techniques to handle this issue.
I hope I can continue in the weekend with it and then I bring feedback.
I see in that almost all the examples a String is used as id. Personally I find that a long ( or int) is more natural as id.
Is there a global approach from Vaadin towards this topic?

You can use any object type as property or item identifier, such as Integer, but not int.

But you can use ints and longs for property values in a bean.

I’m new to Vaadin and I had the same problem. I’ve spent quite a few hours on it and I think I got a solution. This is how I’ve done it so far… would love to get some feedback from experienced Vaadin developers.

I looked at the CustomField add-on which was mentioned previously in the thread, but didn’t feel that I have enough knowledge to just add another addon. Instead I wanted to solve it with the standard Vaadin classes.

Thoughts

  • I do get the feeling that I’m doing something wrong somewhere since this seems to be a very common use case, but I can’t figure out the simple solution. Is there something like a “listBox.registerItemConverter()” somewhere?

  • I started out with a List in my class… that was a misstake since AbstractSelect#getValue() will return a Set even if my property is a List as far as I can see. When I used a List Vaadin threw a “NoSuchMethodException: java.util.List.(java.lang.String)” because it couldn’t find a constructor with a String argument.

  • I don’t want to use the Entity as the ItemID since this will load a lot of objects into memory when the system is in production. That leaves an ID. At commit time the LazyListSelect will access the data source (items) which will fetch the needed objects from the database.

public class LazyListSelect extends ListSelect {

    public LazyListSelect(String caption, Container dataSource) {
        super(caption, dataSource);
    }

    @Override
    public Object getValue() {
        final Object retValue = super.getValue();
        if (retValue instanceof Set) {
            Set<Integer> ids = (Set<Integer>)retValue;
            return replaceIdsWithObjects(ids);
        } else {
            return retValue;
        }
    }

    protected Set replaceIdsWithObjects(Set<Integer> ids) {
        Set entitySet = new HashSet();

        for(Integer id : ids) {
            Item item = items.getItem(id);
            if(item instanceof BeanItem) {
                entitySet.add(((BeanItem)item).getBean());
            } else {
                entitySet.add(id);
            }
        }

        return entitySet;
    }

}

Hi there.

I ran into the same problem of conversion exception when commiting the form - as long as I use the HbnContainer.
When I use the BeanItemContainer, saving is ok - but then I get another problem: The current value is not displayed in the ComboBox.

See
this thread
for details.

Can anybody supply a working example of a Bean with nested property, which is displayed as ComboBox?

Hi.

In
the other thread
Henri Sara helped to find a solution for the BeanItemContainer.

And while taking a closer look at the equals() method I realized, that in case of the HbnContainer there was a comparison between IDs (Long) from the container and a Department-Object from the current value.

I still do not know how to tell the HbnContainer to use the entity as ItemId instead of its id property. But as the BeanItemContainer now works, I will use this for filling and displaying the ComboBox.

Bye,
Horst

HbnContainer does not support using the entity itself as its id.

For conversions for the UI, see the demos for HbnContainer or use FieldWrapper and converters from the CustomField add-on. If I remember correctly, in the CustomFieldUtils add-on, there is even a helper class specifically for entity-id mappings, although I’m not sure if it supports HbnContainer. Even if it doesn’t, adapting it should be straight-forward.

In Vaadin 7, conversions are simpler to do and do not require add-ons.