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.

Reply via email to