JSF的很多文档上面说,要建立一个自定义组件,需要定义继承自UIComponentBase之类的类,然后写decode/encode方法等等,一大套内容,很复杂。幸好我们有其他的办法。其实定义一个组件在大多数情况下是不用去写这样的代码的。本节的内容是一个选择true/false两个值得下拉框组件。定义一个这样的组件需要做如下工作:

1.定于一个页面,用来实现这个组件,主要内容如下(其实这个页面的内容没有强制要求):

<h:selectOneMenu id="#{id}" value="#{value}" rendered="#{rendered}" styleClass="#{styleClass}">
    
<!--是否展示null值对应的选项-->
     
<c:if test="#{not empty noSelectionLabel}">
         
<f:selectItem itemLabel="#{noSelectionLabel}"/>
     
</c:if>
     
<f:selectItem itemLabel="#{empty trueLabel ? '是' : trueLabel}" itemValue="true"/>
     
<f:selectItem itemLabel="#{empty falseLabel ? '否' : falseLabel}" itemValue="false"/>
 
</h:selectOneMenu>

上面的代码中的每一个#{xx},都是我们最终使用这个组件的时候用需要用属性参数的形式传进来的。

2.注册Tag,需要在类路径下的某个*.taglib.xml中增加一个注册:


<!--这个是标记库的namespace-->
<namespace>http://www.a.com/jsf/facelets/tags</namespace>
<!--该标记的注册-->
<tag>
    
<tag-name>selectBooleanMenu</tag-name>
    
<source>resources/jsf/components/selectBooleanMenu.xhtml</source>
</tag>


如此,在页面上我们就可以使用这个标记了:


<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h
="http://java.sun.com/jsf/html"
      xmlns:f
="http://java.sun.com/jsf/core"
    xmlns:jsf
="http://www.a.com/jsf/facelets/tags">
<body>
    
<h:form>
        你吃或者不吃,饭就在那里:
<br/>
        
<jsf:selectBooleanMenu value="#{someAction.booleanValue}" noSelectionLabel="请选择" trueLabel="吃" falseLabel="不吃"/>
    
</h:form>
</body>
</html>


如此,自定义标记组件还是很容易的。在一个组件的页面中(selectBooleanMenu.xhtml)你可以使用后台任意bean。所以即使某些工作需要结合后台的代码来完成,你也不需要实现啥接口。


关于模块化,在传统的mvc中,你请求/a.do,到某ActionBean,处理,然后forward到页面。这样会有一个问题:假设a.do和ActionA用来处理a业务,b.do和ActionB用来处理b业务。c.do集合了a,b的一些功能,那么你无法再c对应的页面上同时使用ActionA,ActionB。所以你要绕了。


在JSF中,一个页面上使用表达式引用到后台上下文中的任意Bean,要做c页面,你在c页面上直接引用ActionA和ActionB完成功能即可。这样你不用做一些“混合”的东西。后台代码的模块化更加容易实现,前台展示和后台业务处理分离的更加清楚。