>>>>> On Tue, 18 Mar 2014 13:39:59 -0500, Leonardo Uribe <lu4242_at_gmail.com> said:
LU> Hi
LU> In JSF 2.2, it was added a new method called
LU> VDL.createComponent(...) . The intention was "normalize"
LU> programatic additions of composite components to a view or component
LU> tree. But over the time, I have notice some suggestions that
LU> deserves the attention of the EG. Additionally, I have found some
LU> cases that does not work as it should be with the reference
LU> implementation (Mojarra 2.2.6 and earlier versions), and that raise
LU> some concerns about the portability of this feature between
LU> implementations of the same JSF spec.
LU> This mail continues the following discussion on this list:
LU> [jsr344-experts] implications of use FaceletFactory
LU> (JAVASERVERFACES_SPEC_PUBLIC-611)
LU>
https://java.net/projects/javaserverfaces-spec-public/lists/jsr344-experts/archive/2012-11/message/91
LU> The related issue in MyFaces is here:
LU>
https://issues.apache.org/jira/browse/MYFACES-3733
LU> There are three possible options to use VDL.createComponent(...):
LU> // Normal component
LU> UIComponent component = vdl.createComponent(facesContext,
LU> "
http://java.sun.com/jsf/html",
LU> "outputText", attributes);
LU> // Composite component
LU> UIComponent component = vdl.createComponent(facesContext,
LU> "
http://java.sun.com/jsf/composite/testComposite",
LU> "dynComp_1", attributes);
LU> // Dynamic <ui:include >
LU> Map<String, Object> attributes = new HashMap<String, Object>();
LU> attributes.put("src", "/addSimpleIncludeVDL_1_1.xhtml");
LU> UIComponent component = vdl.createComponent(facesContext,
LU> "
http://java.sun.com/jsf/facelets",
LU> "include", attributes);
LU> The first two are pretty obvious that should work, but the third one
LU> is tricky. Should <ui:include ...> tag be considered a component?
LU> From a technical perspective, ui:include is a facelet template tag,
LU> so there is no underlying component behind it. But an user could
LU> easily think the opposite. He/She could say "... an ui:include is
LU> just a component that should put the content of the fragment point
LU> by "src" attribute ...".
LU> The syntax is just straightforward. The method returns a component
LU> that you can attach to the component tree in the place you want. The
LU> logic behind the algorithm should hide details like if multiple
LU> components are returned from the ui:include call, just wrap them
LU> into a single panel and so on. The invocation should be legal from
LU> spec perspective, even if it was not devised in the moment the
LU> feature was conceived, so why don't support it?.
LU> Now the problem.
LU> Some time ago, I did some black box tests over Mojarra to see what
LU> happen in these cases, but I found out that the feature is not
LU> working as it should. Let's take a look at this example:
LU> @FacesComponent(value = "com.myapp.UIAddSimpleCCVDL")
LU> public class UIAddSimpleCCVDL extends UIComponentBase implements
LU> SystemEventListener
LU> {
LU> public UIAddSimpleCCVDL()
LU> {
LU> setRendererType(null);
LU> FacesContext context = FacesContext.getCurrentInstance();
LU> UIViewRoot root = context.getViewRoot();
LU> root.subscribeToViewEvent(PreRenderViewEvent.class, this);
LU> }
LU> /* ... some code not related to the test... */
LU> public void processEvent(SystemEvent event) throws AbortProcessingException
LU> {
LU> FacesContext facesContext = FacesContext.getCurrentInstance();
LU> if (!facesContext.isPostback())
LU> {
LU> ViewDeclarationLanguage vdl = facesContext.getApplication().
LU> getViewHandler().getViewDeclarationLanguage(
LU> facesContext, facesContext.getViewRoot().getViewId());
LU> Map<String, Object> attributes = new HashMap<String, Object>();
LU> UIComponent component = vdl.createComponent(facesContext,
LU> "
http://java.sun.com/jsf/composite/testComposite",
LU> "dynComp_1", attributes);
LU> getChildren().add(component);
LU> }
LU> }
LU> }
LU> /resources/testComposite/dynComp_1.xhtml
LU> <cc:interface>
LU> </cc:interface>
LU> <cc:implementation>
LU> Dynamically added markup
LU> <h:outputText value="Dynamically added child"/>
LU> </cc:implementation>
LU> /addSimpleCCVDL.xhtml (the page that declares it)
LU> <test:addSimpleCCVDL id="component"/>
LU> /WEB-INF/testcomposite.taglib.xml
LU> <facelet-taglib>
LU> <namespace>
http://testcomponent</namespace>
LU> <tag>
LU> <tag-name>addSimpleCCVDL</tag-name>
LU> <component>
LU> <component-type>com.myapp.UIAddSimpleCCVDL</component-type>
LU> </component>
LU> </tag>
LU> <!-- ... -->
LU> </facelet-taglib>
LU> The example is just a normal component that use a listener
LU> subscribed to PreRenderViewEvent and add the composite component to
LU> the component tree. This is a typical case and things should work.
LU> But take a look at the implementation. There is a real JSF component
LU> (h:outputText) and some HTML markup. Facelets use a wrapper
LU> component called UILeaf that wraps the HTML markup but please note
LU> this component is a "transient" component, which means this
LU> component does not have any state to be saved, so it is not saved in
LU> the state. When the component tree is restored, the HTML markup
LU> should be "refreshed" somehow to reconstruct the component tree the
LU> way it was before render response.
LU> The problem in Mojarra is, the code that should do the refresh is
LU> not there. Just put a button and do a postback over the page and
LU> you'll see the HTML markup just vanish, but the h:outputText
LU> component do not, because it is a component that was included with
LU> the state.
LU> I will not bore you with the implementation details required to make
LU> it run. Instead, I will just say that a solution requires to use
LU> something (like a listener for example) to refresh the tree when it
LU> is restored.
LU> Now let's suppose that some fix was done and now let's try to modify
LU> the example just a bit:
LU> /resources/testComposite/dynComp_1.xhtml
LU> <cc:interface>
LU> <cc:facet name="header" required="true"/>
LU> </cc:interface>
LU> <cc:implementation>
LU> <h:panelGrid>
LU> <cc:insertFacet name="header"/>
LU> </h:panelGrid>
LU> Dynamically added markup
LU> <h:outputText value="Dynamically added child"/>
LU> </cc:implementation>
LU> public void processEvent(SystemEvent event) throws AbortProcessingException
LU> {
LU> FacesContext facesContext = FacesContext.getCurrentInstance();
LU> if (!facesContext.isPostback())
LU> {
LU> ViewDeclarationLanguage vdl = facesContext.getApplication().
LU> getViewHandler().getViewDeclarationLanguage(
LU> facesContext, facesContext.getViewRoot().getViewId());
LU> Map<String, Object> attributes = new HashMap<String, Object>();
LU> UIComponent component = vdl.createComponent(facesContext,
LU> "
http://java.sun.com/jsf/composite/testComposite",
LU> "dynComp_1", attributes);
LU> UIOutput text = (UIOutput) facesContext.getApplication().
LU> createComponent(UIOutput.COMPONENT_TYPE);
LU> text.setValue("Dynamically added header");
LU> component.getFacets().put("header", text);
LU> getChildren().add(component);
LU> }
LU> }
LU> In this example, the facet is added to the composite component, and
LU> then the component is added to the component tree. In Mojarra, the
LU> example does not work, because in some point it tries to check if
LU> the facet is there but it is not. Here is the stack trace.
LU> javax.faces.view.facelets.TagException:
LU> //G:/workspace/myfaces/test22/myfaces-helloworld22-test-comp-prog/target/tmp/mojarra6162439279887177630.tmp
LU> @2,102 <j:dynComp_1> The following facets(s) are required, but no
LU> facets have been supplied for them: header.
LU> at com.sun.faces.facelets.tag.composite.InterfaceHandler.validateComponent(InterfaceHandler.java:232)
LU> at com.sun.faces.facelets.tag.composite.InterfaceHandler.apply(InterfaceHandler.java:125)
LU> at javax.faces.view.facelets.CompositeFaceletHandler.apply(CompositeFaceletHandler.java:95)
LU> at com.sun.faces.facelets.compiler.NamespaceHandler.apply(NamespaceHandler.java:93)
LU> at com.sun.faces.facelets.compiler.EncodingHandler.apply(EncodingHandler.java:87)
LU> at com.sun.faces.facelets.impl.DefaultFacelet.include(DefaultFacelet.java:312)
LU> at com.sun.faces.facelets.impl.DefaultFacelet.include(DefaultFacelet.java:371)
LU> at com.sun.faces.facelets.impl.DefaultFaceletContext.includeFacelet(DefaultFaceletContext.java:326)
LU> at com.sun.faces.facelets.tag.jsf.CompositeComponentTagHandler.applyCompositeComponent(CompositeComponentTagHandler.java:385)
LU> at com.sun.faces.facelets.tag.jsf.CompositeComponentTagHandler.applyNextHandler(CompositeComponentTagHandler.java:186)
LU> at com.sun.faces.facelets.tag.jsf.ComponentTagHandlerDelegateImpl.apply(ComponentTagHandlerDelegateImpl.java:203)
LU> at javax.faces.view.facelets.DelegatingMetaTagHandler.apply(DelegatingMetaTagHandler.java:120)
LU> at javax.faces.view.facelets.CompositeFaceletHandler.apply(CompositeFaceletHandler.java:95)
LU> at com.sun.faces.facelets.compiler.NamespaceHandler.apply(NamespaceHandler.java:93)
LU> at com.sun.faces.facelets.compiler.EncodingHandler.apply(EncodingHandler.java:87)
LU> at com.sun.faces.facelets.impl.DefaultFacelet.apply(DefaultFacelet.java:161)
LU> at com.sun.faces.facelets.impl.DefaultFaceletFactory._createComponent(DefaultFaceletFactory.java:403)
LU> at com.sun.faces.application.view.FaceletViewHandlingStrategy.createComponent(FaceletViewHandlingStrategy.java:841)
LU> at org.apache.myfaces.examples.helloworld22.UIAddSimpleCCVDL.processEvent(UIAddSimpleCCVDL.java:81)
LU> The problem is the call to VDL.createComponent cause the composite
LU> component to be applied, but it is too early for that, because it is
LU> necessary to let the user time to put the facet and then trigger the
LU> composite component code when the component is added to the
LU> component tree. But if you remove the required="true" from the
LU> facet, the code "apparently" works. Anyway the stack trace looks
LU> like something that is not working as it should.
LU> Now let's try this example (it uses the same composite component):
LU> /componentBindingVDL_1.xhtml
LU> <h:panelGroup id="panel" binding="#{componentBindingVDLBean1.panel}"/>
LU> <h:form id="mainForm">
LU> <h:commandButton id="postback" value="POSTBACK"/>
LU> </h:form>
LU> @ManagedBean(name="componentBindingVDLBean1")
LU> @RequestScoped
LU> public class ComponentBindingVDLBean1
LU> {
LU> private UIPanel panel;
LU> public UIPanel getPanel()
LU> {
LU> if (panel == null)
LU> {
LU> panel = new HtmlPanelGroup();
LU> FacesContext facesContext = FacesContext.getCurrentInstance();
LU> ViewDeclarationLanguage vdl = facesContext.getApplication().
LU> getViewHandler().getViewDeclarationLanguage(
LU> facesContext, facesContext.getViewRoot().getViewId());
LU> Map<String, Object> attributes = new HashMap<String, Object>();
LU> UIComponent cc = vdl.createComponent(facesContext,
LU> "
http://java.sun.com/jsf/composite/testComposite",
LU> "dynComp_1", attributes);
LU> UIOutput text = (UIOutput) facesContext.getApplication().
LU> createComponent(UIOutput.COMPONENT_TYPE);
LU> text.setValue("Dynamically added header");
LU> cc.getFacets().put("header", text);
LU> panel.getChildren().add(cc);
LU> }
LU> return panel;
LU> }
LU> This is like the previous one but please note the component is
LU> injected into the tree through the "binding" attribute. This one
LU> also does not work with Mojarra, but in this case it is much worst,
LU> because not only the html markup but the whole composite component
LU> disappear. This case is quite special because the h:panelGroup is
LU> managed by facelets but the content is created by the user, but note
LU> the composite component needs to be refreshed by facelets
LU> again. Think about what will happen with a <c:if ...> tag inside the
LU> composite component.
LU> These problems are only the tip of the Iceberg. It is possible to
LU> find more problems, but at the end, the important thing is to get to
LU> an agreement about which cases should work and which ones should
LU> not. I have to say that all previous cases works with MyFaces 2.2,
LU> and according to the spec, all these cases should work like
LU> magic. The javadoc of VDL.createComponent(...) says this:
LU> "... Create a component given a ViewDeclarationLanguage specific tag
LU> library URI and tag name. The runtime must support this method
LU> operating for the Facelets VDL. ..."
LU> In my personal opinion, and based on the work done with MyFaces, at
LU> least these 3 minimal cases should be valid for JSF 2.2. Enable
LU> ui:include is also wanted, since many users are looking for a
LU> solution to avoid ugly and error prone hacks related to
LU> FaceletFactory.
LU> My suggestion is if the code in Mojarra requires some refactoring,
LU> please be sure to include the previous cases on the fix, so both
LU> implementations point in the same direction.
LU> regards,
LU> Leonardo Uribe
--
| edward.burns_at_oracle.com | office: +1 407 458 0017
| 1 Work Days Til JavaLand 2014
| 31 Work Days til JAX 2014