Validation failure message for TextField in Grid not shown

Hi,

I created a grid and added a header row to it. In the header row i added textfield for each column (to enable filtering of that column based on the text entered). Columns could be of Non String type (Numeric, Integer, Double, etc). I want that validation error is shown when a user enters a string inside the text field where the column type is non string.

I get InvalidValueException in the console, but nothign on the UI.

            for(Grid.Column col : grid.getColumns())
                {
                    com.vaadin.ui.Field field = beanFieldGroup.buildAndBind(null,col.getPropertyId());
                    if(TextField.class.isAssignableFrom(field.getClass()))
                    {
                        ((TextField)field).setNullRepresentation("");
                        ((TextField)field).setTextChangeEventMode(AbstractTextField.TextChangeEventMode.LAZY);
                        ((TextField)field).setImmediate(true);
                        ((TextField)field).setValidationVisible(true);
                        ((TextField)field).addTextChangeListener(new FieldEvents.TextChangeListener() {
                            @Override
                            public void textChange(FieldEvents.TextChangeEvent event) {
                                try {
                                    ((TextField)field).setValue(event.getText());

                                    // workaround cursor position problem
                                    ((TextField)field).setCursorPosition(event.getCursorPosition());

                                    ((TextField)field).validate();
                                    } catch (InvalidValueException e) {
                                        ((TextField)field).setComponentError(new SystemError(e));
                                    }
                            }
                        });
                    }
                    filteringHeader.getCell(col.getPropertyId()).setComponent(field);
                    
                }

If I am not wrong, I shouldn’t even be writing the text listner. Since I have BeanFieldGroup, it should automatically create Validators for the text fields and trigger it.

What am I doing wrong?

thanks for the help,
Chahat

Hi,

Try using a custom field factory and add a custom “number validator” when required. For example:

beanFieldGroup.setFieldFactory(new DefaultFieldGroupFieldFactory() {

    @Override
    public <T extends Field> T createField(Class<?> dataType, Class<T> fieldType) {
        T field = super.createField(dataType, fieldType);

        if (Number.class.isAssignableFrom(dataType)) {
            field.addValidator(value -> {
                if (value != null && !value.toString().isEmpty() && !value.toString().matches("-?\\d+(\\.\\d+)?")) {
                    throw new InvalidValueException("Please specify a number");
                }
            });
        }

        return field;
    }
});

Dear Alejandro,

Thanks for your reply. Unfortuntely it leads to same result

The InvalidValueException you throw leads to stacktrace printing on the console
SEVERE:
com.vaadin.data.Validator$InvalidValueException: Could not convert value to Integer
at com.vaadin.ui.AbstractField.validate(AbstractField.java:985)
at com.vaadin.ui.AbstractField.validate(AbstractField.java:960)
at com.aahar.ui.design.grid.GridPanel$2.textChange(GridPanel.java:266)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)

However I want to show the User on the UI that the value entered was not a number

for example Like in the sampler example http://demo.vaadin.com/sampler/#databinding/declarative-validation

the fields show error when validation fails.

thanks
Chahat
28211.png

Could it be because the fields are added to the cols of header row of a grid? Is it possible that Header row is not suppose to show validation errors??

Nope, I tested the code I published and it works. Did you remember to remove the TextChangeListener when trying with my aproach?

Hi Alejandro,

I guess i did remove the TextChangeListner, I have a break point inside the validator, Now the break point is not even hit when i loose focus after entering a wrong value in the textfield. Below is my code

[code]
T instance = clazz.getConstructor().newInstance();
final BeanFieldGroup beanFieldGroup = new BeanFieldGroup(clazz);
beanFieldGroup.setItemDataSource(instance);
beanFieldGroup.setFieldFactory( new FieldGroupFieldFactory() {

                @Override
                public <T extends com.vaadin.ui.Field> T createField(Class<?> dataType, Class<T> fieldType) {
                    T field = DefaultFieldGroupFieldFactory.get().createField(dataType, fieldType);
                    
                    if (Number.class.isAssignableFrom(dataType)) {
                        field.addValidator(value -> {
                            if (value != null && !value.toString().isEmpty() && !value.toString().matches("-?\\d+(\\.\\d+)?")) {
                                throw new InvalidValueException("Please specify a number");
                            }
                        });
                    }

                    return field;
                }
            });
            for(Grid.Column col : grid.getColumns())
            {
                com.vaadin.ui.Field field = beanFieldGroup.buildAndBind(null,col.getPropertyId());
                if(TextField.class.isAssignableFrom(field.getClass()))
                {
                    ((TextField)field).setNullRepresentation("");
                    ((TextField)field).setTextChangeEventMode(AbstractTextField.TextChangeEventMode.LAZY);
                    ((TextField)field).setImmediate(true);
                    ((TextField)field).setValidationVisible(true);
                }
                filteringHeader.getCell(col.getPropertyId()).setComponent(field);
                
            }

[/code]I could not create an instance of new DefaultFieldGroupFieldFactory() like you did. Probably different Vaadin versions, but I suppose what I did should have worked as well.

Thanks for the help :slight_smile:

thanks
Chahat

I’m using Vaadin 7.7.1. Here’s the code that worked for me:

beanFieldGroup.setFieldFactory(new DefaultFieldGroupFieldFactory() {
    @Override
    public <T extends Field> T createField(Class<?> dataType, Class<T> fieldType) {
        T field = super.createField(dataType, fieldType);
        if (Number.class.isAssignableFrom(dataType)) {
            field.addValidator(value -> {
                if (value != null && !value.toString().isEmpty() && !value.toString().matches("-?\\d+(\\.\\d+)?")) {
                    throw new InvalidValueException("Please specify a number");
                }
            });
        }
        return field;
    }
});

for (Grid.Column col : grid.getColumns()) {
    Field<?> field = beanFieldGroup.buildAndBind(null, col.getPropertyId());
    filteringHeader.getCell(col.getPropertyId()).setComponent(field);
}

Dear Alejandro,

I was using 7.7.0, I upgraded to 7.7.1

Made a very simple test

VerticalLayout lay = new VerticalLayout();
        lay.setSizeFull();
        
        Grid grid = new Grid("Testing");
        BeanItemContainer<RecipeList> cont = new BeanItemContainer<>(RecipeList.class);
        cont.addAll(stb.getRecipeList());
        
        grid.setContainerDataSource(cont);
        grid.setSizeFull();
        
        BeanFieldGroup<RecipeList> beanFieldGroup = new BeanFieldGroup<>(RecipeList.class);
        beanFieldGroup.setFieldFactory(new DefaultFieldGroupFieldFactory() {
            @Override
            public <T extends Field> T createField(Class<?> dataType, Class<T> fieldType) {
                T field = super.createField(dataType, fieldType);
                if (Number.class.isAssignableFrom(dataType)) {
                    field.addValidator(value -> {
                        if (value != null && !value.toString().isEmpty() && !value.toString().matches("-?\\d+(\\.\\d+)?")) {
                            throw new InvalidValueException("Please specify a number");
                        }
                    });
                }
                return field;
            }
        });

        HeaderRow filteringHeader = grid.addHeaderRowAt(0);
        for (Grid.Column col : grid.getColumns()) {
            Field<?> field = beanFieldGroup.buildAndBind(null, col.getPropertyId());
            filteringHeader.getCell(col.getPropertyId()).setComponent(field);
        }
        
        lay.addComponent(grid);
        
        testingTab.setContent(lay);

Everything as you told me to do. I still do not see any error on the UI. The debugger does enter the InvalidValueException, I follwed the code a bit and it seems the code flow does want to communicate the error to the GUI. BUT it doesnt.

Please see attched the GUI screenshot. (FOODID textfield is bound to Integer type)

I would expect the text field with wrong entry to be marked red or something,

It is frustrating to learn you can make it work and I cannot :frowning:

Thanks for the help.

Thanks
Chahat

28221.png

You are using the “reindeer” theme. Unless, the “valo” theme, I think “reindeer” won’t change the look of the field when a validation fails, but you should see a tooltip with the error when the validation fails if you put the mouse pointer over the field.

Hehe Bingo:)

I knew it was some tiny detail we were missing.

Thanks a lot, Bringing the mouse curser I do see the error.

Hi there,
using vaadin 8.0.5 and the Grid row Editor in buffered mode,
bean is not updated when Validator fails, but Editor closes and I see no Error message …
I expect it to stay open and tell me why validation fails.

final TextField nameEditor = new TextField();
final Binder<Site> binder = grid.getEditor().getBinder();
final Binder.Binding<Site, String> nameBinding = binder
    .withValidator( site -> {
        System.err.println("VALIDATE name");
        return site.getName().length() < 20;·
    }, "Name must be less than 20 characters long")
    .bind(nameEditor, Site::getName, Site::setName);
grid.addColumn(Site::getName)
    .setId("name")
    .setCaption("Site Name")
    .setEditorBinding(nameBinding);
grid.getEditor().setEnabled(true);
//grid.getEditor().setErrorGenerator((colMap, status) -> {
//    System.err.println("           Editor has errors : " + status.hasErrors());
//    return "Error String";
//});
grid.getEditor().getBinder().addStatusChangeListener( evt ->
    System.err.println("Status Change :" + evt. ▸  hasValidationErrors()));
grid.getEditor().addSaveListener( evt -> {
    System.err.println("SAVE");
    try { evt.getBean().save(); }
    catch(Exception e) { System.err.println(e.getMessage()); }
});

[java] VALIDATE name [java] Status Change :true thanks in advance :wink:

Hi, in case you still have the issue. This is a possible implementation that works as expected:

Grid<Site> grid = new Grid<>();
grid.getEditor().setEnabled(true);
grid.getEditor().addSaveListener(t -> t.getBean().save());

TextField name = new TextField("Name");
Binder<Site> binder = grid.getEditor().getBinder();
Binder.Binding<Site, String> binding = binder.forField(name)
        .withValidator(new StringLengthValidator("Wrong length", 0, 19))
        .bind(Site::getName, Site::setName);

grid.addColumn(Site::getName).setEditorBinding(binding).setEditorComponent(name, Site::setName);

Hi, thanks for your attention, I’ve tested it and nailed the difference,

using forField(…) is the magic incantation, but then your validator deals with the value of the field

// works
final Binder.Binding<Site, String> binding =
    binder.forField(name)
    .withValidator(string -> string.length() > 6, "at least 6 chars")
    .bind(Site::getName, Site::setName);

as I would prefer to work with my object and simply use one of it’s method, the following fails

// does not work final Binder.Binding<Site, String> binding = binder .withValidator(obj -> obj.isValid(), "is not valid") .bind(name, Site::getName, Site::setName); using setEditorComponent(…) or not doesn’t change anything,
validation is not called at value change.

still .bind(name, Site::getName, Site::setName) calls forField(field).bind(getter, setter);
https://github.com/vaadin/framework/blob/master/server/src/main/java/com/vaadin/data/Binder.java#L1271

there is something fishy under there,
I’ll try to investigate more later on.

Keep in mind that the buffered mode won’t make the validations until you click “save”.