[jsr-314-open] Ajax rendering of components among compositions?
Andy Schwartz
andy.schwartz at ORACLE.COM
Mon May 25 18:43:16 EDT 2009
Gang -
I spent the last couple of hours testing out various composite
component/f:ajax/id-related use cases, using the ajax-switchlist demo
(switchlistAjaxComponent.xhtml) as my base test case. The
ajax-switchlist component hierarchy looks like this (simplified down to
significant parts):
> <h:body>
> <h:form id="form1">
> <ez:switchlist id="switchlist"/>
> <h:commandButton value="reload" type="submit"/>
> </h:form>
> </h:body>
And <ez:switchlist> composite component contains the following (again,
stripped down to interesting parts):
> <cc:implementation>
>
> <h:selectManyListbox id="list1"/>
>
> <h:commandButton id="move1to2">
> <f:ajax execute="@this #{cc.clientId}:list1"
> render="#{cc.clientId}:list1 #{cc.clientId}:list2"/>
> </h:commandButton>
>
> <h:selectManyListbox id="list2"/>
>
> </cc:implementation>
First thing to note... The execute/render ids are specified in an
overcomplicated manner. As I mentioned in email earlier today, the
f:ajax execute/render ids are spec'ed to adhere to findComponent() id
resolution semantics. Since the <f:ajax> behavior is attached to a
component that is in the same naming container as selectManyListbox
components that we are targeting, we can simplify from this:
> <f:ajax execute="@this #{cc.clientId}:list1"
> render="#{cc.clientId}:list1 #{cc.clientId}:list2"/>
To:
> <f:ajax execute="@this list1" render="list1 list2"/>
Ah, that feels much better. Actually, this isn't just a
simplification... this is correct solution. Unfortunately the original
code only happens to work due to non-spec compliant implementation
behavior. That is, when we specify:
> render="#{cc.clientId}:list1"
This is the same as specifying:
> render="form1:switchlist:list1"
Which should have the same behavior as calling:
> move1to2ButtonComponent.findComponent("form1:switchlist:list1");
This will result in the search expression "form1:switchlist:list1" being
resolved relative to the nearest naming container, which in this case is
the switchlist composite component itself. Since there is no
"form1:switchlist:list1" component within the composite component naming
container, findComponent() would return null, and the <f:ajax> tag
should log a warning.
However, instead of failing/logging a warning, we've got some code in
AjaxBehaviorRenderer.findComponent() that does the following:
> UIComponent resolvedComponent = component.findComponent(expr);
> if (resolvedComponent == null) {
> // not found using a relative search, try an absolute search
> resolvedComponent = component.findComponent(':' + expr);
> }
The bonus absolute search is not part of the spec - ie. is not typical
findComponent() id resolution behavior. This code should be removed and
replaced by a warning. (We should be able to make the warning clearer
by identifying the naming container that provided the scope of the search.
In any case, when switching over to:
> <f:ajax execute="@this list1" render="list1 list2"/>
Everything works as expected. We definitely should update this demo to
use this simpler, more spec-compliant solution.
In order to try to reproduce the problem that David was seeing, I
decided to see whether I could update the "reload" button on the main
page from within the composite component. So, first I gave the reload
button an id:
> <h:commandButton value="reload" type="submit" id="reload"/>
And then I added a new render target inside of our <f:ajax> tag (inside
of the composite component):
> <f:ajax execute="@this list1" render="list1 list2
> :#{cc.parent.clientId}:reload"/>
Note that I used the leading ":" character to indicate that this is an
absolute id.
I was thinking that "#{cc.parent}" would resolve to the composite
component's immediate parent, which in this case happens to be the
"form1" component. As such, I figured that "#{cc.parent.clientId}"
would give me "form1", and ":#{cc.parent.clientId}:reload" would produce
the absolute id ":form1:reload", which should target the outer reload
button (leaving aside the question of whether this is a good thing to do
or not).
However, when I ran the test case, I hit the following error:
> <f:ajax> contains an unknown id '::reload'
Okay, that's weird. So this means that "#{cc.parent.clientId}" is not
resolving to the parent component's client id, but instead to null/empty
string. Huh.
After poking around some more, I found my way to the
com.sun.faces.el.CompositeComponentAttributesELResolver, which provides
some special handling for certain properties on the "cc" object,
including the "parent" property:
> if (COMPOSITE_COMPONENT_PARENT_NAME.equals(propertyName)) {
> UIComponent c = (UIComponent) base;
> context.setPropertyResolved(true);
> UIComponent ccParent =
> UIComponent.getCompositeComponentParent(c);
> return ccParent;
> }
And, of course, UIComponent.getCompositeComponentParent() does not
return the composite component's parent, but instead returns the nearest
ancestor composite component. Which, in this particular demo, is...
null. Yuck. Okay, in retrospect, rather than re-defining the meaning
of "parent" here, we should have introduced a new derived property, eg.
"compositeParent", or something along those lines. (Unfortunately the
current behavior is defined by the spec - section 5.6.2.2, so too late
to do anything about this for 2.0.)
I thought I might be able to work around this by avoiding the reference
to the "cc" implicit object - ie. thought I might be able to get this to
work by using the "component" implicit object instead of the "cc"
object, eg: ":#{component.parent.parent.clientId}:reload". But I ended
up running into the same problem.
So, David - it is possible that you are running into this as well. It's
kind of tough to tell with the auto-generated ids in your content. Any
chance you could add explicit ids to the naming containers in your
sample just to make it easier to understand the client ids that are
being rendered?
On a positive note, I was able to manually pass in the reload button id
to the composite component without any problems. To do this, I added
the following to the composite:interface:
> <cc:attribute name="reloadId"/>
And the following to the composite:implementation:
> <f:ajax execute="@this list1" render="list1 list2
> #{cc.attrs.reloadId}"/>
And then updated the main page to specify the new attribute:
> <ez:switchlist id="switchlist" reloadId=":form1:reload"/>
This worked without any problems.
David - since I think we all agree that this is the preferred approach,
and since there doesn't seem to be any way to get at a composite
component's actual parent (unless I am missing something), this seems
like the right way to go for your J1 demo. If you continue to have
problems with this approach, maybe cc me when you send Ryan your war.
Don't know how much time I'll have to take a look, but I am definitely
curious.
Andy
David Geary wrote On 5/25/2009 1:36 PM ET:
> 2009/5/24 Jim Driscoll <Jim.Driscoll at sun.com
> <mailto:Jim.Driscoll at sun.com>>
>
> On 5/24/09 8:39 PM, David Geary wrote:
>
> And that id is *exactly* what <f:ajax> says it cannot find.
> It's in the
> page, but <f:ajax> cannot find it?!?
>
> My guess is that <f:ajax> is evaluating the id before the page is
> completely constructed, and therefore, it doesn't find it, but
> that's
> just a WAG on my part.
>
> So, if this is not correct:
>
> <f:ajax render="#{cc.parent.clientId}:image"/>
>
> Then how do I access the image in the parent (map) component?
>
> It seems to me that I'm using <f:ajax> correctly, but I'd be
> happy to be
> told otherwise.
>
>
>
> It's late, but yes, that looks correct, and yes, your guess sounds
> correct. Could you file this as an impl bug?
>
>
> Okay. I'll send a WAR to Ryan, and see if we can get this fixed asap.
> I'd like to get this working for my J1 demo.
>
>
>
> Now, we do need to fix that as a bug, but I must argue that your
> use case represents bad practice. Your component is using ajax
> inside a component to modify the using page. That's difficult to
> maintain, and I suspect it will lead to obfuscated code quite
> easily. I'd instead argue that if you are going to have one
> component modify another, you should either group them into one
> composite, or, have the ID of the component you act upon passed in
> as an attribute (say "for="?)
>
>
> Yup, I concur--too much coupling between parent and child components.
> Unfortunately, I can't seem to pass the component ID to the child
> component either. IOW, this doesn't work:
>
> In parent component:
>
> <places:mapZoomControl componentToRender="#{cc.clientId}:image"/>
>
> And in mapZoomControl.xhtml:
>
> <composite:attribute name="componentToRender"/>
> ...
> <f:ajax render="#{cc.attrs.componentToRender}"/>
>
> I suppose it's not surprising that the preceeding doesn't work--it
> looks like a manifestation of the same bug. I'll give Ryan a buzz.
>
> Thanks,
>
>
> david
>
>
>
> Of course, that second usage suggestion opens up another hole in
> the cc api - the lack of ability to do name resolution. Though
> if/when we get method parameters, then cc.findComponent will work,
> I guess. Though I've been saying that for a pretty long time
> now, I should check on when we're getting those method params...
>
> Jim
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://lists.jboss.org/pipermail/jsr-314-open-mirror/attachments/20090525/ca75ea59/attachment.html
More information about the jsr-314-open-mirror
mailing list