On Thu, May 28, 2009 at 2:49 PM, Andy Schwartz <andy.schwartz@oracle.com> wrote:
Hey Alex -

Alexandr Smirnov wrote On 5/26/2009 5:41 PM ET:

JSF already has a smart enough code that look components by 'id'. Both
<h:outputLabel> and <h:message > use it to lookup target component that
is defined by the 'for' attribute, RichFaces library uses the same
strategy and it works very well. It would be better to use a same code
for <f:ajax> tag instead of simple local/global search.

 


Good point. I agree that ideally we should be consistent across the various cases where component attributes refer to other components by id.  I took a closer look at the spec/code for the outputLabel "for" attribute.  The spec is unfortunately vague (and possibly incorrect).  The "for" tag doc simply says:

Client identifier of the component for which this element is a label.


Adam logged the following related spec issue on this a couple of years ago:

https://javaserverfaces-spec-public.dev.java.net/issues/show_bug.cgi?id=266

In which Adam says:

The Javadoc for UIMessage and TLDDOC for h:message describes it as one that takes a clientId.  This was
never the intent - this should always have been a relative ID, exactly as in h:outputLabel.  As specified,
h:message is incredibly difficult to use.  IMO, this is an errata, not a spec change, though for backwards
compatibility I'd recommend continuing to look for messages in addition as if "for" were for clientIds.


One thing that is odd is that this implies that outputLabel is correctly specified, though as far as I can tell, both outputLabel and message share the same "for" doc.  (At least the tag doc - perhaps I am missing other doc in the spec itself.)

Looking at the implementations, MyFaces does the following:

- Calls findComponent() using the outputLabel/message component as the base component.
- If a component is found, return the id.
- If the component is found, attempt to construct an id manually (via string manipulation).

So, this is pretty much identical to our f:ajax execute/render id behavior, at least in the case where the id corresponds to a component that can be found in the component tree.  That is:

 <h:outputLabel for="foo"/>

And:

 <f:ajax execute="foo"/>

Will find the same components when running in MyFaces.  (Same goes for the id ":foo").

Mojarra starts off similar...

- Calls findComponent() using the outputLabel/message component as the base component.

So for cases where a relative id is specified in the same naming container, or an absolute id is specified, both <f:ajax> and <h:outputLabel> behave the same in Mojarra.  However, in the event that the findComponent() call does not find the component, Mojarra then goes on to:

- Walk up the ancestor chain.
- For each component in the ancestor chain, call findComponent().
- If we reach the root and still haven't found the matching component, recursively walk over the entire component tree looking for NamingContainers, and call findComponent on each NamingContainer.

Umm... Yikes.  That's a lot of component tree walking!

So, a few issues here:

1. We need a spec clarification

MyFaces and Mojarra are providing different behaviors...  As things currently stand, the following code:

 <f:subview id="bar">
  <h:outputLabel for="foo">
 </f:subview>
 <h:inputText id="foo"/>

Is not going to be portable across JSF implementations.

2. The idea of doing so many findComponent() calls worries me.

A case that we should be able to handle efficiently is having an <f:ajax> tag attached to a component (eg. an <h:commandButton>) that is being stamped out repeatedly (eg. in an <h:data> component).  It is possible that the <f:ajax> execute/render lists may contain multiple execute/render ids each.  In this case, we would end up doing possibly many findComponent() calls each time the component is stamped out - ie. for each row in a table.  Perhaps with very small component trees this may be acceptable, but I can easily see this becoming inefficient as the size of the component tree grows.

Personally I would like to see an approach that optimizes common cases to avoid unnecessary findComponent() calls.  For example, there may be cases where we could:

- Take the id that we are provided.
- Perform some string manipulation to derive the desired id (eg. append the id to the nearest naming container's client id).
- Return the derived id.  (No findComponent() calls).

Or, if we want to provide diagnostic capabilities:

- When project stage is development, after we have derived the id, call findComponent() to verify that the referenced component is present.

We may not be able to derive ids in this fashion for all case, but it seems to me that this should work okay for simple "relative" ids - eg.  execute="foo".

3. We should re-evaluate our component referencing mechanisms for 2.1.

For example, I believe that we should provide a way to explicitly address components in ancestor naming containers, rather than rely on an expensive implicit search mechanism.  Trinidad provides a way to do this - we use the "::" prefix to indicate that we should pop up one level in the naming container hierarchy (similar to ".." for directory paths).

Dan mentioned earlier in the thread:


Frankly, for JSF 2.1, I would like to see us go to an XPath-like syntax (or jQuery) to find components because component IDs in JSF just plain suck.

Not sure what the best solution is, but I agree with Dan that we should take another look at this.

4. Does <f:ajax> actually need to perform id processing/encoding at render time?

<f:ajax>/AjaxBehavior currently send all execute/render ids to the client - which means:

- Lots of findComponent() calls at render time.
- Lots of content bloating.

However, as Alex has suggested in the past, this may be unnecessary.  Instead of sending these ids back and forth to/from the client, we should be able to enhance our Behavior mechanism to give the Behavior that triggered a postback the first crack at processing the request...  So, for example, this would allow AjaxBehavior to enhance the PartialViewContext's execute/render lists early on during the Faces lifecycle (when processing a postback).  No need to send the ids to the client at all.  The cost of doing this is one extra tree visit, which seems like a reasonable trade-off.

Let's revisit this one 2.1.

+1

The goals, simply put, is to design a syntax and behavior that is intuitive, consistent, and efficient. After many years of JSF, I have days where I feel like I am just shooting in the dark trying to hit a component. I think the source of that confusion comes from the different approach taken by Mojarra and MyFaces, as you have identified. Now I know why with Mojarra I often get a "hit" when I wasn't expecting one.

-Dan

--
Dan Allen
Senior Software Engineer, Red Hat | Author of Seam in Action

http://mojavelinux.com
http://mojavelinux.com/seaminaction
http://in.relation.to/Bloggers/Dan

NOTE: While I make a strong effort to keep up with my email on a daily
basis, personal or other work matters can sometimes keep me away
from my email. If you contact me, but don't hear back for more than a week,
it is very likely that I am excessively backlogged or the message was
caught in the spam filters.  Please don't hesitate to resend a message if
you feel that it did not reach my attention.