TextField loses focus/selection when adding ComponentError

Hello everyone,

I am trying to build a form with two fields confirming each other - in other words, the user has to enter his email address twice and I want to check if they are equal.

My approach is to add a custom Validator to each field in which the value of the current field is compared to the value of the second field. This works perfectly. Next step was to validate both fields when one of them changes. I have done that with deactivating the default field validation and adding a listener to both fields. When the listener gets notified, it calls the validate of my two fields and I set the error message with setComponentError. Also that works.

Now the Problem:

When I change the value of the first field and jump to the second one with “tab”, the value inside the field is not selected and the cursor is positioned at the first position (in IE) or at the last position (at least in chrome). The default behaviour of TextFields is that a change from one field to another with “tab” is that the value of the new field is selected and the cursor is not positioned.

I have a code demo here so feel free to try it out, hopefully someone has a hint for me :slight_smile:


		Person p = new Person();
		p.setEmail("max@muster.de");
		p.setEmailRepeat("max@muster.de");
		p.setForename("Max");
		p.setName("Muster");
		
		myForm = new Form();
		myForm.setWriteThrough(false);
		myForm.setImmediate(true);
		myForm.setItemDataSource(new BeanItem<Person>(p));
		
		//	Attach the logic brick:
		new MyFormLogic(myForm);
		
		
		Button commit = new Button("commit");
		commit.addListener(new Button.ClickListener() {
			private static final long serialVersionUID = -8227976221537838397L;
			public void buttonClick(ClickEvent event) {
				myForm.commit();
			}
		});
		
		Button discard = new Button("discard");
		commit.addListener(new Button.ClickListener() {
			private static final long serialVersionUID = -5434678703904412833L;
			public void buttonClick(ClickEvent event) {
				myForm.discard();
			}
		});

package com.example.temp_mac_test_conditionalvalidator;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

import com.vaadin.data.Property;
import com.vaadin.data.Property.ValueChangeEvent;
import com.vaadin.data.Validator;
import com.vaadin.terminal.ErrorMessage;
import com.vaadin.ui.AbstractComponent;
import com.vaadin.ui.Field;
import com.vaadin.ui.Form;
import com.vaadin.ui.TextField;

public class MyFormLogic implements Serializable {
	private static final long serialVersionUID = -5903742307127723658L;
	private Form form;

	public MyFormLogic(Form aForm) {
		form = aForm;

		TextField email = (TextField) form.getField("email");
		TextField emailRepeat = (TextField) form.getField("emailRepeat");
		
		//	Enable direct event handling:
		email.setImmediate(true);
		emailRepeat.setImmediate(true);
		
		//	Disable default validation behavior:
		email.setValidationVisible(false);
		emailRepeat.setValidationVisible(false);
		
		//	Add validators which compare current value with value of other field:
		email.addValidator(new EqualValueValidator(emailRepeat));
		emailRepeat.addValidator(new EqualValueValidator(email));
		
		//	Enable own validation control:
		email.addListener(new MyValidatorControl(email, emailRepeat));
		emailRepeat.addListener(new MyValidatorControl(email, emailRepeat));
		
		
	}
	
	public class MyValidatorControl implements Property.ValueChangeListener {
		private static final long serialVersionUID = 2653132836678222194L;
		private List<Field> fieldList = new ArrayList<Field>();

		public MyValidatorControl(Field... aFields ) {
			for (Field f : aFields) {
				fieldList.add(f);
			}
		}
		
		public void valueChange(ValueChangeEvent event) {
			for (Field f: fieldList) {
				try {
					f.validate();
					((AbstractComponent) f).setComponentError(null);
				} catch (Exception e) {
					if (e instanceof ErrorMessage) {
						((AbstractComponent) f).setComponentError((ErrorMessage) e);
					}
				}
			}
		}
	}
	
	public class EqualValueValidator implements Validator {
		private static final long serialVersionUID = -2911514602683824227L;
		private Field foreignField;

		public EqualValueValidator(Field aForeignField) {
			foreignField = aForeignField;
		}

		public void validate(Object value) throws InvalidValueException {
			if (!isValid(value)) {
				throw new InvalidValueException("Value does not equal value of foreign field!");
			}
		}

		public boolean isValid(Object value) {
			if ((value != null && value.equals(foreignField.getValue())) || (value == null && foreignField.getValue() == null)) {
				return true;
			} else {
				return false;
			}
		}
	}

}

package com.example.temp_mac_test_conditionalvalidator;

import java.io.Serializable;

public class Person implements Serializable {
	private static final long serialVersionUID = -3480636645654695927L;
	private String name;
	private String forename;
	private String email;
	private String emailRepeat;
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public String getForename() {
		return forename;
	}
	public void setForename(String forename) {
		this.forename = forename;
	}
	public String getEmail() {
		return email;
	}
	public void setEmail(String email) {
		this.email = email;
	}
	public String getEmailRepeat() {
		return emailRepeat;
	}
	public void setEmailRepeat(String emailRepeat) {
		this.emailRepeat = emailRepeat;
	}
}

Setting the componentError for the TextField causes it to be repainted. Event this small example causes the focus to be lost it you modify the first field and tab to the second.

    @Override
    public void init() {
        Window mainWindow = new Window("Testfocus Application");
        setMainWindow(mainWindow);

        final TextField textField1 = new TextField();
        textField1.setImmediate(true);
        final TextField textField2 = new TextField();
        textField2.setImmediate(true);

        textField1.addListener(new Property.ValueChangeListener() {

            public void valueChange(ValueChangeEvent event) {
                textField2.requestRepaint();
            }
        });

        mainWindow.addComponent(textField1);
        mainWindow.addComponent(textField2);
    }

Sorry to say, but I didn’t come up with any work around for that. Please file a bug to vaadin trac if there’s not already one. Sorry, I didn’t have time to search.