Andy Schwartz wrote:
Gang -
I have been spending a lot of time thinking about this topic and
reading the doc/looking at the source code. I can now say that I
definitely understand the difference between composite:insertFacet and
composite:renderFacet. :-) However, I am still confused about why we
decided to go with two separate tags and wanted to share my thoughts.
FWIW, I realize that it is like a year late to be bringing this up,
but thought it was important to discuss even if our options are
limited at this point.
Let's start by taking a look at composite:insertFacet:
<composite:implementation>
<h:panelGrid>
<composite:insertFacet name="caption"/>
</h:panelGrid>
</composite:implementation>
In the above case, the component specified for the "caption" facet
ends up in the facet map of the h:panelGrid component. This has
certain implications - eg. the component's parent property will point
to the h:panelGrid instead of the composite component. If we happen
to insert the facet into a NamingContainer, the client id will reflect
this.
Currently we provide no way to insert a composite component's facet
into one of the composite's implementation components as a plain old
(non-facet) child. So, if instead of inserting the "caption" facet as
a facet on an h:panelGrid, I wanted to insert it as a (non-facet)
child of an h:panelGroup, eg:
<composite:implementation>
<h:panelGroup>
<!-- Insert the facet as a direct child here -->
</h:panelGroup>
</composite:implementation>
I cannot do that. Or, at least, I cannot do that with the same
semantics as <composite:insertFacet>, where the component specified
via the facet ends up as a child of the containing component (the
h:panelGroup).
I can, however, use composite:renderFacet:
<composite:implementation>
<h:panelGroup>
<composite:renderFacet name="caption"/>
</h:panelGroup>
</composite:implementation>
This will cause the facet to be rendered as a child of the
h:panelGroup, but without being a child of the h:panelGroup.
This raises a number of questions. Why do we re-parent the facet
component when we want the component to be used by the parent
component as a facet,
There is no way to add a facet without it being re-parented.
Perhaps we
should have changed the behavior to allow non-child components to be
facets of a component.
but not when we want the component to be used by the parent as a
direct child?
As you pointed out, there is no way to add a component from the page
to
a component in the EZComp as a child (only as a facet). If it were
possible, under the current JSF code, it too would be re-parented.
Is there a reason why we need to treat the direct child case
differently (not re-parent) than the facet case (re-parent)? Is there
some benefit to having subtly different behavior between these two cases?
Yes, the
not-reparent case is not a child. It is an omission and were
it to be added,it too would re-parent (unless we changed the rules).
The reason the 2 tags are treated differently is this: 1 modifies the
component tree; the other occurs during rendering. Two very different
phases in JSF.
If we were to explore not re-parenting, we'd probably need to create a
ProxyComponent which could be added to the child and delegate everything
to the real component which would remain on the parent. That might work
nicely.
My feeling is that the good old Facelets ui:insert tag got this
right. Facelets uses a single tag to handle insertion of ui:define'd
components into the template, regardless of whether the components are
being inserted as facets or directly children. So, for example,
assuming I've got a template that allows insertion of a "foo"
component, I can do either this:
<h:panelGrid>
<f:facet name="caption">
<!-- Insert the "foo" component into the "caption" facet
-->
<ui:insert name="foo"/>
</f:facet>
</h:panelGrid>
Or this:
<h:panelGroup>
<!-- Insert the "foo" component as a non-facet child -->
<ui:insert name="foo"/>
</h:panelGroup>
While the first case inserts the component as a facet and the second
as a direct child, both have the same semantics - ie. in both cases
the component that is being inserted ends up as a child of the parent
component that the child is being inserted into.
Which is wrong. The parent
ideally should be the *composite
component*. Keep in mind the template is a black box that the page
author should not know anything about. So if I do:
<my:ezComponent id="foo">
<f:facet name="myfacet">
<h:outputText id="usercomp" value="here" />
</f:facet>
</my:ezComponent>
So looking at this page excerpt, you'd say the parent of "usercomp" is
"foo". And if foo is a naming container, the clientId would be
something like: "...:foo:usercomp".
However, if the implementation of the ezcomp component (that the page
author has no control over and no know knowledge of) is implemented like
this:
...
<some:namingcontainer id="hiddenContainer">
<ui:insert facet="myfacet" />
</some:namingcontainer>
...
Then we have problems:
1) The parent is *not* "foo" as the page author expected (and as would
be the case with ALL Java-based components).
2) The clienId is not what we expected:
"...:foo:...:hiddenContainer:myfacet"
3) Changes that a component author makes to the component will break any
code the page author writes that depends on the location or clientId of
the facet component.
Now... was facelets wrong w/ ui:insert? Absolutely not... In facelets
they were not making components, they were making pages. The page
author had visibility and full control of the UIComponent tree structure
that was being created. VERY different than the component scenario.
Which is likely why facelets was never able to create real components.
Actually, Facelets provides an API that is specifically designed to
handle this type of insertion: TemplateClient. The TemplateClient API
allows for clean/efficient insertion of included content into
templates. I believe that ui:define/ui:insert use this to insert
included content directly into the target location, avoiding the need
to re-locate the included components from one parent to another. This
has benefits when re-applying tags over a restored component tree,
since there is no need to repeatedly re-create (or re-locate) these
components.
Not sure I follow you. But I "think" you're describing
meta-data that
represents the "files" which define the UIComponent tree (aka, VAST).
Which is something I have been advocating for and can greatly help.
However, once components are instantiated as UIComponents, they do
either have to be relocated, copied, or proxied (never seen this done in
practice)... so I think there's more to it than what you suggest.
After thinking this through, one other question that I am left with
is: Is there a reason why we did not follow the ui:insert approach?
See above.
Yes, I realize that this is a moot point now, and no reason to dwell
on this, but I am still curious as to whether there were
limitations/problems with the ui:insert mechanism that prevented us
from following this precedent.
If we had the ability to revisit the design now, my preferred solution
would be to:
1. Deprecate/remove composite:renderFacet.
2. Re-spec composite:insertFacet to match the behavior of ui:insert.
I think we
could do this if we can also ensure that components are NOT
re-parented. Then we could provide the correct behavior AND provide a
nice syntax for component authors. However, I don't envy the person
(Ryan) that has to implement this. :) There are a lot of tags and code
that would have to have special-case stuff to make this happen.
Ken
I prefer this approach because I feel that we should share a single
insertion behavior across all of these use cases. The fact that we
have 3 subtly different mechanisms is, well... bad. I also think that
the ui:insert approach has already been proven, which is in part why I
favor that approach.
Of course, I don't imagine that we have the ability to make this
change given that the JSF 2.0 spec has been final for some time now,
and this would be a significant change.
I suppose my second choice would be to deprecate both
composite:renderFacet/composite:insertFacet in JSF 2.1 and replace
both of these with a new composite:insert tag that matches ui:insert
behavior.
Getting back to our original problem of not having control over the
"target" facet name:
> If the spec does not yet provide a solution for this, I think that we
> could/should solve this in one of two ways:
>
> 1. Add an attribute to composite:insertFacet that allows a target
> facet name to be specified:
>
> <h:panelGrid>
> <composite:insertFacet name="backupCaption"
targetName="caption"/>
> </h:panelGrid>
>
> 2. Specify that the target facet name can be picked up from a
> wrapping <f:facet> tag:
>
> <h:panelGrid>
> <f:facet name="caption">
> <composite:insertFacet name="backupCaption"/>
> </f:facet>
> </h:panelGrid>
I still prefer #1 as this is closer to ui:insert behavior, but given
that I have more fundamental reservations about
composite:insertFacet/composite:renderFacet, I don't feel especially
strongly about forcing #1 if people prefer #2.
Andy
Ken Paulsen wrote:
>
> When / where should this discussion take place? Do we want to have a
> call for this?
>
> In addition to this issue, Alexander raised the issue that
> f:insertFacet and f:renderFacet are confusing. Not sure if there's
> anything we can do resolve this at this point, but minimally, it
> would be worth ensuring the EG members understand the difference
> (which IMO, is huge). In hindsight, f:insertFacet probably should
> have been f:attachFacet, and of course had an optional "target"
> attribute for Andy's case.
>
> Ken
>
> Andy Schwartz wrote:
>> Thanks Ed -
>>
>> Ed Burns wrote:
>>>
>>> I happen to prefer #1, but everyone else favors #2, we'll go with #2.
>>>
>>
>> Seems like some people prefer #2 as well, so perhaps this needs more
>> discussion.
>>
>>> Andy, can you please file a spec issue and share the number with the
>>> group? Once you have it, I'll add an entry in the changelog wiki.
>>>
>>>
>>
>> Sorry for taking so long to follow up on this. I have logged the
>> following spec issue:
>>
>>
https://javaserverfaces-spec-public.dev.java.net/issues/show_bug.cgi?id=631
>>
>>
>> Andy
>>
>>