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