[
https://issues.apache.org/jira/browse/MYFACES-2502?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
]
Leonardo Uribe updated MYFACES-2502:
------------------------------------
Resolution: Fixed
Fix Version/s: 2.0.0-beta-2
Status: Resolved (was: Patch Available)
> 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
> Fix For: 2.0.0-beta-2
>
> Attachments: MYFACES-2502-2.patch, MYFACES-2502-3.patch,
> MYFACES-2502-4.patch
>
>
> 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.