在JSF中通过ID查找组件

53

我希望通过提供的ID从托管bean中找到一些UIComponent

我已经编写了以下代码:

private UIComponent getUIComponent(String id) {  
      return FacesContext.getCurrentInstance().getViewRoot().findComponent(id) ;  
}

我已经定义了一个p:inputTextarea,如下:

<p:inputTextarea id="activityDescription" value="#{adminController.activityDTO.activityDescription}" required="true" maxlength="120"
    autoResize="true" counter="counter" counterTemplate="{0} characters remaining." cols="80" rows="2" />
现在,如果以getUIComponent("activityDescription")的方式调用方法,则返回null,但是如果我以getUIComponent("adminTabView:activityForm:activityDescription")的方式调用,则可以获得org.primefaces.component.inputtextarea.InputTextarea实例。
是否有任何方法能够仅使用id即“activityDescription”而不是绝对id即“adminTabView:activityForm:activityDescription”获取组件?

8
为什么会是-1?这不是一个合理的问题吗? - Tapas Bose
3
也许只是一些在 [java] 标签中闲逛的天真用户,不知道你在说什么。 - BalusC
5
我注意到你正在使用PrimeFaces。你是否已经检查了ComponentUtils.java文件?http://grepcode.com/file/repository.primefaces.org/org.primefaces/primefaces/3.0.M1/org/primefaces/util/ComponentUtils.java#ComponentUtils.findComponent%28javax.faces.component.UIComponent%2Cjava.lang.String%29 - HRgiger
2
@Tapas Bose:一道非常棒的问题,真正澄清了我的UIComponent类的视觉化。我给你+1。这种问题应该得到更多的回报。 - Farhan stands with Palestine
5个回答

50
你可以使用以下代码:
public UIComponent findComponent(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 != null 
                && id.equals(component.getId())) {
                found[0] = component;
                return VisitResult.COMPLETE;
            }
            return VisitResult.ACCEPT;              
        }
    });

    return found[0];

}

这段代码只会查找树中具有您传递的 id 的第一个组件。如果在树中有两个名称相同的组件(如果它们位于两个不同的命名容器下,则可能存在此情况),则需要自定义处理。


1
如果 (component != null && component.getId() != null && component.getId().equals(id)) // 进行这些空值检查 - Fuzzy Analysis
10
使用JSF API进行编译时,无法使用FullVisitContext。但可以使用VisitContext.createVisitContext(FacesContext.getCurrentInstance())来创建访问上下文。 - jessepeng
即使进行了空值检查,这个程序在我的电脑上陷入了无限循环,所以它并不是一个好的解决方案。 - Jim
您需要检查 component 不是 null。虽然这通常是一个好习惯,但是 visitTree 是否有可能返回一个 null 组件呢? - Marco Sulla

2
我尝试了这段代码,它很有帮助:
private static UIComponent getUIComponentOfId(UIComponent root, String id){
    if(root.getId().equals(id)){
        return root;
    }
    if(root.getChildCount() > 0){
        for(UIComponent subUiComponent : root.getChildren()){
                UIComponent returnComponent = getUIComponentOfId(subUiComponent, id);
                if(returnComponent != null){
                    return returnComponent;
            }
        }
    }
    return null;
}

谢谢


1
也许这是不可能的。 FacesContext.getCurrentInstance().getViewRoot().findComponent(id) 方法只返回一个 UIComponent。ViewRoot 是作为树构建的,因此如果您在视图中有两个表单,每个表单都有一个带有 id="text" 的组件,则它们将添加到其父组件的 id 中,以便它们不会冲突。如果您将两个 id="text" 组件放在同一个表单中,则会抛出 java.lang.IllegalStateException
如果您想查找所有具有搜索 id 的组件,可以编写实现此功能的方法:
List<UIComponent> foundComponents = new ArrayList();
for(UIComponent component: FacesContext.getCurrentInstance().getViewRoot().getChildren()) {
    if(component.getId().contains("activityDescription")){
        foundComponents.add(component);
    }
}

或者如果你想找到第一次出现的位置:

UIComponent foundComponent;
for(UIComponent component: FacesContext.getCurrentInstance().getViewRoot().getChildren()) {
    if(component.getId().contains("activityDescription")){
        foundComponent = component;
        break;
    }
}

4
有一个问题,因为如果你从视图根获取子元素,可能会有另一个具有子元素的组件,然后再在它内部继续嵌套更多... 在这里你必须使用递归——我认为没有其他选择。 - Michał Kupisiński
1
当然,你是正确的,这是一棵树。我忽略了数据结构。 - kauedg
1
请注意,UIComponent.findComponent 上的文档提示使用回调函数和 invokeOnComponent 来搜索组件的 clientId。 - Elias Dorneles
@elias 我认为你的回答完全符合 OP 的需求。 - kauedg

0

是的,在所有父组件中,如果它们是NamingContainers,则必须添加属性prependId =“false” - 它将在<h:form>中起作用,并且应该在其他地方起作用。
如果无法通过.xhtml文件中的属性设置它,则必须通过编程方式设置此值。

问题作者评论后的建议:

如果您正在使用的组件中没有这样的属性,请尝试编写类似于此的查找方法:

private UIComponent findComponent(String id, UIComponent where) {
if (where == null) {
   return null;
}
else if (where.getId().equals(id)) {
   return where;
}
else {
   List<UIComponent> childrenList = where.getChildren();
   if (childrenList == null || childrenList.isEmpty()) {
      return null;
   }
   for (UIComponent child : childrenList) {
      UIComponent result = null;
      result = findComponent(id, child);
      if(result != null) {
         return result;
   }
   return null;
}   

接下来只需调用

UIComponent iamLookingFor = findComponent(myId, FacesContext.getCurrentInstance().getViewRoot());

那会有帮助吗?


感谢您的回复。在<h:form/>中有一个属性prependId="false",但是<p:tabView/>没有这个属性。 - Tapas Bose

0
只需在包含此文本区域的表单中添加prependId="false"即可。

7
糟糕的建议。不要那样做。永远不要使用prependId="false"。相关信息请参考:https://dev59.com/yGs05IYBdhLWcg3wJurp 。这个选项只是为了让非Ajax基础的j_security_check表单能够在JSF 1.x中工作而临时引入的。在JSF 2.x上,永远不要使用它。 - BalusC

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接