内容


高级 Facelets 编程

创建定制的逻辑标记和 EL 函数

Comments

Facelets 好像确实符合 JSF 开发社区的需求。我的 第一篇关于 Facelets 的文章 得到了大量积极肯定的反馈,我希望能够通过本文为您展示 Facelets 功能的更多有趣素材。感谢 Jacob Hookom 和其他所有参与 Facelets 开发的人员!

在上一篇文章中,我介绍了 Facelets 背后的理念,并为您演示了如何创建和操作 HTML 样式模板以及可重用复合组件。本文以上篇文章为基础,使用很多相同的示例和组件。首先介绍一种使用 Facelets 表达式语言(EL)函数进行国际化的简便方法。然后,为您演示如何创建合理的默认值和定制逻辑标记。最后介绍使用 Facelets 进行轻量级元编程的方法。

因为本文中有很多示例以前一篇文章中的示例为基础,因此强烈建议您 首先阅读那篇文章。在进一步深入学习之前,您也可能想 下载本文的示例代码,并 安装 Facelets(和 Tomahawk)

轻松国际化

有了 Facelets ,您不必再为国际化头疼。第一个练习演示如何扩展上篇文章中的字段复合组件,以处理日期、布尔值和各种应显示为文本的 Java 类型。另外还展示了如何为国际化字段标签而使用合理的默认值。

回想一下,在上一篇文章中,若未传递标签,字段复合组件将使用字段名作为默认标签,如下所示:

...
<!--  The label is optional. Use fieldName as label if label missing. -->
    <c:if test="${empty label}">
        <c:set var="label" value="${fieldName}" />
    </c:if>

假设您要改变这种方法。不再将 fieldName 作为默认标签载入,而是想让字段复合组件在与 Faces 相关的资源包中查找 fieldName。为此,创建定义标记的标记库和 EL 函数。首先,创建在资源包中查找 fieldName 的 EL 函数。如果无法在资源包中找到标签,则根据给定 fieldName 的大小写混合(camel case)形式生成 fieldName。 清单 1 给出了用法示例:

清单 1. EL 函数的用法示例
<html xmlns="http://www.w3.org/1999/xhtml"
      ...
      xmlns:arc="http://www.arc-mind.com/jsf/core"
      xmlns:t="http://myfaces.apache.org/tomahawk">


<!--  The label is optional. Generate it if it is missing. -->
    <c:if test="${empty label}">
        <c:set var="label"
               value="${arc:getFieldLabel(fieldName,namespace)}" />
    </c:if>

创建 EL 函数

在清单 1 中,EL 函数 arc:getFieldLabel(fieldName,namespace) 用来在与 Faces 相关的资源包中查找 fieldNamegetFieldLabel() 方法在资源包中实际查找标签。创建 EL 函数的步骤如下:

  1. 创建 TagLibrary 类。
  2. 创建 Java 实用程序类。
  3. 注册 getFieldLabel() 方法。
  4. 注册 TagLibrary 类。
  5. 注册 facelets-taglib 描述符文件。

下面逐一解释这些步骤,为您演示如何使用新的 EL 函数。

步骤 1. 创建 TagLibrary 类

在 Facelets 的 TagLibrary 类中定义 EL 函数和逻辑标记。为创建 TagLibrary 类,需要继承 AbstractTagLibrary,如清单 2 所示:

清单 2. 子类化 AbstractTagLibrary
package com.arcmind.jsfquickstart.tags;

import com.sun.facelets.tag.AbstractTagLibrary;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;


/**
 * JsfCoreLibrary is an example for IBM developerWorks (c).
 * @author Rick Hightower from ArcMind Inc. http://www.arc-mind.com
 */
public final class JsfCoreLibrary extends AbstractTagLibrary {
    /** Namespace used to import this library in Facelets pages  */
    public static final String NAMESPACE = "http://www.arc-mind.com/jsf/core";

    /**  Current instance of library. */
    public static final JsfCoreLibrary INSTANCE = new JsfCoreLibrary();
    ...
    ...

定义好标记库后,即可向其中添加 EL 函数和逻辑标记。

步骤 2. 创建 Java 实用程序类

接下来需要创建带有 getFieldLabel() 静态方法的 Java 实用程序类。此方法与 Facelets 没有特别的联系,只是常规 Java 静态方法。如清单 3 所示:

清单 3. 带有 getFieldLabel() 的 Java 实用程序类
package com.arcmind.jsfquickstart.tags;

import java.util.Locale;
import java.util.MissingResourceException;
import java.util.ResourceBundle;

import javax.faces.context.FacesContext;

/**
 * Functions to aid developing JSF applications.
 */
public final class JsfFunctions {
	/**
	 * Stops creation of a new JsfFunctions object.
	 */
	private JsfFunctions() {
	}

	/**
	 * Get the field label.
	 *
	 * @param fieldName
	 *            fieldName
	 * @param formId
	 *            form id
	 * @return Message from the Message Source.
	 */
	public static String getFieldLabel(final String fieldName,
			final String formId) {

		Locale locale = FacesContext.getCurrentInstance().getViewRoot()
				.getLocale();
		String bundleName = FacesContext.getCurrentInstance().getApplication()
				.getMessageBundle();

		ResourceBundle bundle = ResourceBundle
                                        .getBundle(bundleName, locale, getClassLoader());

		/** Look for formId.fieldName, e.g., EmployeeForm.firstName. */

		String label = null;
		try {
			label = bundle.getString(formId + fieldName);
			return label;
		} catch (MissingResourceException e) {
			// do nothing on purpose.
		}

		try {
			/** Look for just fieldName, e.g., firstName. */
			label = bundle.getString(fieldName);
		} catch (MissingResourceException e) {
			/**
			 * Convert fieldName, e.g., firstName automatically becomes First
			 * Name.
			 */
			label = generateLabelValue(fieldName);
		}

		return label;

	}

	private static ClassLoader getClassLoader() {
		ClassLoader classLoader = Thread.currentThread()
				.getContextClassLoader();
		if (classLoader == null) {
			return JsfFunctions.class.getClassLoader();
		}
		return classLoader;
	}

	/**
	 * Generate the field. Transforms firstName into First Name. This allows
	 * reasonable defaults for labels.
	 *
	 * @param fieldName
	 *            fieldName
	 *
	 * @return generated label name.
	 */
	public static String generateLabelValue(final String fieldName) {
		StringBuffer buffer = new StringBuffer(fieldName.length() * 2);
		char[] chars = fieldName.toCharArray();

		/* Change firstName to First Name. */
		for (int index = 0; index < chars.length; index++) {
			char cchar = chars[index];

			/* Make the first character uppercase. */
			if (index == 0) {
				cchar = Character.toUpperCase(cchar);
				buffer.append(cchar);

				continue;
			}

			/* Look for an uppercase character, if found add a space. */
			if (Character.isUpperCase(cchar)) {
				buffer.append(' ');
				buffer.append(cchar);

				continue;
			}

			buffer.append(cchar);
		}

		return buffer.toString();
	}
}

清单 3 所示的 getFieldLabel() 方法在与 JSF 相关的资源包中查找字段。如果找不到字段,则分隔大小写混排的字符串,改为首字母大写,使 字段名“firstName”变为“First Name”。这给出了一个合理的默认值,如果需要重写该值,可在资源包中进行设置。

步骤 3. 注册 getFieldLabel 方法

清单 2 (JsfCoreLibrary) 所示的 AbstractTagLibrary 类中,需要向基类注册 getFieldLabel() 方法,以将其作为 EL 函数使用。清单 4 完成这一任务:

清单 4. 将 JsfCoreLibrary.java 注册为 EL 函数
    public JsfCoreLibrary() {
        super(NAMESPACE);

        try {
            Method[] methods = JsfFunctions.class.getMethods();

            for (int i = 0; i < methods.length; i++) {
                if (Modifier.isStatic(methods[i].getModifiers())) {
                    this.addFunction(methods[i].getName(), methods[i]);
                }
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

如您所见,清单 4 只是使用反射遍历 JsfFunction 类的所有方法。然后使用继承的方法 addFunction() 添加 JsfFunction 的所有静态方法。

步骤 4. 注册 TagLibrary 类

现在已经定义了 EL 函数和包含该函数的 TagLibrary 类。接着,需要注册新的 TagLibrary 类,以使它能被 Facelets 找到。为此,在名为 jsf-core.taglib.xml 的新 facelets-taglib 文件中声明新 library 类,如清单 5 所示:

清单 5. jsf-core.taglib.xml
<?xml version="1.0"?>

<!DOCTYPE facelet-taglib PUBLIC
  "-//Sun Microsystems, Inc.//DTD Facelet Taglib 1.0//EN"
  "facelet-taglib_1_0.dtd">

<facelet-taglib>
	<library-class>
           com.arcmind.jsfquickstart.tags.JsfCoreLibrary
      </library-class>
</facelet-taglib>

步骤 5. 注册 facelets-taglib 描述符文件

为使 Facelets 找到标记库,您需要在 WEB-INF/web.xml 文件中注册它,如清单 6 所示:

清单 6. 向 Facelets 注册新的 taglib
	<context-param>
		<param-name>facelets.LIBRARIES</param-name>
		<param-value>
			/WEB-INF/facelets/tags/arcmind.taglib.xml;
			/WEB-INF/facelets/tags/jsf-core.taglib.xml
		</param-value>
	</context-param>

注意,我将 jsf-core.taglib.xml 添加到了传递给 init-param facelets.LIBRARIES 的由分号分隔的列表。复合组件放进其各自的 taglib 文件中。EL 函数和 Facelets 逻辑标记可放在同一 taglib 文件中。

在复合组件中使用 EL 函数

现在我们已经定义了 EL 函数,将其放在 Facelets taglib 描述符文件中,并在 web.xml 中注册了 taglib 描述符文件,接下来就可以开始使用标记了。在字段复合组件中,可使用 arc:getFieldLabel 在资源包中查找标签,如清单 7 所示:

清单 7. 使用 EL 函数
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml"  
      xmlns:ui="http://java.sun.com/jsf/facelets"  
      xmlns:h="http://java.sun.com/jsf/html" 
      xmlns:f="http://java.sun.com/jsf/core" 
      xmlns:arc="http://www.arc-mind.com/jsf/core"   
      xmlns:c="http://java.sun.com/jstl/core"
      xmlns:fn="http://java.sun.com/jsp/jstl/functions"  
      xmlns:t="http://myfaces.apache.org/tomahawk">

  THIS TEXT WILL BE REMOVED
  <ui:composition>

    <c:if test="${empty label}">
        <c:set var="label"
               value="${arc:getFieldLabel(fieldName,namespace)}" />
    </c:if>
       ...

注意,我在 xmlns:arc="http://www.arc-mind.com/jsf/core" HTML 元素中指定了 arc 库的名称空间,从而导入了该库。名称空间必须与 TagLibrary 类(本例中为 JsfCoreLibrary)中声明的名称空间匹配,如下所示:

public final class JsfCoreLibrary extends AbstractTagLibrary {
    /** Namespace used to import this library in Facelets pages  */
    public static final String NAMESPACE = "http://www.arc-mind.com/jsf/core";

仅仅是五步而已,但看上去似乎工作量不小。但只要完成了这些基本设置,即可添加更多的逻辑标记和 EL 函数,而不必每次都完成这样麻烦的步骤。所有的字段标签名都将具有合理的默认值。并且,如果您的应用程序只支持本国语言和位置,那么在大多数情况下,不必向资源包中添加新条目。只有在您希望使用特殊标签或用其他语言和/或在其他位置使用该应用程序时,才需要向资源包中添加条目。

Facelets 解决了国际化给我带来的一大烦恼:编写大量可能永远也用不到的代码。现在完全不必担心了!下一节,让我们来看看若您决定改变字段标记以使其自动生成布尔值、日期和文本组件,会发生怎样的情况。

创建定制逻辑标记

首先,您想为 field.jsp 标记添加元编程功能。为此,创建 Facelets 逻辑标记,使您了解一个特殊值绑定类型为 Boolean (isBoolean)、某些形式的文本 (isText) 还是日期 (isDate)。然后使用这些标记呈现适当的组件。

清单 8 演示这些标记的用法 (field.xhtml):

清单 8. field.xhtml
<!--  Initialize the value binding -->
<arc:setValueBinding var="vb" valueBinding="#{entity[fieldName]}"/>

<!--  If the value binding is a string, display an inputText field. -->
<arc:isText id="vb">
    <h:inputText id="#{fieldId}"  value="#{entity[fieldName]}" 
                 required="${required}" styleClass="fieldInput">
          <ui:insert />
    </h:inputText>
</arc:isText>

<!--  If the value binding is a boolean, display a  
      selectBooleanCheckbox field. -->
<arc:isBoolean id="vb">
         <h:selectBooleanCheckbox id="#{fieldId}"
                  value="#{entity[fieldName]}" required="${required}"/>
</arc:isBoolean>

<!--  If the value binding is a date, display a t:inputDate field. -->
<arc:isDate id="vb">
	<t:calendar id="#{fieldId}" renderPopupButtonAsImage="true"
			        renderAsPopup="true" 
                          value="#{entity[fieldName]}" 
                          required="${required}"
			        styleClass="fieldInput" >
                <ui:insert />
      </t:calendar>
	
</arc:isDate>

...

如果值绑定是某种类型的文本,则清单 8 中的 field.xhtml 代码将呈现 inputText。如果值绑定类型是 Boolean,则呈现 selectBooleanCheckbox。最后,如果是日期(参见 “关于 Tomahawk”),则呈现 Tomahawk 日历组件。

下一节,您将创建值绑定,以获得绑定到特殊组件的类型。值绑定包含关于组件所绑定的值的类型信息。如果您使用过 JSF,应对值绑定比较熟悉。例如,#{Employee.firstName} 是可能等同于字符串类型的值绑定标记,而 #{Employee.age} 标记可能等同于整型。下面我们将学习如何使用 Facelets 创建值绑定标记。

创建值绑定标记

创建在 Facelets 中检索值绑定的标签包含四个步骤:

  1. 创建一个子类化 TagHandler 的类。
  2. 在构造器中注册属性。
  3. 重写 apply 方法。
  4. JsfCoreLibrary 类中注册标记。

下面逐步介绍构建值绑定标记的过程,然后演示其用法。

步骤 1. 创建子类化 TagHandler 的类

可以使用 Facelets TagHandler 注入逻辑以决定是否把标记体中的组件添加到组件树中。切记,Facelets 模板的最后阶段是创建 JSF 组件树。

清单 9 演示 SetValueBindingHandler 标记如何子类化 TagHandlerSetValueBindingHandler 只定义了一个变量并把变量放置在 Facelets 作用域内,这样您的逻辑标记就可以重用该变量。

清单 9. SetValueBindingHandler 子类化 TagHandler
package com.arcmind.jsfquickstart.tags;

import com.sun.facelets.FaceletContext;
import com.sun.facelets.tag.TagAttribute;
import com.sun.facelets.tag.TagConfig;
import com.sun.facelets.tag.TagHandler;


import javax.el.ValueExpression;
import javax.faces.component.UIComponent;


/**
 * Maps value binding and type so other tags can access it.
 */
public final class SetValueBindingHandler extends TagHandler {

步骤 2. 在构造器中注册属性

编写 Facelets 标记时,务必牢记属性是由标记自身定义的,而不是在 XML 文件中定义的。 SetValueBindingHandler 的构造器在构造器中定义并注册了两个属性(varvalueBinding),如清单 10 所示:

清单 10. 在构造器中注册属性
/**
 * Maps value binding and type so other tags can access it.
 */
public final class SetValueBindingHandler extends TagHandler {
    /**   The name of the new variable that this tag defines. */
    private final TagAttribute var;

    /**   The actual value binding expression. */
    private final TagAttribute valueBinding;

    /**
     * Constructor. Set up the attributes for this tag.
     *
     * @param config TagConfig
     */
    public SetValueBindingHandler(final TagConfig config) {
        super(config);
        /* Define var and valueBinding attributes. */
        this.var = this.getRequiredAttribute("var");
        this.valueBinding = this.getRequiredAttribute("valueBinding");
    }

步骤 3. 重写 apply 方法

然后,重写 SetValueBindingHandler 中的 apply 方法。通常,重写 apply 方法是为了程序决定是否把标记体中定义的组件添加到组件树中。但是,SetValueBindingHandler 只为所传递的值绑定定义了一个变量 (不论 var 设置为何值;例如,“vb”)。清单 11 给出了重写 apply 方法的代码:

清单 11. 重写 apply 方法
/**
 * Maps value binding and type so other tags can access it.
 */
public final class SetValueBindingHandler extends TagHandler {
...
...

    /**
     * Apply.
     *
     * @param faceletsContext faceletsContext
     * @param parent parent
     *
     * @throws IOException IOException
     */
    public void apply(final FaceletContext faceletsContext, final UIComponent parent) {
    	/* Create the ValueExpression from the valueBinding attribute. */
        ValueExpression valueExpression =
            this.valueBinding.getValueExpression(faceletsContext, Object.class);

        /* Get the name of the new value. */
        String tvar = this.var.getValue(faceletsContext);
        Class type = valueExpression.getType(faceletsContext);

        /* Put the value binding into the FaceletsContext where
         * we can retrieve it from other components.
         */
        faceletsContext.setAttribute(tvar, valueExpression);

        /* Cache the type so we don't have to look it
         * up in each tag. */
        faceletsContext.setAttribute(tvar + "Type", type);
    }

清单 11 定义了两个变量:varvar + "Type"。 然后,直接把变量放进 faceletsContext (更多细节请参见清单 11 中的注释)。

步骤 4. 在 JsfCoreLibrary 类中注册标记

可使用与前一个示例相同的 JsfCoreLibraryTagLibrary可以同时包含 EL 函数和逻辑标记。现在,在此练习的最后一步,通过调用超类的 addTagHandlerJsfCoreLibrary 中注册 SetValueBindingHandler 标记,如清单 12 所示:

清单 12. 在 JsfCoreLibrary 中注册标记
/**
 * JsfCoreLibrary is an example for IBM developerWorks (c).
 * @author Rick Hightower from ArcMind Inc. http://www.arc-mind.com
 */
public final class JsfCoreLibrary extends AbstractTagLibrary {
    /** Namespace used to import this library in Facelets pages  */
    public static final String NAMESPACE = "http://www.arc-mind.com/jsf/core";

    /**  Current instance of library. */
    public static final JsfCoreLibrary INSTANCE = new JsfCoreLibrary();

    /**
     * Creates a new JsfCoreLibrary object.
     *
     */
    public JsfCoreLibrary() {
        super(NAMESPACE);

        this.addTagHandler("setValueBinding", SetValueBindingHandler.class);
        ...
    }

Facelets 的优势是,与在 JSP 中编写相同的标记相比,Facelets 使用了的 XML 更少。您可以轻松开发更多的标记并在 JsfCoreLibray 中注册它们。 注意标记的名称是 setValueBinding

使用定制逻辑标记

现在我们已经定义了 setValueBinding 标记,可以开始使用它,如清单 13 所示:

清单 13. 使用 setValueBinding 标记
<html xmlns="http://www.w3.org/1999/xhtml"  
      xmlns:ui="http://java.sun.com/jsf/facelets"  
      xmlns:h="http://java.sun.com/jsf/html" 
      xmlns:f="http://java.sun.com/jsf/core" 
      xmlns:arc="http://www.arc-mind.com/jsf/core"   
      xmlns:c="http://java.sun.com/jstl/core"
      xmlns:fn="http://java.sun.com/jsp/jstl/functions"  
      xmlns:t="http://myfaces.apache.org/tomahawk">

  THIS TEXT WILL BE REMOVED
  <ui:composition>
  ...
    <!--  Initialize the value binding -->
    <arc:setValueBinding var="vb" 
                         valueBinding="#{entity[fieldName]}" />

不需要向 web.xml 导入任何东西,也不必进行任何设置。 只要定义了标记库(如 “arc”),即可向其中添加标记和函数,不必更新任何 XML 文件。没错,您可以 避开 XMHell!

使用 Facelets 进行元编程

关于 Facelets 的最后一点有趣的用途(对于本文来说),若值绑定是某种特殊类型,您可创建允许添加标记体中所定义的组件的标记。

神奇的标记 IsTypeHandler() 选择性地决定是否把一个组件添加到组件树中。如果应该将标记体中定义的组件添加到组件树,标记将调用 this.nextHandler.apply(faceletsContext, aParent)isTypeHandler() 调用抽象方法 isType() 来决定是否应调用 this.nextHandler.apply(faceletsContext, aParent) 并把标记处理程序体中定义的组件添加到组件树中。清单 14 显示所有这些方法的基类:

清单 14. 用于处理类型的基类
package com.arcmind.jsfquickstart.tags;

import com.sun.facelets.FaceletContext;
import com.sun.facelets.tag.TagAttribute;
import com.sun.facelets.tag.TagConfig;
import com.sun.facelets.tag.TagHandler;

import java.io.IOException;

import javax.faces.component.UIComponent;


/**
 * Is the current field a boolean.
 */
public abstract class IsTypeHandler extends TagHandler {
    /**   */
    private final TagAttribute id;

    /**
     * Create tag.
     *
     * @param config TagConfig
     */
    public IsTypeHandler(final TagConfig config) {
        super(config);
        this.id = this.getRequiredAttribute("id");
    }

    /**
     * Is the current field a boolean.
     *
     * @param faceletsContext ctx
     * @param aParent parent
     *
     * @throws IOException IOException
     */
    public void apply(final FaceletContext faceletsContext, final UIComponent aParent)
        throws IOException {
        /* Get the name of the value binding. */
        String tid = this.id.getValue(faceletsContext);
        Class type = 
          (Class) faceletsContext.getVariableMapper().resolveVariable(tid + "Type")
                                                .getValue(faceletsContext);

        /* If the type is a boolean, process the body of the tag.
         */
        if (isType(type)) {
            this.nextHandler.apply(faceletsContext, aParent);
        }
    }

    /**
     *
     *
     * @param type type
     *
     * @return true if this is the correct type.
     */
    protected abstract boolean isType(Class type);
}

然后,为您想处理的每个类型添加一个标记,如清单 15 所示:

清单 15. 处理特定类型
package com.arcmind.jsfquickstart.tags;

import com.sun.facelets.tag.TagConfig;


/**
 * Is the current field a boolean.
 */
public final class IsBooleanHandler extends IsTypeHandler {

    /**
     * Create tag.
     *
     * @param config TagConfig
     */
    public IsBooleanHandler(final TagConfig config) {
        super(config);
    }

    /**
     *
     *
     * @param type type
     *
     * @return true if this is a boolean.
     */
    protected boolean isType(final Class type) {
        /* If the type is a boolean, process the body of the tag.
         */
        if (type == Boolean.class) {
            return true;
        } else if (type.isPrimitive() && "boolean".equals(type.getName())) {
            return true;
        }

        return false;
    }
}

package com.arcmind.jsfquickstart.tags;

import java.util.Date;

import com.sun.facelets.tag.TagConfig;

/**
 * Is the current field a date.
 */
public final class IsDateHandler extends IsTypeHandler {

    /**
     * Create tag.
     *
     * @param config TagConfig
     */
    public IsDateHandler(final TagConfig config) {
        super(config);
    }

    /**
     *
     *
     * @param type type
     *
     * @return true if this is a boolean.
     */
    protected boolean isType(final Class type) {
        /* If the type is a string, process the body of the tag.
         */
        if (type == Date.class) {
            return true;
        }
        return false;
    }
}

package com.arcmind.jsfquickstart.tags;

import java.math.BigDecimal;
import java.math.BigInteger;

import com.sun.facelets.tag.TagConfig;

/**
 * Is the current field a string.
 */
public final class IsTextHandler extends IsTypeHandler {

    /**
     * Create tag.
     *
     * @param config TagConfig
     */
    public IsTextHandler(final TagConfig config) {
        super(config);
    }

    /**
     *
     *
     * @param type type
     *
     * @return true if this is a boolean.
     */
    protected boolean isType(final Class type) {
        /* If the type is a string, process the body of the tag.
         */
        if (type == String.class
        		|| type == Integer.class
        		|| type == BigDecimal.class
        		|| type == BigInteger.class
        		|| type == Character.class
        		|| type == Long.class
        		|| type == Short.class
        		|| type == Byte.class
        		|| type == Float.class
        		|| type == Double.class
        		|| (type.isPrimitive() && !type.getName().equals("boolean"))
        		) {
            return true;
        } else {
        	return false;
        }
    }
}

清单 15 只使用值绑定的类型来决定是否把标记体内定义的组件添加到组件树中。 每个标记都重写 isType() 方法。 这里只使用了类型确定是否把标记体内的组件添加到组件树中,但如果您愿意,还可以轻松地使用注释或其他形式的元数据。

自然,您的任务尚未完成,还要向 JsfCoreLibrary注册 上述所有组件,如清单 16 所示:

清单 16. 向 TagLibrary 添加类型处理标记
public final class JsfCoreLibrary extends AbstractTagLibrary {
   ...
    public JsfCoreLibrary() {
        ...

        this.addTagHandler("isBoolean", IsBooleanHandler.class);
        this.addTagHandler("isText", IsTextHandler.class);
        this.addTagHandler("isDate", IsDateHandler.class);
        ...

使用基于类型的标记

现在,标记库中又有了三个标记:isBooleanisTextisDate()。 如清单 17 所示,可在字段标记中使用这些标记:

清单 17. 最终的 field.xhtml
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" 
      xmlns:ui="http://java.sun.com/jsf/facelets" 
      xmlns:h="http://java.sun.com/jsf/html" 
      xmlns:f="http://java.sun.com/jsf/core" 
      xmlns:arc="http://www.arc-mind.com/jsf/core" 
      xmlns:c="http://java.sun.com/jstl/core"
	xmlns:fn="http://java.sun.com/jsp/jstl/functions"   
      xmlns:t="http://myfaces.apache.org/tomahawk">

	THIS TEXT WILL BE REMOVED
  <ui:composition>

    <!--  The label is optional. Generate it if it is missing. -->
    <c:if test="${empty label}">
		<c:set var="label" 
                   value="${arc:getFieldLabel(fieldName,namespace)}" />
    </c:if>

    <!-- The required attribute is optional, 
         initialize it to true if not found. -->
    <c:if test="${empty required}">
        <c:set var="required" value="true" />
    </c:if>


    <h:panelGroup>
        <h:outputLabel id="${fieldName}Label" 
                       value="${label}" for="#{fieldName}" />
    </h:panelGroup>

    <!--  Initialize the value binding -->
    <arc:setValueBinding var="vb" 
                         valueBinding="#{entity[fieldName]}" />

    <!--  If the value binding is a string, 
          display an inputText field. -->
    <arc:isText id="vb">

        <h:inputText id="#{fieldName}" 
                     value="#{entity[fieldName]}" 
                     required="${required}" styleClass="fieldInput">
            <ui:insert />
        </h:inputText>
    </arc:isText>

    <!--  If the value binding is a boolean, display a
           selectBooleanCheckbox field. -->
    <arc:isBoolean id="vb">
        <h:selectBooleanCheckbox  id="#{fieldName}" 
                                  value="#{entity[fieldName]}" 
                                  required="${required}" />
    </arc:isBoolean>

    <!--  If the value binding is a date, 
          display a t:inputDate field. -->
    <arc:isDate id="vb">
        <t:calendar id="#{fieldName}" renderPopupButtonAsImage="true" 
                   renderAsPopup="true" value="#{entity[fieldName]}" 
                   required="${required}" styleClass="fieldInput">
              <ui:insert />
        </t:calendar>
    </arc:isDate>

    <!--  Display any error message that are found -->
    <h:message id="${fieldName}Message" 
               style="color: red; text-decoration: overline" 
               for="#{fieldName}" />

</ui:composition>
	THIS TEXT WILL BE REMOVED AS WELL

</html>

如果值绑定类型是 IntegerStringBigDecimalCharacterLongShortByteFloatDoubleshortintchar 等,字段标记将显示 inputText 字段。如果值绑定为 boolean 或 Boolean 类型,则字段标记将显示一个复选框。如果值绑定是 java.util.Date 类型 —— 坦率地说它非常酷,字段标记将显示 Tomahawk 日历组件。

结束语

本文为您演示了一些有趣而强大的 Facelets 应用程序。Facelets EL 函数大大降低了国际化的难度,定制标记使您能够扩展 Facelets 的内置逻辑标记。随后,我们又轻轻松松地扩展了上一篇文章中的简单示例,使它具有更高级的功能。我还为您演示了如何更新字段复合组件来处理日期(Tomahawk 日历)、Boolean(复选框)和各种应显示为文本的 Java 类型。另外还介绍了用于为字段组件国际化标签的合理默认值。请继续关注,未来几个月中,我们还将介绍 Facelets 的更多有趣功能。


下载资源


相关主题


评论

添加或订阅评论,请先登录注册

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Java technology
ArticleID=126237
ArticleTitle=高级 Facelets 编程
publish-date=06082006