JSF2.0实战 - 4、自定义组件

这里展示一个JSF2.0组件是如何开发的。示例程序是一个简单的数字文本框,在使用时前端页面会自动加载css和js,对文本框进行功能增强和美化,后台java类控制文本框的值。

项目环境:

1、JSF2.1+

2、JDK1.6+

3、Tomcat6.0+ 

4、Eclipse3.6+ 我用的Indigo

目录结构:

JSF2.0实战 - 4、自定义组件_第1张图片

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.onkeyup=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

JSF2.0实战 - 4、自定义组件_第2张图片

有非数字字符时,样式有变化

JSF2.0实战 - 4、自定义组件_第3张图片

这个组件因为只继承了UIComponentBase,因此不支持ajax特性,也不支持listener事件。如果要支持ajax或者listener事件,需要组件类继承UIInput。这里只是为了演示组件是如何开发的,在实际应用中,我们一般对于数据交互组件,如输入框、日期控件、下拉框等,都直接继承UIInput,对于一般的显示组件,如Layout、Tree等才继承UIComponentBase。


下载示例代码


JSF2.0官方实现Mojarra提供了基于html标准控件实现的组件库,这套组件库在前端页面表现太弱,开发企业级软件项目不实用。通常企业要根据自己的实际情况,结合一套丰富的javascript ui库,如ExtJs、JQuery ui等来打造自己的JSF组件库。后面我将一步步演示如何利用一套完整的javascript ui库来打造企业组JSF组件库。

你可能感兴趣的:(框架,前端,JSF,jsf2,JSF2.0)