Hi everybody,
I am currently dealing with
https://issues.apache.org/jira/browse/WICKET-5265:
A FencedFeedbackPanel marks the component it uses as a fence with a
metadata entry, so that outer FencedFeedbackPanels do not traverse
beyond that component to "steal" feedback messages. When an FFP is
removed from the hierarchy (either by itself or as part of a subtree
that is being removed), onRemove is called on it (and on all other
components of that subtree). In onRemove, the FFP removes the fence
mark. So far, so correct.
When the FFP is added back to the page, there is no corresponding
lifecycle callback that would tell it about the fact that it has been
added back, so it cannot recreate the fence mark. This leads to outer
FFPs stealing the messages.
This "adding the same component back" is not a rare operation: It is
for example what the Wizard component does with its WizardStep panels.
They get removed and added all the time.
I tried working around this by using onConfigure and/or onBeforeRender.
In the really simple cases that works, but there can be more
complicated component hierarchies. Even in a simple wizard the way
Wicket traverses the tree and calls these two events can't guarantee
that the inner FFP's onConfigure is called before the outer FFP tries
to get the messages.
I have uploaded a quickstart to the WICKET-5265 ticket to demonstrate
this.
Using a filter to prevent already rendered messages from being rendered
again does not help, because then it is only the outer panel that shows
them, not the inner one where they should be.
martin-g suggested using onInitialize, but that does not work.
onInitialize is called only once over the entire lifetime of the
component, when it is first added to a page, and never again. It
doesn't matter whether one sees it as basically a delayed constructor
that allows calling overridden subclass methods, or as an indication of
"hey, your page is here for the first time". The problem is that it is
by definition only called once. The remove-and-add situation is not
covered.
onRemove is called every time a component (or a parent) is removed from
the page hierarchy, so components can clean up after themselves.
I think that just like behaviors have bind() and unbind(), components
should have a kind of onAdd() to complement onRemove(). I have an
implementation for this that is tested and works. With this, the
problem described above can be elegantly solved.
I named the method onAddToPage to make it more clear when it will be
called and to reduce the chance for a name collision.
I have defined it as follows:
onAddToPage is called whenever a component is added to a parent in a
way that connects it to the Page instance, but not before onInitialize
has been called.
For example:
- a component gets added to the page after it is constructed
1. onInitialize
2. onAddToPage
- a component is removed from the page and then added again
1. onAddToPage
- a component is added to a container that is already added to the page
1. onInitialize (if necessary)
2. onAddToPage
- a component is added to a container that is *not yet* added to the
page
1. nothing is called!
When the container is added to the page, its onAddToPage is called,
and recursively for all its children, just like all the other
lifecycle events.
I think this is quite well-defined. martin-g commented there might be
confusion with onInitialize.
The javadoc of onInitialize says:
> This method is meant to be used as an alternative to initialize
> components. Usually the component's constructor is used for this task,
> but sometimes a component cannot be initialized in isolation, it may
> need to access its parent component or its markup in order to fully
> initialize. This method is invoked once per component's lifecycle
> when a path exists from this component to the {@link Page} thus
> providing the component with an atomic callback
> when the component's environment is built out.
This is reasonably well-defined, though it completely ignores the fact
that onRemove might happen. Also, it only says that onInitialize will
be called "sometime before Component#onBeforeRender()" which I think is
more confusing than anything else. It should probably be changed to
"just before onConfigure".
I propose this for documentation of onAddToPage:
/**
* This method is called whenever a component is added to the component
* tree connected to the page,
* to allow it to initialize things that depend on other
* components in the page. This is similar
* to {@link #onInitialize()}. However, onInitialize is only
* ever called once, the first time the
* component is added. It is not called when a component is
* removed and then added again.
*
* This method, however, is called every time the component is
* added to the page's tree. It is
* never called before onInitialize(). It is thus the opposite
* of onRemove().
*
* Subclasses that override this must call super.onAddToPage().
*/
I think the important point here is that it is the opposite of
onRemove, and is meant to complement it.
I've created https://issues.apache.org/jira/browse/WICKET-5677 for this
and have pushed my implementation to the branch WICKET-5677.
What do you think?
Carl-Eric