Component state is lost for composite component childs of facets relocated by
composite:insertChildren or composite:insertFacet
-------------------------------------------------------------------------------------------------------------------------------
Key: MYFACES-2502
URL: https://issues.apache.org/jira/browse/MYFACES-2502
Project: MyFaces Core
Issue Type: Task
Components: JSR-314
Affects Versions: 2.0.0-beta
Reporter: Leonardo Uribe
Assignee: Leonardo Uribe
When partial state saving is not used, component state is lost for composite
component childs of facets relocated by composite:insertChildren or
composite:insertFacet
To understand why this is happening, it is necessary to understand how facelets
works in context and how composite:insertChildren and composite:insertFacet
works, so I'll do a resume for it.
In jsf 1.2, a facelet is applied in two cases:
1. When a page is request for first time, the whole component tree is build.
2. On a postback to update transient components like facelets UIInstruction.
To see it in context, suppose a simple app that ask for a name an it print it
on another component on the same page:
<h:form>
Name: <h:inputText value="{bean.name}/>
Previous Name: <h:outputtext value="{bean.name}/>
<h:commandButton value="submit" action="submitToThisSamePage"/>
</h:form>
That is what happened when facelets + jsf 1.2 is used:
First Request:
- There is a call to FaceletViewHandler.buildView from
FaceletViewHandler.renderView that cause the UIViewRoot instance to be filled
for first time calling to f.apply().
- The view is rendered.
- Save the state for all non transient components found on the view. This
include save the tree structure too, so it can be reconstructed later.
Postback (The user send his name and do a submit):
- Restore the tree structure and component state for all saved components.
- All lifecycle phases continues until before renderView
- On FaceletViewHandler.renderView there is a call to buildView, and this one
causes all transient components like facelets UIInstruction to be added to the
tree. ComponentHandler first try to detect if the component is on the view
before create it, and if that is true do not create it, instead it takes this
instance and continue apply the taghandles and it remove and add it from tree,
to give the chance to other transient components to be created and added
correctly.
- The view is rendered.
- Save the state for all non transient components found on the view. This
include save the tree structure too, so it can be reconstructed later.
In jsf 2.0 it happens something similar. ViewDeclarationLanguage and
TagHandlerDelegate abstract classes were created, so some code was "relocated".
To be clear, the algorithm in jsf 2.0 without partial state saving is this:
First Request:
- There is a call to ViewDeclarationLanguage.buildView from
RenderResponseExecutor.execute that cause the UIViewRoot instance to be filled
for first time calling to f.apply().
- The view is rendered.
- Save the state for all non transient components found on the view. This
include save the tree structure too, so it can be reconstructed later.
Postback (The user send his name and do a submit):
- Restore the tree structure and component state for all saved components.
- All lifecycle phases continues until before renderView
- On RenderResponseExecutor.execute there is a call to buildView, and this one
causes all transient components like facelets UIInstructions to be added to the
tree. ComponentHandler first try to detect if the component is on the view
before create it, and if that is true do not create it, instead it takes this
instance and continue apply the taghandles and it remove and add it from tree,
to give the chance to other transient components to be created and added
correctly.
- The view is rendered.
- Save the state for all non transient components found on the view. This
include save the tree structure too, so it can be reconstructed later.
The algorithm in jsf 2.0 with partial state saving is different:
First Request:
- There is a call to ViewDeclarationLanguage.buildView from
RenderResponseExecutor.execute that cause the UIViewRoot instance to be filled
for first time calling to f.apply().
- The view is rendered.
- Save the state for all non transient components found on the view that has
delta or was added after build. This does not include the structure, because
all state is just saved on a big Map<String,Object> where the keys are the
clientId for each component.
Postback (The user send his name and do a submit):
- StateManagementStrategy.restoreView calls ViewDeclarationLanguage.buildView
passing an empty UIViewRoot to be filled for first time calling to f.apply().
- Restore the component state for all saved components. Note the tree
structure now is already provided.
- All lifecycle phases continues until before renderView
- On RenderResponseExecutor.execute there is a call to buildView, but this
time, since the view is already filled, nothing happens. It just return.
- The view is rendered.
- Save the state for all non transient components found on the view that has
delta or was added after build. This does not include the structure, because
all state is just saved on a big Map<String,Object> where the keys are the
clientId for each component.
Really from a point of view, partial state saving integrates a param called
FaceletViewHandler.buildBeforeRestore with other ideas found on Trinidad.
Now suppose this scenario:
Component impl:
<composite:interface>
</composite:interface>
<composite:implementation>
<h:outputText value=" Hello " />
<p>
<composite:insertChildren/>
</p>
<h:outputText value=" Bye " />
</composite:implementation>
Snippet on page:
<testComposite:simpleInsertChildren id="sic">
Mr.
<h:inputText id="name" value="John " />
Smith
</testComposite:simpleInsertChildren>
composite:insertChildren and composite:insertFacet uses a listener attached to
PostAddToViewEvent to relocate the components. So, when the listener is called
the body of testComposite:simpleInsertChildren looks like this:
<h:outputText value=" Hello " />
<p>
Mr.
<h:inputText id="name" value="John " />
Smith
</p>
<h:outputText value=" Bye " />
The reason why we do that on a listener is described on MYFACES-2317. In few
words, we need the relocation occur from root to branches to allow nesting on
composite components.
Now the big question: Why the state is lost for child or facet components using
composite:insertChildren or composite:insertFacet?
When the component tree is updated (call to buildView on render response phase
and postback), since the components inside the composite component were moved
to some location "inside" the composite component, the current algorithm is
unable to detect this condition, so the components are created again. The
reason why there is no duplicate components in the view is facelets has an
algoritm to mark and delete component instances that are not traversed (this is
the real magic behind c:if tag). Then facelets detach and attach all components
while traversing, triggering PostAddToViewEvent and our relocation listener is
activated again (before do this the components on the location were deleted).
Why this does not happens when partial state saving is used? In this case there
is no "update" call. buildView just return because the view is already filled.
In this case, c:if will not work as in jsf 1.2 if it depends on a value that
changes on invoke application phase (MYFACES-2483).
In this case we have two options:
1. Do some algorithm that helps facelets to "jump" to the correct location
where the component was relocated. Then on the listener, relocate the
components created (UIInstructions instances) based on the component order
saved on first time.
Advantages: Keep things simple and only has to deal with the components that
were moved in relocation.
Disadvantages : c:if will not work correctly inside a composite component
that uses composite:insertChildren or composite:insertFacet.
2. Let facelets create the components and then on the listener "unify the
tree", adding the new components and remove the ones that shouldn't be there.
Advantages: c:if will work correctly on all situations.
Disadvantages: The algorithm necessary has to traverse the tree for all
children and facets looking for added components.
I tried both solutions and finally I think solution 1 is the option to do.
Obviously, things like this:
<testComposite:simpleInsertChildren id="sic">
Mr.
<c:if test="#{helloWorldIfBean.toogle}">
<p>Pepito</p>
<h:outputText value="Perez"/>
</c:if>
Smith
</testComposite:simpleInsertChildren>
will not work correctly, but if you put this one like this:
<testComposite:simpleInsertChildren id="sic">
Mr.
<h:panelGroup>
<c:if test="#{helloWorldIfBean.toogle}">
<p>Pepito</p>
<h:outputText value="Perez"/>
</c:if>
</h:panelGroup>
Smith
</testComposite:simpleInsertChildren>
It will.
If no objections I'll commit the solution 1 soon.
--
This message is automatically generated by JIRA.
-
You can reply to this email to add a comment to the issue online.