Leonardo Uribe created MYFACES-3733:
---------------------------------------

             Summary: Implement vdl.createComponent(...)
                 Key: MYFACES-3733
                 URL: https://issues.apache.org/jira/browse/MYFACES-3733
             Project: MyFaces Core
          Issue Type: Task
          Components: JSR-344
            Reporter: Leonardo Uribe


This is a difficult issue to do in JSF 2.2 . I have spent a long time to solve 
this one, and given the complexity involved and since there is no documentation 
anywhere about how this should work, I'll let the required explanation here.

The idea is allow to include generated vdl fragments into pages 
programatically. This includes normal components, composite components or just 
fragments of markup. The method signature is this:

public UIComponent createComponent(FacesContext context, String taglibURI, 
String tagName, Map<String,Object> attributes)

Some valid examples of this are:

// Normal component
UIComponent component = vdl.createComponent(facesContext, 
    "http://java.sun.com/jsf/html";, 
    "outputText", attributes);

// Composite component
UIComponent component = vdl.createComponent(facesContext, 
    "http://java.sun.com/jsf/composite/testComposite";, 
    "dynComp_1", attributes);

// Dynamic include
Map<String, Object> attributes = new HashMap<String, Object>();
attributes.put("src", "/addSimpleIncludeVDL_1_1.xhtml");
UIComponent component = vdl.createComponent(facesContext, 
    "http://java.sun.com/jsf/facelets";, 
    "include", attributes);

The javadoc does not suggest the dynamic include is valid, but I think users 
expect these kind of stuff work. 

Theoretically it sounds like something easy to do, but unfortunately it is not. 
The reasons why this is so are:

- Facelets algorithm wraps html markup into UILeaf instances, which is a 
special "transient" component. UILeaf instances are never saved or restored 
from the component tree, but in some points of the algorithm (restore view and 
before render response when vdl.buildView() is called) the component tree is 
updated, adding or removing UILeaf instances.

- Facelets has an algorithm that require id generation to be stable, otherwise 
a duplicate id exception may arise. A lot of effort has been done to organize 
this part, and the current solution works very well. But in this case, we need 
to generate unique ids that can be refreshed somehow.

- Facelets algorithm has an special logic to deal with dynamic sections like 
the ones generated by c:if or 
ui:include src="#{...}" . Add facelets sections programatically could make this 
algorithm fail, removing sections that should be.

- Facelets PSS algorithm needs to be taken into account too. The listener that 
is used to register programmatic changes on the tree in 
DefaultFaceletsStateManagementStrategy uses  ComponentSupport.MARK_CREATED to 
identify which component belongs to the component tree and which one was added 
by outside. Add facelets sections programatically could make this algorithm 
fail, because it could assume some sections of the tree does not need to be 
saved fully, even if that's not true.

The issue in the spec is this:

https://java.net/jira/browse/JAVASERVERFACES_SPEC_PUBLIC-611

At start the idea was to export FaceletFactory directly, but I told to the EG 
that it was a bad idea by multiple reasons (That's a Pandora's Box). See:

https://java.net/projects/javaserverfaces-spec-public/lists/jsr344-experts/archive/2012-11/message/91

This previous message is useful too:

https://java.net/projects/javaserverfaces-spec-public/lists/jsr344-experts/archive/2012-06/message/18



After thinking and trying different strategies to overcome this issue, I 
finally found the following solution:

- Use the compiler for generate a custom Facelet "inline" or "on the fly". It 
is not necessary to create an
xml document and then parse it, just generate the Tag class and pass it to the 
compiler to generate an
Abstract Syntax Tree (AST), with the hierarchy of facelet TagHandler instances.

- To solve the issue with UILeaf instances, the best is create a stateful 
ComponentSystemEventListener that on restore view phase it compiles the custom 
Facelet and apply it over the fragment. The ideal and only event to attach the 
listener is PostRestoreStateEvent, but we need to add the code in 
UIComponent.processEvent().

- In the case of a ui:include, if multiple components are returned, all of them 
are grouped into a single
UIPanel. If the code returns one component, it returns that component.

- If the code generates a branch, or in other words, multiple nested 
components, it should attach the 
listener to deal with UILeaf instances, if it just generates one component do 
not do that because it is
not necessary.

- To solve the issue with the ids, just call UIViewRoot.createUniqueId() and 
use the generated value to derive unique facelets ids. The new algorithm that 
generate ids is very flexible and it will support this case. This base key 
should be saved in the state so the same ids are generated for the same 
fragment.

- Support composite components needs special treatment. The idea is support 
something like this:

UIComponent component = vdl.createComponent(facesContext, 
    "http://java.sun.com/jsf/composite/testComposite";, 
    "dynComp_1", attributes);
    
// .... add children / facets to the algorithm

someComponentInTheView.getChildren().add(component);

In this case the "processing" of the composite component content must be done 
only when the component is added to the view. The idea is vdl.createComponent 
only create the root component class, and then use a listener attached to 
PostAddToViewEvent to process the content. We need to modify the algorithm, 
because in this case children/facets are created programatically and not using 
facelets algorithm. The idea is add an extra facelet in the compiler to detect 
when the result is a composite component. The listener attached to 
PostAddToViewEvent must be done in a way that only works on the first addition 
to PostAddToViewEvent.

- If the call to vdl.createComponent() occur when there is an active 
FaceletCompositionContext instance, reuse that instance doing the necessary 
changes in the context, otherwise instantiate a clean context.

- Facelets PSS algorithm will work just fine as long as the returned component 
does not have ComponentSupport.MARK_CREATED set when the view is refreshed, 
saved or restored. It is enough to just use the component attribute map.

- The two base cases to test are:
   * Create components programatically inside a "binding" method.
   * Create components programatically in PreRenderViewEvent or in the Renderer.
  The difference is the "binding" occur when there is a 
FaceletCompositionContext instance active, but in the other cases there is no 
active instance.

Comply with all previous requirements can be difficult, but it is very 
important, otherwise the algorithm will not be stable enough.

--
This message is automatically generated by JIRA.
If you think it was sent incorrectly, please contact your JIRA administrators
For more information on JIRA, see: http://www.atlassian.com/software/jira

Reply via email to