I am developing a JSF custom component. This component has the purpose of encapsulating another component (namely a PrimeFaces table) and adding customized behaviour to it.
The simple answer is: it is the other frameworks fault that is in use. This framework overwrites the state saving mechanism of JSF and replaces it with a custom mechanism. The implementation, however, was faulty and did not take care of the DynamicAddRemoveListener
s responsible for saving the actions of dynamic componants correctly. They fixed the bug, now it works fine.
However, there were several things that were necessary to fix my component that I want to point out:
First, @BalusC pointed me to the correct way of moving child components in a custom JSF component: it should be done using a PostAddToView
event listener.
@ListenerFor(systemEventClass=PostAddToViewEvent.class) public class YourComponent extends SomeUIComponent { @Override public void processEvent(ComponentSystemEvent event) { if (event instanceof PostAddToViewEvent) { targetParent.getChildren().add(componentToMove); } } }
This approach has the drawback, however, that the components attributes won't be set at this point. So if you need those, components can only be created/moved during render response phase.
Second, child components of a custom JSF component should not be saved in the StateHelper
. They should be recreated on every request so that JSF finds these components when replaying the dynamic actions.
Third, the ID of dynamically created child components (if set) should always be set when the component is created itself. My custom component set the ID of its children components only during render response phase, so when JSF tried to replay the dynamic actions, it could not find the respective components. This was the solution to the problem mentioned above in the section Partial state saving enabled.
So with all these adaptions and the fix for the other framework, now finally my component works as desired.
JSF state management remembers dynamic manipulations to the component tree so that it can ensure that it's after the restore view of a postback exactly the same as it was during render response of the previous request which generated the post form.
The exception which you got,
javax.faces.FacesException: Cannot remove the same component twice: table:additional
basically tells that you've added the very same component twice to the same parent.
In other words, you obtained the desired component from the component tree and then added it to the desired parent. However, as per the given exception, it was at that moment already attached to the desired parent! Effectively, you performed a no-op. But JSF remembers every single dynamic component add/remove through postbacks on the same view, even though it's effectively a no-op. That part may in turn be a bug in JSF implementation itself, but you should in first place not be moving around components when they are aready in the desired place.
A quick fix would be to check the component's parent by UIComponent#getParent()
if it isn't already the desired one and if so then skip the getChildren().add()
call.
if (!componentToMove.getParent().equals(targetParent)) {
targetParent.getChildren().add(componentToMove);
}
A hack would be to set UIComponent#setInView() to false
so that JSF doesn't remember the dynamic action.
componentToMove.setInView(false);
targetParent.getChildren().add(componentToMove);
componentToMove.setInView(true);
// NOTE: with MyFaces, call setInView() on componentToMove.getParent() instead.
Be however cautious when using this method, see also its javadoc.
However, the most natural way to perform component tree manipulations is a postAddtoViewEvent
listener instead of during encodeXxx()
method.
@ListenerFor(systemEventClass=PostAddToViewEvent.class)
public class YourComponent extends SomeUIComponent {
@Override
public void processEvent(ComponentSystemEvent event) {
if (event instanceof PostAddToViewEvent) {
targetParent.getChildren().add(componentToMove);
}
}