Extensible/nestable binders?

In my application I am making good use of Vaadin’s Binder concept (which I find superb and extremely elegant and powerful, btw).
But - since my application keeps growing - the desire came up to make these Binders nestable. I.e. could one form with a binder contain another (sub-)form that has its own binder? And could the outer binder’s status methods (isValid() / hasChanges() / …) as well as a binder’s statusListener-events also cover such “contained” sub-binders?
I guess that’s not readily available but is there maybe a suggested/preferred pattern to combine those? Or a good example?

I’ll try to explain what I mean by “nestable bindings” or “sub-bindings”:

I have an entity type, say, A with fields a,b,c:

class A {
	int a;
	boolean b;
	String c;
}

For that entity I have defined me a Form_A with a Binder<A> that handles the updating and validation of the form and a corresponding bean.

I have another entity, here called B

class B {
	String d;
	boolean e;
}

which is also used in other contexts and for which I already defined an other FormB and Binder<B>.

Now I want to extend A with an additional field of class B like so:

class A {
	int a;
	boolean b;
	String c;
	B x;
}

I know that I could extend A’s form with suited fields and connect these with A’s Binder using a nested property by referring to that fields via a dot notation, here e.g. as x.d and x.e. I find that approach not so scalable - esp. when considering real-life (i.e. bigger and much more complex) forms.

Rather I envision something where I could define a field and a binder as “sub-forms” by hooking these into the parent binders by specifying something like

FormA() {
	... // defining components for fields a, b and c 
	FormB formB = new FormB(); // FormB defines a Form for an entity `B` and a `Binder<B>`.
	add(formB);  // add the form for B as a sub-component (i.e. a part) of the form for A
	binderA.forField(x).useSubBinder(formB.getBinder()); // define `formB`s binder to handle field `x` and hook it into the binder for `FormA`
	...
}

...
foo = binderA.isValid();   // this should also contain whether all fields of `x` are valid
...
bar = binderA.hasChanges();  // this should also contain whether any fields of `x` have changed.

Hope, I could make myself clear…

There are couple of approaches you can play with.

  1. Write custom validators, that require the other form to be valid. I would assume this approach could be used also in “wizard” type forms that are splitted to steps, that are not necessarily nested.

binderA.forField(field) .withValidator(value -> binderB.isValid(),"Check input in subform"); .withValidator(value -> .., "value is not valid")

  1. Binder supports nested properties. This is a solution that works on a smaller scaler. Say you have Person object and Person has Address, which in turn consists of street address, postal code etc.

If you give true flag in constructor, the bindInstanceFields() will scan the nested properties.

binder = new Binder<>(Person.class,true);
binder.bindInstanceFields();

Lambdas allow complex binding patterns too

binder.forField(streetField).bind(person -> person.getAddress().getStreet())

Thanks for the suggestions. Currently I used an approach that’s similar to the last one, i.e. using nested fields. But that’s not really scalable or re-usable. I need to write specific code for every form that uses the sub-form and hence the nested properties.

I actually considered and started an attempt to extend the binder class and write me an “ExtendedBinder” but then had to give up that approach again. Unfortunately too many fields and methods in Binder are IMHO unnecessarily private. If they were protected I think it would have been feasible. I also tried to copy the Binder class into my own code and do the necessary changes on that copy but that attempt got grounded by a couple of internal interfaces and classes that Binder uses and which are package visible only in the com.vaadin.flow.data.binder package (e.g. ValidationResultWrap, SimpleResult, etc.) and which were thus not accessible from the package I had copied Binder into. :frowning: