Databinding should not call ValueChangeListeners

… but it does; on both the form and the table.

Here is the use-case:

I have a form that allows admins to create new users. Users have a username, first name, last name, email address, account and location. Admins can enter whatever username they want; but by default, the username should be a combination of the first part of the email address (basically whatever appears before the @) and the name of the account. If the email address or the account are changed ; the username is dynamically changed.

In order to do this; I’m adding ValueChangeListeners to the email and account fields when they are being created by the FieldFactory. I.e.:


else if(propertyId.equals(UserField.EMAIL.getPropertyName()))
		{
			field = new TextField(UserField.EMAIL.getCaption());
			((AbstractTextField)field).setNullRepresentation("");
			((AbstractTextField)field).setImmediate(true);
			field.setWidth(FIELD_WIDTH);
			field.setRequired(true);
			field.addValidator( new EmailValidator("Not a valid email address."));
			
			if(Form.class.isAssignableFrom(uiContext.getClass()))
			{
				field.addListener(new FormEmailValueChangeListener((Form)uiContext));
			}
			
		}
		else if(propertyId.equals(UserField.ACCOUNT.getPropertyName()))
		{
			ComboBox accountField = new ComboBox(UserField.ACCOUNT.getCaption());
			accountField.setImmediate(true);
			accountField.setContainerDataSource(accountContainer);
			accountField.setItemCaptionPropertyId("accountCode");
			accountField.setMultiSelect(false);
			accountField.setNullSelectionAllowed(false);
			accountField.setNewItemsAllowed(false);
			accountField.setWidth(FIELD_WIDTH);
			accountField.setRequired(true);
			if(Form.class.isAssignableFrom(uiContext.getClass()))
			{
				accountField.addListener(new FormAccountValueChangeListener((Form)uiContext));
			}
			field=accountField;
		}

and the listeners are:


class FormEmailValueChangeListener implements Property.ValueChangeListener
	{
		protected Form form;
		
		public FormEmailValueChangeListener(Form form)
		{
			this.form = form;
		}

		public void valueChange(ValueChangeEvent event) {
			try
			{
				Account account= null;
				String accountCode= null;
				String email=null;
				
				if(form.getField(UserField.ACCOUNT.getPropertyName())!= null)			
				{
					account = (Account)form.getField(UserField.ACCOUNT.getPropertyName()).getValue();
					if(account!= null)
					{
						accountCode= account.getAccountCode();
					}
				}
				
				if(event.getProperty()!= null && event.getProperty().getValue()!= null)			
				{
					email= (String)event.getProperty().getValue();
					
				}
				
				
				if(form.getField(UserField.USER_NAME.getPropertyName())!= null)
				{
					Field usernameField = form.getField(UserField.USER_NAME.getPropertyName());
					usernameField.setValue(ProvManagerUtils.generateUsername(email, account));
				}
				
			}
			catch(UsernameGenerationException exc)
			{
				if(form.getField(UserField.USER_NAME.getPropertyName())!= null)
				{
					Field usernameField = form.getField(UserField.USER_NAME.getPropertyName());
					usernameField.setValue(null);
				}
				
			}
			
		
			
		}
		
	}


class FormAccountValueChangeListener implements Property.ValueChangeListener
	{
		protected Form form;
		
		public FormAccountValueChangeListener(Form form)
		{
			this.form= form;
			
			
		}

		public void valueChange(ValueChangeEvent event) {
			try
			{
				Account account= null;
				String accountCode= null;
				String email=null;
				
				if(event.getProperty()!= null && event.getProperty().getValue()!= null)
				{
					account = (Account)event.getProperty().getValue();
					if(account!= null)
					{
						accountCode= account.getAccountCode();
					}
				}
				
				if(form.getField(UserField.EMAIL.getPropertyName())!= null)				
				{
					email= (String)form.getField(UserField.EMAIL.getPropertyName()).getValue();
					
				}
				
				if(form.getField(UserField.USER_NAME.getPropertyName())!= null)
				{
					Field usernameField = form.getField(UserField.USER_NAME.getPropertyName());
					usernameField.setValue(ProvManagerUtils.generateUsername(email, account));
				}
				
			}
			catch(UsernameGenerationException exc)
			{
				if(form.getField(UserField.USER_NAME.getPropertyName())!= null)
				{
					Field usernameField = form.getField(UserField.USER_NAME.getPropertyName());
					usernameField.setValue(null);
				}
				
			}
			
		}
	}



Form looks like this:

This works fine for new users… when you type in valid email address and select an account; the username is automatically set.

However; the problems starts when you load an existing user. The Databinding process actually invokes the Property.ValueChangeListeners() (presumably because under the hood; it just calls Field.setValue(); which in turn invokes any attached ValueChangeListeners). Since the listeners now are triggered; the username is changed on load and basically overwrites any custom username an admin may have set.

So, in a nutshell; the problem is this: ValueChangeListeners are triggered on fields whenever setValue is called. This can happen either because of user interaction OR because of databinding. If you want to do something specific as a result of user interaction; you’re now screwed because you cannot distinguish that user interaction from the actual databinding process…

From my perspective, this should not happen. IF developers want to be notified about the databinding process; a separate DatabindingListener (or something similar) should be fired.

Why don’t you just add a check using Field.getValue() to see if the username has already been filled out?