tags:

views:

455

answers:

1

Actually in my JSF application I have this code to compute a component clientId from its id:

public static String getComponentClientId(String id) {
  try {
    FacesContext fctx = FacesContext.getCurrentInstance();
    UIComponent found = getComponentWithId(fctx.getViewRoot(), id);
    if (found!=null)
      return found.getClientId(fctx);   
    else
      return null;
  } catch (Exception e) {
    return null;
  }  
}
public static UIComponent getComponentWithId(UIComponent parent, String id) {
  for (Iterator<UIComponent> chs = parent.getFacetsAndChildren(); chs.hasNext();) {
    UIComponent ch = chs.next();
    if (ch.getId().equalsIgnoreCase(id))
     return ch;  
    else {
     UIComponent found = getComponentWithId(ch, id);
     if (found!=null)
       return found;
    }
  }
  return null;
}

The method works, but it navigate through the view component tree, so it can be very inefficient, specially in presence of highly populated pages. There is a clever way or an API that I don't know to make the job faster/easier?

+1  A: 

Not that I know of.

It is difficult to cache this information, because:

  1. The relationship between an instance of UIComponent and its clientIds can be 1:N. This is required for components like UIData that manage the state of their children. The return value from getClientId can be context-sensitive.
  2. The lifetime of a UIComponent instance probably won't exceed the request. Some implementations of StateManager can be configured to save the view state in the session, in which case the lifetime of the objects might be longer, but making your code rely on this makes it fragile and it would be easy to introduce memory leaks.
  3. Views can be dynamic. Admittedly, this is an edge case, but it is possible to add, remove and move components programmatically.

I would prove that this is a problem before trying another approach.


You don't say what you need the clientIds for, but I'm guessing it is for some form of JavaScript support. Consider writing a custom component and emitting your markup via its renderer. This could be used with a for attribute, similar to the label control. Finding a neighbour in the same NamingContainer is relatively easy. You might use code like this in your renderer implementation:

// untested code!
String clientId = mycomponent.getClientId(context);
// get id of target control
String _for = mycomponent.getFor();
int n = clientId.lastIndexOf(NamingContainer.SEPARATOR_CHAR);
String targetClientId = clientId.substring(0, n)
  + NamingContainer.SEPARATOR_CHAR + _for;
ResponseWriter writer = context.getResponseWriter();
// write markup

Components usually need to share a naming container to make use of each other's clientId anyway, so this isn't a big constraint. If you can make the component a child, it is easier to find a parent.

Of course, this approach has problems too. It makes your UI tree even bigger, with a knock-on effect on lifecycle processing and state-management. Whether the trade-off is worth it would require testing.


EDIT:

I thought about this over the weekend. Since 50% of JSF queries seem to be about how to work with IDs, I wrote a post so I can just refer people to it - JSF: working with component IDs. In includes a mechanism for caching the IDs (sort of!) with some sample code.

McDowell
Thank you, McDowell. I need clientIDs only to emitting messages for the correct component. I don't want to bind every component to my backing bean: is this a correct approach?
Pier Luigi
Have a look at taglib functions (see blog link). If you can improve on my solutions, I'd be interested to hear about it.
McDowell