Updated, more succint answer
My original answer went way beyond what your actual problem was, but I didn't quite realise until afterwards. Here's a more succint version, but I'll keep the original around at the bottom.
The issue you're having is because the naive extension I suggested has one critical flaw.
CompoundPropertyModel
will try to instantiate a model for any child component that does not have a model. When you use regular CompoundPropertyModel
this is not an issue; where the model is irrelevant, its object is never attempted to be obtained.
What's happening here is that this extension of the model will not only create a model for any model-less child, but will also try to evaluate it for the sake of checking what the type of the property is.
The problem with that is, that you might have children components (e.g. links) where a model will not necessarily be resolved to anything. In this specific instance you seem to have added a component whose ID is "dialog"; even though it might not necessarily need a model, CompoundPropertyModel
will still be invoked for it, and it'll try to get a property by the name of "dialog" from the underlying CustomerUI
object.
So in order to not encounter this issue you need to prevent your extension from evaluating the model every time. Something simple like
@Override
public <T> IWrapModel<T> wrapOnInheritance(Component component)
{
IWrapModel<T> actualModel;
actualModel = super.wrapOnInheritance(component);
if (component instanceof DateTextField && actualModel.getObject() instanceof LocalDate)
{
return new LocalDateModelButAlsoWrapping(actualModel);
}
else
{
return actualModel;
}
}
Hope it helps.
Original answer
To understand why your code is not working, you need to understand how CompoundPropertyModel
works.
I'll use a slightly modified code snippet of yours to illustrate this
IModel<CustomerUI> customerUIModel = Model.of(new CustomerUI());
detailsDialog = new MyCustomerDetailsDialog("createDialog", new CompoundPropertyModel<CustomerUI>(customerUIModel));
Let's imagine we want to add a label to the detailsDialog
that displays the "activeDate" property of the CustomerUI. If we were not using a CompoundPropertyModel
, then in order to achieve that we'd have to do something like the following:
IModel<LocalDate> activeDateModel = PropertyModel(customerUIModel, "activeUntil");
detailsDialog.add(new Label("dialog", activeDateModel));
What CompoundPropertyModel
does is lets us bypass providing a model to the component and doing simply
detailsDialog.add(new Label("activeDate"))
Then when the page is being rendered, a component will detect that it has no model and so it gives the parent models a chance to provide a model to it.
This is where CompoundPropertyModel
would kick in. It looks at what component is requesting to have a model provided and does exactly that. However, how does it do that? Well, quite simply it does
return new PropertyModel(innerModel, component.getId())
And so because of this simple piece of logic you can bypass instantiating your own models on the children components, and have the models provided to them by the parent's CompoundPropertyModel
. However the ID of the child component must be referring to some property of the model object that is stored in the CompoundPropertyModel. Otherwise you will get the exact error you're getting:
Property could not be resolved for class: class example.ui.customer.CustomerUI expression: dialog
Now let's look at your extension of the CompoundPropertyModel. When a component requests to get a model for itself, your extension does the following:
@Override
public <T> IWrapModel<T> wrapOnInheritance(Component component)
{
IWrapModel<T> actualModel;
actualModel = super.wrapOnInheritance(component);
if (actualModel.getObject() instanceof LocalDate)
{
return new LocalDateModelButAlsoWrapping(actualModel);
}
else
{
return actualModel;
}
}
What it does it invokes the default behavior of the CompoundPropertyModel
to instantiate a model for a component, and then it detects whether this model returns an instance of a LocalDate
and wraps that model in order to have a model that returns Date
instead.
Because your extension still relies on the default behavior of CompoundPropertyModel
, it will still use the component's wicket ID to determine what the name of the property within the inner model is. And so what's happening is this:
- You added a component to your
detailsDialog
whose wicket ID is
"dialog"
- On line
actualModel = super.wrapOnInheritance(component);
your extension creates a PropertyModel
to referring to your
CustomerUI
model and its property "dialog" (as that's the ID of
the component provided)
- On line
if (actualModel.getObject()
instanceof LocalDate)
you attempt to get the value of this model.
However, no property "dialog" exists on the CustomerUI
object and
so the error is thrown
So in the end you've done everything right. The only problem is that you added a child component whose wicket ID did not refer to a valid property.
Which also showcases a problem. The implementation I provided is naive. Whenever a component is added that does not have a model, this extended model will create a model for it but also it will always try to get its value. So if you add a child component which isn't even supposed to have a model, the compound property model will kick in even for those components.
So the more permanent solution would be something that does not involve trying to get the value of the model. E.g. you could check if the component is a DateTextField
:
@Override
public <T> IWrapModel<T> wrapOnInheritance(Component component)
{
IWrapModel<T> actualModel;
actualModel = super.wrapOnInheritance(component);
if (component instanceof DateTextField && actualModel.getObject() instanceof LocalDate)
{
return new LocalDateModelButAlsoWrapping(actualModel);
}
else
{
return actualModel;
}
}
This would defer looking at the model until after you established that the component whose model you're trying to instantiate really does need a LocalDate model.