Содержание


Расширенное программирование Facelets

Создание собственных логических тегов и EL функций

Comments

Facelets похоже понравились сообществу разработчиков JSF. Я получил много положительных отзывов на мою первую статью о Facelets и с нетерпение хочу показать в этой статье множество интересных вещей, которые вы можете сделать с помощью Facelets. Слава Якобу Хукому (Jacob Hookom) и всем, кто принимал участие в разработке Facelets!

В моей предыдущей статье я дал общее представление о Facelets и показал, как создавать и управлять его шаблонами HTML-style и компонентами композиции многократного использования. Эта статья продолжает начатое тогда исследование и использует примеры и компоненты, которые я представил в предыдущей статье. Для начинающий я покажу безболезненный способ интернационализации, используя функции Facelets expression language (EL). Далее мы создадим рациональные значения по умолчанию и собственные логические теги. И наконец я покажу несложное метапрограммирование на Facelets.

Так как большинство примеров этой статьи основаны на примерах из предыдущей, я настоятельно рекомендую прочитать первую статью. Вы также можете загрузить код примеров для этой статьи и установить Facelets (и Tomahawk) прежде, чем двигаться дальше.

Интернационализировать просто

Интернационализация не доставит вам много хлопот, если вы работаете с Facelets. В качестве первого упражнения я покажу, как расширить компонент композиции поля из первой статьи для работы с базой данных, булевыми выражениями и всеми видами Java, которые могут быть представлены как текст. Я также покажу, как использовать рациональные значения по умолчанию для интернационализации ярлыка поля.

Вспомним, что компонент композиции поля использовал имя поля в качестве ярлыка по умолчанию, если ярлык был пропущен, как в этом листинге:

...
<!--  Ярлык необязателен. Используйте fieldName  
(имя поля) в качестве ярлыка, если он пропущен. -->
    <c:if test="${empty label}">
        <c:set var="label" value="${fieldName}" />
    </c:if>

Хотите это изменить? Чтобы не загружать fieldName в качестве ярлыка по умолчанию, вы хотите, чтобы компонент композиции поля искал fieldName в пакете ресурсов, связанных с Faces. Для этого вы создаете тег library (библиотеки), который определяет теги и EL функции. Сначала вы создаете EL функцию, которая ищет fieldName в пакете ресурсов. Если она не может найти ярлык в пакете ресурсов, то пытается создать fieldName, исходя из fieldName, записанного в виде camel-case. В листинге 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">


<!--  Ярлык необязателен. 
Создайте его, если он пропущен.-->
    <c:if test="${empty label}">
        <c:set var="label"
               value="${arc:getFieldLabel
               (fieldName,namespace)}" />
    </c:if>

Создание EL функции

В листинге 1 EL функция использовала метод arc:getFieldLabel(fieldName,namespace) для поиска fieldName в пакете ресурсов, связанных с Faces. Метод getFieldLabel() на самом деле ищет ярлык в пакете ресурсов. Ниже шаги создания EL функции:

  1. Создать класс TagLibrary.
  2. Создать утилиту-класс Java.
  3. Зарегистрировать метод getFieldLabel().
  4. Зарегистрировать класс TagLibrary.
  5. Зарегистрировать дескриптор файла facelets-taglib.

Следующие разделы расскажут по порядку о каждом шаге и продемонстрируют, как использовать вашу новую EL функцию.

Шаг 1. Создание класса TagLibrary

EL функции и логические теги определены в Facelets в классе TagLibrary. Чтобы создать класс TagLibrary, нам надо расширить (subclass) 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 - это пример для  IBM developerWorks (c).
 * @author Rick Hightower from ArcMind Inc. http://www.arc-mind.com
 */
public final class JsfCoreLibrary extends AbstractTagLibrary {
    /** Пространство имен (Namespace) используется 
    для импорта этой библиотеки на страницы Facelets*/
    public static final String NAMESPACE = "http://www.arc-mind.com/jsf/core";

    /**  Текущее состояние библиотеки. */
    public static final JsfCoreLibrary INSTANCE = new JsfCoreLibrary();
    ...
    ...

Как только тег библиотеки определен, вы можете добавить к нему EL функции и логические теги.

Шаг 2. Создание утилиты-класса Java

Теперь надо создать Java утилиту-класс, в котором будет статический метод getFieldLabel(). Этот метод утилиты не имеет конкретной привязки к Facelets; это обычный статический метод в Java, как видно в листинге 3:

Листинг 3. Java утилита-класс с методом getFieldLabel()
package com.arcmind.jsfquickstart.tags;

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

import javax.faces.context.FacesContext;

/**
 * функции, помогающие разрабатывать JSF приложения.
 */
public final class JsfFunctions {
	/**
	 * Останавливает создание нового объекта JsfFunctions.
	 */
	private JsfFunctions() {
	}

	/**
	 * Получает ярлык поля.
	 *
	 * @param fieldName
	 *            fieldName
	 * @param formId
	 *            form id
	 * @return возвращает сообщение от 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());

		/** Ищет formId.fieldName, например,
		 EmployeeForm.firstName. */

		String label = null;
		try {
			label = bundle.getString(formId + fieldName);
			return label;
		} catch (MissingResourceException e) {
			//ничего не делает для наших целей.
		}

		try {
			/** Ищет  just fieldName, например, firstName. */
			label = bundle.getString(fieldName);
		} catch (MissingResourceException e) {
			/**
			 * Преобразовывает fieldName, например,
			  firstName автоматически станет 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;
	}

	/**
	 * Создает поле. Преобразует
	  firstName в First Name. Это будет 
	 * наше рациональное значение по умолчанию для ярлыка.
	 *
	 * @param fieldName
	 *            fieldName
	 *
	 * @return возвращает созданный ярлык.
	 */
	public static String generateLabelValue(final String fieldName) {
		StringBuffer buffer = new StringBuffer(fieldName.length() * 2);
		char[] chars = fieldName.toCharArray();

		/* Изменяет firstName на First Name. */
		for (int index = 0; index < chars.length; index++) {
			char cchar = chars[index];

			/* Переводит первый символ в верхний регистр. */
			if (index == 0) {
				cchar = Character.toUpperCase(cchar);
				buffer.append(cchar);

				continue;
			}

			/* Ищет символ в верхнем регистре, 
			когда находит, ставит перед ним пробел. */
			if (Character.isUpperCase(cchar)) {
				buffer.append(' ');
				buffer.append(cchar);

				continue;
			}

			buffer.append(cchar);
		}

		return buffer.toString();
	}
}

Метод getFieldLabel(), показанный в листинге 3, ищет поле в пакете ресурсов, связанных с JSF. Если метод не может найти поле, расщепляет и пишет каждое слово с заглавной буквы в стоке вида camel-case (в такой строке слова пишутся без пробела, каждое новое слово начинается с заглавной буквы), таким образом поле "firstName" станет "First Name". Это дает вам рациональное значение по умолчанию, которое вы еще можете установить в пакете ресурсов, если хотите переписать его.

Шаг 3. Регистрация метода getFieldLabel

Для класса AbstractTagLibrary, показанного в листинге 2 (JsfCoreLibrary), вам надо зарегистрировать метод 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, используя отражение. Затем все статические методы класса JsfFunction добавляются с помощью наследственного метода addFunction ().

Шаг 4. Регистрация класса TagLibrary

Итак мы определили EL функцию и содержащий ее класс TagLibrary. Теперь нам надо зарегистрировать новый класс TagLibrary, чтобы он стал виден для Facelets. Это можно сделать, объявив новый класс library (библиотеки) в новом файле facelets-taglib с именем jsf-core.taglib.xml, как показано в листинге 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 смог найти тег library, нам надо зарегистрировать его в файле WEB-INF/web.xml, как показано в листинге 6:

Листинг 6. Регистрация нового taglib с Facelets
	<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 к списку с точкой с запятой в качестве разделитея (semicolon-delimited), введеный в init-param facelets.LIBRARIES. Компоненты композиции идут в своем собственном файле taglib. EL функции и логические теги Facelets могут быть в одном файле taglib.

Использование EL функции в компоненте композиции

Когда вы определили EL функцию, разместили ее в дескрипторе файла taglib Facelets и зарегистрировали файловый дескриптор в web.xml, можно начинать использовать тег. В компоненте композиции поля можно использовать 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/функция s"  
      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>
       ...

Обратите внимание, что я импортирую тот "arc" библиотеки, определяя его пространство имен в HTML элементе xmlns:arc="http://www.arc-mind.com/jsf/core". Это пространство имен никак не совпадает с пространством имен, объявленном в классе TagLibrary (в данном случае JsfCoreLibrary), как показано ниже:

public final class JsfCoreLibrary extends AbstractTagLibrary {
    /** Пространство имен используется для импорта этой библиотеки на страницы Facelets*/
    public static final String NAMESPACE = "http://www.arc-mind.com/jsf/core";

Кажется, это огромный труд даже для пяти шагов. Однако, завершив эти основные установки, вы с легкостью сможете добавить новые логические теги и EL функции, не потратив столько времени. У вас есть рациональные значения по умолчанию для ярлыков всех полей. И если ваше приложение поддерживает только родные язык и размещение, тогда в большинстве случаев вам не понадобится добавлять входные данные в пакет ресурсов. Их придется добавить, только если вам захочется особых ярлыков или если вы будете использовать свое приложение с другими языками и/или размещениями (locations).

Facelets устранили мою "любимую мозоль" на счет интернационализации: мне никогда не приходилось писать много строк кода для чего-то определенного. И не придется! Что произойдет, если вы решите изменить поле тегов так, чтобы оно создавало булевы выражения, даты и текст автоматически.

Создание собственных логических тегов

Для начала надо наделить тег field.jsp некоторыми возможностями метапрограммирования. Создадим логический тег в Facelets, который сообщит вам, является ли тип определенного значения булевым (isBoolean), каким-либо текстом (isText) или датой (isDate). Теперь используем теги для перевода соответствующего компонента.

Листинг 8 показывает использование таких тегов (field.xhtml):

Листинг 8. field.xhtml
<!--  Инициализируем нужное нам значение -->
<arc:setValueBinding var="vb" valueBinding="#{entity[fieldName]}"/>

<!--  Если значение - строка, отображаем поле inputText. -->
<arc:isText id="vb">
    <h:inputText id="#{fieldId}"  value="#{entity[fieldName]}" 
                 required="${required}" styleClass="fieldInput">
          <ui:insert />
    </h:inputText>
</arc:isText>

<!--  Если значение - булево, отображаем поле  selectBooleanCheckbox. -->
<arc:isBoolean id="vb">
         <h:selectBooleanCheckbox id="#{fieldId}"
                  value="#{entity[fieldName]}" required="${required}"/>
</arc:isBoolean>

<!--  Если значение - дата, отображаем поле t:inputDate. -->
<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>

...

Код field.xhtml в листинге 8 возвращает inputText, если связанное значение - какой-либо вид текста, selectBooleanCheckbox, если связанное значение булево и возвращает компонент календаря Tomahawk, если значение - дата (см. "О Tomahawk").

Теперь нам надо создать связанное значение, чтобы получить тип, который будет границей конкретного компонента. Связанное значение содержит информацию о типе значения компонента, с которым граничит. Если вы работали с JSF, значит вы уже знакомы со связанными значениями. Например, #{Employee.firstName} - это связанное значение тега, которое скорее всего относится к типу строки, а #{Employee.age} - к целочисленному типу. В следующем разделе мы научимся создавать связанное значение тегов с помощью Facelets.

Создание связанного значения тега

Ниже представлены четыре этапа создания тега, который извлекает связанное значение в Facelets:

  1. Создать класс, расширяющий TagHandler.
  2. Зарегистрировать атрибуты в конструкторе.
  3. Переписать метод apply.
  4. Зарегистрировать тег в классе JsfCoreLibrary.

Следующие разделы объясняют каждый шаг, как построить связанные значения и как их применять.

Шаг 1. Создать класс, расширяющий TagHandler

Вы можете использовать TagHandler, относящийся к Facelets, чтобы ввести логику, которая будет решать, добавлять ли компоненты из тела тега к компонентам дерева. Помните, что конечная цель шаблонов Facelets - создание дерева из JSF компонент.

Листинг 9 показывает, как тег SetValueBindingHandler расширяет TagHandler. SetValueBindingHandler просто определяет переменную и помещает ее в область 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;


/**
 * Преобразовать связанное значение и тип, чтобы они стали доступными для других тегов.
 */
public final class SetValueBindingHandler extends TagHandler {

Шаг 2. Зарегистрировать атрибуты в конструкторе

Когда вы пишите теги в Facelets, важно понимать, что атрибуты определены в самим тегом, а не в XML файле. Конструктор SetValueBindingHandler определяет и регистрирует два атрибута (var и valueBinding) в конструкторе, как показано в листинге 10:

Листинг 10. Регистрация атрибутов в конструкторе
/**
 * Преобразовать связанное значение и тип, чтобы они стали доступными для других тегов.
 */
public final class SetValueBindingHandler extends TagHandler {
    /**  Имя новой переменной, которое определяет этот тег.*/
    private final TagAttribute var;

    /**   Текущее выражение для связанного значения. */
    private final TagAttribute valueBinding;

    /**
     * Конструктор. Устанавливаем атрибуты для этого тега.
     *
     * @param config TagConfig
     */
    public SetValueBindingHandler(final TagConfig config) {
        super(config);
        /* Определяем атрибуты var и  valueBinding. */
        this.var = this.getRequiredAttribute("var");
        this.valueBinding = this.getRequiredAttribute("valueBinding");
    }

Шаг 3. Переписать метод apply

Далее нам надо переписать метод apply в SetValueBindingHandler. Обычно мы переписываем этот метод, чтобы программно решать, добавлять ли компоненты, определенные в теле тега, к компонентам дерева. Однако, SetValueBindingHandler просто определяет переменную (например, var установлен на "vb") для связанного значения, которое было передано. В листинге показан код для переписывания метода apply:

Листинг 11. Переписывания метода apply
/**
 * Преобразовать связанное значение и тип, чтобы они стали доступными для других тегов.
 */
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) {
    	/* Создать ValueExpression из атрибута valueBinding. */
        ValueExpression valueExpression =
            this.valueBinding.getValueExpression(faceletsContext, Object.class);

        /* Получить имя нового значения. */
        String tvar = this.var.getValue(faceletsContext);
        Class type = valueExpression.getType(faceletsContext);

        /* Получаем связанное значение в FaceletsContext, откуда 
         * мы можем его извлечь из других компонентов.
         */
        faceletsContext.setAttribute(tvar, valueExpression);

        /* Сохраняет тип, чтобы нам не пришлось его искать
         * в каждом теге. */
        faceletsContext.setAttribute(tvar + "Type", type);
    }

Листинг 11 определяет две переменные: var и var + "Type". Затем мы просто помещаем переменные в faceletsContext (Более подробно об этом см. мои комментарии в листинге 11).

Шаг 4. Зарегистрировать тег в классе JsfCoreLibrary

Вы можете использовать ту же библиотеку JsfCoreLibrary для этого примера, как и для предыдущего. TagLibrary может содержать как EL функции, так и логические теги. Таким образом, на последнем шаге в этом упражнении мы регистрируем тег SetValueBindingHandler в JsfCoreLibrary, вызывая суперкласс addTagHandler, как показано в листинге 12:

Листинг 12. Регистрация тега в JsfCoreLibrary
/**
 * JsfCoreLibrary - пример для IBM developerWorks (c).
 * @author Rick Hightower from ArcMind Inc. http://www.arc-mind.com
 */
public final class JsfCoreLibrary extends AbstractTagLibrary {
    /** Пространство имен используется для импорта библиотеки на страницы  Facelets */
    public static final String NAMESPACE = "http://www.arc-mind.com/jsf/core";

    /**  Текущее состояние библиотеки */
    public static final JsfCoreLibrary INSTANCE = new JsfCoreLibrary();

    /**
     * Создать новый объект  JsfCoreLibrary.
     *
     */
    public JsfCoreLibrary() {
        super(NAMESPACE);

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

Что мне нравится в Facelets, так это то, что если вы сравните разработку тегов в Facelets с написанием таких же тегов в JSF, первое займет у вас намного меньший 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/функция s"  
      xmlns:t="http://myfaces.apache.org/tomahawk">

  THIS TEXT WILL BE REMOVED
  <ui:composition>
  ...
    <!--  Инициализация связанного значения -->
    <arc:setValueBinding var="vb" 
                         valueBinding="#{entity[fieldName]}" />

Вам ничего не надо ни импортировать, ни устанавливать в web.xml. Однажды определив тег библиотеки (как "arc"), вы можете добавлять теги и функции к нему, не обновляя какой-либо xml файл. Вы действительно можете избежать ада с XML!

Метапрограммирование в 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;


/**
 * Текущее поле булево.
 */
public abstract class IsTypeHandler extends TagHandler {
    /**   */
    private final TagAttribute id;

    /**
     * Создать тег.
     *
     * @param config TagConfig
     */
    public IsTypeHandler(final TagConfig config) {
        super(config);
        this.id = this.getRequiredAttribute("id");
    }

    /**
     * Текущее поле булево.
     *
     * @param faceletsContext ctx
     * @param aParent parent
     *
     * @throws IOException IOException
     */
    public void apply(final FaceletContext faceletsContext, final UIComponent aParent)
        throws IOException {
        /* Получить имя связанного значения. */
        String tid = this.id.getValue(faceletsContext);
        Class type = 
          (Class) faceletsContext.getVariableMapper().resolveVariable(tid + "Type")
                                                .getValue(faceletsContext);

        /* Если текущий тип булевый, обработать тело тега.
         */
        if (isType(type)) {
            this.nextHandler.apply(faceletsContext, aParent);
        }
    }

    /**
     *
     *
     * @param type type
     *
     * @return вернуть  true в случае правильного типа.
     */
    protected abstract boolean isType(Class type);
}

Затем вы добавляете тег для каждого типа, который хотите обработать. Я это реализовал в листинге 15:

Листинг 15. Обработка определенных типов
package com.arcmind.jsfquickstart.tags;

import com.sun.facelets.tag.TagConfig;


/**
 * Текущее поле булево. */
public final class IsBooleanHandler extends IsTypeHandler {

    /**
     * Создать тег.
     *
     * @param config TagConfig
     */
    public IsBooleanHandler(final TagConfig config) {
        super(config);
    }

    /**
     *
     *
     * @param type type
     *
     * @return вернуть  true в случае булева типа.
     */
    protected boolean isType(final Class type) {
        /* Если текущий тип булевый, обработать тело тега.
         */
        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;

/**
 * Текущее поле дата.
 */
public final class IsDateHandler extends IsTypeHandler {

    /**
     * Создать тег.
     *
     * @param config TagConfig
     */
    public IsDateHandler(final TagConfig config) {
        super(config);
    }

    /**
     *
     *
     * @param type type
     *
     * @return вернуть  true в случае булева типа.
     */
    protected boolean isType(final Class type) {
        /* Если текущее поле строка,  string, обработать тело тега.
         */
        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;

/**
 * Текущее поле строка.
 */
public final class IsTextHandler extends IsTypeHandler {

    /**
     * Создать тег.
     *
     * @param config TagConfig
     */
    public IsTextHandler(final TagConfig config) {
        super(config);
    }

    /**
     *
     *
     * @param type type
     *
     * @return вернуть  true в случае булева типа.
     */
    protected boolean isType(final Class type) {
        /* Если текущий тип строка, обработать тело тега.
         */
        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);
        ...

Использование тегов, основанных на типе

Теперь в вашей библиотеке тегов (taglibrary) на три тега больше: isBoolean, isText и isDate(). Вы можете их использовать, как показано в листинге 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/функция s"   
      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>

    <!-- Требуемый атрибут не обязателен, инициализируйте его как  
         true, если не нашли. -->
    <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>

    <!-- Инициализируйте  связанное значение-->
    <arc:setValueBinding var="vb" 
                         valueBinding="#{entity[fieldName]}" />

    <!-- Если связанное значение - строка, отобразится поле inputText (введите текст). -->
    <arc:isText id="vb">

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

    <!-- Если связанное значение - булево, отобразится поле
           selectBooleanCheckbox (выберите булеву переменную в окошке для галочки). -->
    <arc:isBoolean id="vb">
        <h:selectBooleanCheckbox  id="#{fieldName}" 
                                  value="#{entity[fieldName]}" 
                                  required="${required}" />
    </arc:isBoolean>

    <!--  Если связанное значение - дата, отобразится поле  inputDate (введите дату). -->
    <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>

    <!--  Если найдена ошибка, отобразится сообщение об этой ошибке -->
    <h:message id="${fieldName}Message" 
               style="color: red; text-decoration: overline" 
               for="#{fieldName}" />

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

</html>

Поле вашего тега отображает поле inputText (введите текст), если связанное значение относится к типу Integer, String, BigDecimal, Character, Long, Short, Byte, Float, Double, short, int, char и так далее. Поле тега будет в виде окошка для галочки, если связанное значение - булева переменная или выражение, а компонент календаря Tomahawk появится, если выражение типа java.util.Date - что по правде сказать, очень здорово.

В заключение

В этой статье я показал некоторые интересные и красивые мощные приложения на Facelets. EL функции намного облегчают труд интернационализации, а собственные логические теги позволяют вам расширять логические теги в Facelets. По пути мы посмотрели, как легко можно расширить простые примеры из предыдущей статьи с более продвинутыми функциональностями. Я показал, как обновить компонент композиции поля для работы с датой (календарь Tomahawk), булевыми переменными (окошки для галочки) и всех проявлений Java, которые могут быть представлены как текст. Мы также поработали с рациональными значениями по умолчанию, которые использовали для интернационализации ярлыка для компонента поля. Следите за новыми статьями о Facelets в следующем месяце!


Ресурсы для скачивания


Похожие темы


Комментарии

Войдите или зарегистрируйтесь для того чтобы оставлять комментарии или подписаться на них.

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Технология Java
ArticleID=170197
ArticleTitle=Расширенное программирование Facelets
publish-date=05092006