这里展示一个JSF2.0组件是如何开发的。示例程序是一个简单的数字文本框,在使用时前端页面会自动加载css和js,对文本框进行功能增强和美化,后台java类控制文本框的值。
项目环境:
1、JSF2.1+
2、JDK1.6+
3、Tomcat6.0+
4、Eclipse3.6+ 我用的Indigo
目录结构:
1、src下建立META-INF目录,放置资源文件和配置文件,这样可以把class文件、配置文件、资源文件js和css打包成独立的jar包,在多个项目间复用。当项目中使用这个组件时,JSF2.0框架会根据jar中的xml配置,找到组件的class,根据class内的annotation自动引入并读取所需的js和css资源文件。
2、组件通常由一个组件类和一个组件的渲染类组合完成。组件类提供给业务开发者使用,组件渲染类提供给JSF框架用于处理组件的解析和渲染。
NumberInputText.java
package test.component;
import javax.faces.component.UIComponentBase;
//组件通常继承UIComponentBase
public class NumberInputText extends UIComponentBase {
//用于xml配置和render
public static final String COMPONENT_FAMILY = "test.NumberInputText";
public static final String COMPONENT_TYPE = "test.NumberInputText";
enum PropertyKeys {
//组件的属性
value
}
@Override
public String getFamily() {
return COMPONENT_FAMILY;
}
public Integer getValue() {
//通常用getStateHelper()来存放属性值,stateHelper可以自动处理属性前后台的序列化和反序列化
return (Integer) getStateHelper().get(PropertyKeys.value);
}
public void setValue(Integer value) {
getStateHelper().put(PropertyKeys.value, value);
}
}
NumberInputTextRenderer.java
package test.component;
import java.io.IOException;
import java.util.Map;
import javax.faces.application.ResourceDependencies;
import javax.faces.application.ResourceDependency;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import javax.faces.render.FacesRenderer;
import javax.faces.render.Renderer;
//告诉这个类是渲染哪个组件的,通过componentFamily和rendererType到配置文件中定位组件
@FacesRenderer(componentFamily = NumberInputText.COMPONENT_FAMILY, rendererType = NumberInputText.COMPONENT_TYPE)
//组件依赖的资源文件,必须位于classes/META-INF/resources目录下,或WebContent/resources目录下,在页面上渲染组件时会自动引入资源文件
@ResourceDependencies({
@ResourceDependency(library = "test/css", name = "numberInputText.css", target = "head"),
@ResourceDependency(library = "test/js", name = "numberInputText.js", target = "head")})
//渲染类必须继承Renderer
public class NumberInputTextRenderer extends Renderer {
//渲染函数。在前端页面上输入组件的HTML代码
@Override
public void encodeBegin(FacesContext context, UIComponent component)
throws IOException {
ResponseWriter writer = context.getResponseWriter();
//组件的ID。如果前端页面没有给组件定义ID,JSF框架会自动给组件分配一个ID
String clientId = component.getClientId(context);
NumberInputText numberInputText = (NumberInputText) component;
//输入一个<input id=[clientId] name=[clientId] class="numberInputText" value=[value] />的HTML元素
writer.startElement("input", component);
writer.writeAttribute("id", clientId, null);
writer.writeAttribute("name", clientId, null);
writer.writeAttribute("class", "numberInputText", null);
writer.writeAttribute("value", numberInputText.getValue(), null);
writer.endElement("input");
//输入一段javascript脚本,增强input的功能
writer.startElement("script", component);
writer.write("createNumberInputText(document.getElementById('" + clientId + "'));");
writer.endElement("script");
}
//解析函数。解析从提交请求中获取的数据,设置组件的属性
@Override
public void decode(FacesContext context, UIComponent component) {
String clientId = component.getClientId(context);
NumberInputText numberInputText = (NumberInputText) component;
//获取请求参数
Map<String, String> parameterMap = context.getExternalContext().getRequestParameterMap();
String value = parameterMap.get(clientId);
try {
//设置组件值
numberInputText.setValue(Integer.parseInt(value));
} catch (NumberFormatException e) {
//e.printStackTrace();
}
}
}
numberInputText.js
function createNumberInputText(element) {
element.οnkeyup=function(e) {
//如果input.value有非数字字符,改变input的样式
if (isNaN(element.value)) {
element.className = "numberInputTextError";
} else {
element.className = "numberInputText";
}
};
}
numberInputText.css
.numberInputText {/*正常时的样式*/
border:solid 1px #759dc0;
}
.numberInputTextError {/*有非数字字符时使用此样式*/
border:solid 1px #d46464;
background-color: #e5f2fe;
color:red;
}
faces-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<faces-config xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_2_1.xsd"
version="2.1">
<component>
<component-type>test.NumberInputText</component-type>
<component-class>test.component.NumberInputText</component-class>
</component>
</faces-config>
test.taglib.xml
<?xml version="1.0" encoding="UTF-8"?>
<facelet-taglib xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
version="2.0" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facelettaglibrary_2_0.xsd">
<namespace>http://test.component</namespace>
<tag>
<tag-name>numberInputText</tag-name>
<component>
<component-type>test.NumberInputText</component-type>
<renderer-type>test.NumberInputText</renderer-type>
</component>
</tag>
</facelet-taglib>
Test.java
import javax.faces.bean.ManagedBean;
import test.component.NumberInputText;
@ManagedBean
public class Test {
private NumberInputText text1;
private NumberInputText text2;
public void print() {
//获取text1的值,赋值给text2
text2.setValue(text1.getValue());
}
public NumberInputText getText1() {
return text1;
}
public void setText1(NumberInputText text1) {
this.text1 = text1;
}
public NumberInputText getText2() {
return text2;
}
public void setText2(NumberInputText text2) {
this.text2 = text2;
}
}
test.xhtml
<!DOCTYPE HTML>
<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:t="http://test.component"><!-- xmlns:t="http://test.component"引入自定义组件 -->
<h:head><!-- 必须用<h:head>,组件才能自动引入依赖的资源文件 -->
<meta charset="utf-8" />
<title>Number Input Test</title>
</h:head>
<body>
<h:form><!-- 必须有<h:form>,后台才能正常运行组件功能 -->
<t:numberInputText binding="#{test.text1}" />
<h:commandButton value="确定" actionListener="#{test.print}" />
<t:numberInputText binding="#{test.text2}" />
</h:form>
</body>
</html>
运行结果
url:http://localhost:8080/JsfComponent/test.faces
有非数字字符时,样式有变化
这个组件因为只继承了UIComponentBase,因此不支持ajax特性,也不支持listener事件。如果要支持ajax或者listener事件,需要组件类继承UIInput。这里只是为了演示组件是如何开发的,在实际应用中,我们一般对于数据交互组件,如输入框、日期控件、下拉框等,都直接继承UIInput,对于一般的显示组件,如Layout、Tree等才继承UIComponentBase。
JSF2.0官方实现Mojarra提供了基于html标准控件实现的组件库,这套组件库在前端页面表现太弱,开发企业级软件项目不实用。通常企业要根据自己的实际情况,结合一套丰富的javascript ui库,如ExtJs、JQuery ui等来打造自己的JSF组件库。后面我将一步步演示如何利用一套完整的javascript ui库来打造企业组JSF组件库。