19

Does JSF 2.0 have a built-in method for finding the client ID of another component? There are about a thousand client ID-related questions on SO and there are a lot of hackish methods to do this, but I'm wondering if JSF 2.0 brought a simpler method that I just don't know.

#{component.clientId} evaluates to a given component's own client ID, but I want to reference another component's ID.

This blog post mentions component.clientId, and it also says #{someComponent.clientId} works, but from what I can tell it does not. I believe he wrote that before any reference implementations of JSF 2.0 were out, so he was just going by the JSR and maybe that functionality changed. I'm not sure.

I know PrimeFaces and RichFaces both have their own functions to return a client ID, but I'm just wondering if there's a built-in JSF 2.0 method for this. Here are some examples:

This works to return the outputText's ID.

`<h:outputText value="My client ID : #{component.clientId}" />`

According to the blog post above, this should work, but it does not. I just get no output.

`<h:button id="sampleButton" value="Sample" />`

`<h:outputText value="sampleButton's client ID : #{sampleButton.clientId}" />`

This works in PrimeFaces:

`<h:outputText value="PrimeFaces : sampleButton's client ID : #{p:component('sampleButton')}" />` 

Works in RichFaces:

`<h:outputText value="RichFaces : sampleButton's client ID : #{rich:clientId('sampleButton')}" />`

Also, if at all possible I'm looking for solutions that won't break if I change the javax.faces.SEPARATOR_CHAR value or if I add/remove containers outside the referenced components. I've spent plenty of time tracking down issues caused by hard-coded ID paths.

cutchin
  • 1,177
  • 1
  • 8
  • 12

3 Answers3

35

You need to assign the component a variable name in the view scope by binding attribute.

<h:button id="sampleButton" binding="#{sampleButton}" value="Sample" />
<h:outputText value="sampleButton's client ID : #{sampleButton.clientId}" />
BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
  • 3
    That's great. I assumed the `binding` attribute was exclusively for exposing components to backing beans - I didn't realize you could also publish a component on the scope. Thanks much. – cutchin Aug 26 '12 at 17:09
  • I also did not know you could do that! I hadn't seen any references in docs to that use of binding/EL. Is this new in JSF 2.x? (i.e. is it also available in 1.2?) – Steve Atkinson Aug 27 '12 at 17:05
  • It has always been in JSF/EL. It's not specific to JSF2. See also this related question/answer: http://stackoverflow.com/questions/8168302/jsf-component-binding-without-bean-property – BalusC Aug 27 '12 at 19:25
  • is there no problems about collision or bad / wrong binding? i'd love to have the clientId, but if I risk on getting wrong values on components... that'd be a killer – Toskan Aug 28 '12 at 11:50
  • @Toskan: I'm not sure if I understand your concern. – BalusC Aug 28 '12 at 11:51
  • @BalusC that's mainly the reason because I don't understand how the binding can influence something. I just got concerned with this reference http://illegalargumentexception.blogspot.ch/2009/10/jsf-working-with-component-identifiers.html#recipeabc – Toskan Aug 28 '12 at 11:53
  • @Toskan: Ah this way. If you have a `@ManagedBean(name="foo")` and you use somewhere `binding="#{foo}"` then you've a collision. You need to make absolutely sure that the variable name of `binding` is unique in the EL scope of the current view. See also the last linked related answer for an easy way to avoid those possible collisions with help of a `HashMap`. – BalusC Aug 28 '12 at 11:54
  • I think it is important that when attaching a binding you can easily destroy the application by i.e. attaching to the same hashmap entry. E.g.: you have two forms, in both forms you have 1 input with id=abc (this is legal), you generically attach to a hashmap entry created by the id e.g. #{components.myInputId} which is twice abc which in fact will result in no input element being displayed at all. I guess safest bet is using some randomly generated Id? it still seems a bit meh though – Toskan Aug 28 '12 at 17:59
  • It does the job, but it can also create a problem if you apply (by mistake or by nestsing) the same binding name in two tags inside your page. That will cause both tags to share the same object which can be a pain to identify. [This link has more details](http://drewdev.blogspot.co.uk/2009/01/jsf-component-binding-stinks.html) – Ioannis Deligiannis Jul 07 '13 at 11:54
  • @john: Ignorance is indeed a pain. That ridiculous link only confirms it more. It's even recommending to never use binding while it has certainly its use when understood. See also http://stackoverflow.com/questions/12506679/what-is-component-binding-in-jsf-when-it-is-preferred-to-be-used – BalusC Jul 07 '13 at 11:59
  • I agree, but I thought that since I (among others) have felt the pain (by my ignorance) it is good to share ;). Also, binding could certainly benefit from better documentation (like your link) – Ioannis Deligiannis Jul 07 '13 at 12:50
1

This worked for me. I would be interested to know if it is Ok to write response like this though.

client.html

<h:outputText value="#{UIHelper.clientId('look-up-address-panel-id')}" />

UIHelper.java

@ManagedBean(name = "UIHelper", eager = true)
@ApplicationScoped
public class UIHelper
{

public String clientId(final String id)
{
  FacesContext context = FacesContext.getCurrentInstance();
  UIViewRoot root = context.getViewRoot();
  final UIComponent[] found = new UIComponent[1];
  root.visitTree(new FullVisitContext(context), new VisitCallback()
  {
    @Override
    public VisitResult visit(VisitContext context, UIComponent component)
    {
      if (component.getId().equals(id))
      {
        found[0] = component;
        return VisitResult.COMPLETE;
      }
      return VisitResult.ACCEPT;
    }
  });
  return found[0] == null ? "" : "#" + found[0].getClientId().replace(":", "\\\\:");
}

}
mert inan
  • 1,537
  • 2
  • 15
  • 26
1

Since this was among the first results of my google search and I wondered why I got a

javax.el.PropertyNotFoundException (Property 'itemId' not found [...])

when trying the accepted solution, I would like to share my solution for JSF 1.2:

The UIComponent's method getClientId needs a FacesContext parameter (see UIComponent documentation). So add a binding to the backing bean and also another method that returns the clientId:

xhtml:

<h:button id="sampleButton" binding="#{backingBean.sampleButton}" value="Sample" />
<h:outputText value="sampleButton's client ID : #{backingBean.sampleButtonClientId}" />

Bean:

private UIComponent sampleButton;

public UIComponent getSampleButton() {
    return sampleButton;
}

public void setSampleButton(final UIComponent sampleButton) {
    this.sampleButton = sampleButton;
}

public String getSampleButtonClientId() {
    final FacesContext context = FacesContext.getCurrentInstance();
    return sampleButton.getClientId(context);
}

Notice that the bean you are binding your component to should be request scoped or else you could end up with a java.lang.IllegalStateException (duplicate Id for a component) (compare to this topic).

Community
  • 1
  • 1
david
  • 389
  • 3
  • 8