[ https://issues.apache.org/jira/browse/MYFACES-2502?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=12804528#action_12804528 ]
Leonardo Uribe commented on MYFACES-2502: ----------------------------------------- Yes, with the latest patch the problem is no more present. I think is the best we can do for solve this issue (really I spent a lot of time finding it and solving it). Suggestions are welcome. > 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 > Attachments: MYFACES-2502-2.patch, MYFACES-2502-3.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.