Создание Ajax-приложений с помощью JSF, CSS и JavaScript: Часть 1. Улучшение внешнего вида JSP-страниц

Использование стилей CSS в стандартных компонентах JSF

Как правило, в Web-приложениях используются каскадные таблицы стилей (CSS) и JavaScript в сочетании с некой серверной инфраструктурой, например, JavaServer Faces (JSF). С помощью CSS можно менять внешний вид Web-компонентов, в частности, компонентов Ajax, создавая приятное и запоминающееся оформление. В первой части данной серии мы расскажем об использовании атрибутов, позволяющих применять стили CSS к стандартным компонентам JSF. Кроме того, вы узнаете, как создавать собственные компоненты JSF, способные задавать стили по умолчанию для всех дочерних компонентов, обеспечивая тем самым согласованность интерфейса всего Web-приложения. Подобный подход применим и для программного управления атрибутами компонентов, речь о котором пойдет в следующий статье серии. Она будет посвящена вопросам улучшения функциональности форм JSF с помощью JavaScript.

Андрей Чиорояну, cтарший Java-разработчик и консультант, Devsphere

Андрей Чиорояну (Andrei Cioroianu) является основателем компании Devsphere, специализирующейся на консалтинговых услугах в сфере разработок Java EE и Ajax/JSF. Он работает с Java и Web-технологиями начиная с 1997 г. и имеет более чем десятилетний опыт решения сложных технических задач и управления полным жизненным циклом коммерческих продуктов, специализированных приложений и инфраструктур с открытым кодом. С Андреем можно связаться, заполнив контактную форму на сайте devsphere.com.



28.08.2008

Использование атрибутов style в компонентах JSF

Практически все JSF-компоненты, имеющие представление в HTML, имеют два необязательных атрибута: style и styleClass, которые преобразуются в атрибуты style и class при генерировании HTML. Вдобавок к этому некоторые компоненты, например, <h:dataTable> и <h:panelGrid>, предоставляют дополнительные атрибуты для своих граней (facets). В этом разделе мы поговорим об атрибутах, имеющих отношение к CSS, содержащихся в JSF-компонентах из библиотеки HTML.

Связывание файлов CSS со страницами JSF

Если стили используются только на одной Web-странице, то их можно описывать с помощью элемента <style> в заголовке страницы. Аналогичным образом можно задать стили с помощью атрибута style для отдельно взятого компонента JSF. Однако в большинстве случаев стили лучше описывать в отдельных файлах CSS, чтобы их можно было использовать на нескольких страницах. Подобные внешние таблицы стилей можно подключать к странице с помощью тега <link>, как показано в листинге 1.

Листинг 1. Использование тега <link>
<link rel="stylesheet" type="text/css"
      href="${pageContext.request.contextPath}/MyStyles.css">
<link rel="stylesheet" type="text/css"
      href="<%=request.getContextPath()%>/styles/MoreStyles.css">

Использование библиотеки MyFaces Tomahawk

Если вы предпочитаете использовать компоненты JSF вместо тегов HTML, обратите внимание на элемент <t:stylesheet> из библиотеки MyFaces Tomahawk. Он преобразуется в тег <link> при генерировании HTML-кода страницы.

В листинге 1 в атрибутах href содержатся абсолютные URI. Можно указывать и относительные пути к файлам CSS, но если URL JSF-страниц содержат префикс /faces/, то лучше включать контекст сервлета с помощью выражения ${pageContext.request.contextPath} или <%=request.getContextPath()%>. Иначе обращение по относительному пути к любому ресурсу (файлу CSS, изображению и т.д.) будет происходить через HTTP-запрос по адресу, включающему префикс /faces/. В результате даже запросы к файлам, не относящимся к JSF, будут обслуживаться сервлетом Faces, что приведет к потере производительности. Проблему можно решить, обращаясь к страницам JSF через суффикс .faces вместо префикса /faces/.

Определение и использование стилевых правил CSS

В файлах CSS определяются правила применения стилей к элементам HTML как генерируемым компонентами JSF, так и заданным непосредственно на странице, между JSF-компонентами. Например, если нужно подчеркивать ссылки только в момент наведения курсора мыши, можно применить следующее правило (листинг 2):

Листинг 2. Правила стилей для ссылок
a { text-decoration: none; }
a:hover { text-decoration: underline; }

Правила будут применяться ко всем ссылкам независимо от того, задавался ли элемент <a> напрямую или был сгенерирован неким компонентом JSF, например, <h:commandLink> (листинг 3).

Листинг 3. Ссылки в виде тега HTML и компонента JSF
<a href="LinkStyles.faces">HTML Link</a>
<h:commandLink value="JSF Link"/>

В листинге 4 показано, как можно определять стили непосредственно внутри компонента JSF.

Листинг 4. Использование атрибута style в компоненте JSF
<h:commandLink value="Red Link" style="color: red"/>

Разумеется, в большинстве случаев удобнее определять классы стилей в файле CSS, как показано в листинге 5. В этом случае одни и те же стили можно использовать в нескольких компонентах.

Листинг 5. Пример класса стиля
.GreenClass { color: green; }

Для указания имени класса CSS-стиля компоненты JSF предоставляют атрибут styleClass (листинг 6).

Листинг 6. Использование атрибута styleClass в компоненте JSF
<h:commandLink value="Green Link" styleClass="GreenClass"/>

Примеры, приведенные в листингах 1-6, можно найти в файлах LinkStyles.jsp и LinkStyles.css , включенных в исходный код к данной статье (см. раздел Загрузка).

Компоненты JSF, использующие несколько классов стилей

Как упоминалось ранее, некоторые компоненты JSF содержат несколько атрибутов для управления стилями. Например, элементы <h:message> и <h:messages> поддерживают следующий набор атрибутов, относящихся к CSS:

  • style
  • styleClass
  • errorClass
  • errorStyle
  • fatalClass
  • fatalStyle
  • infoClass
  • infoStyle
  • warnClass
  • warnStyle

Для оформления сообщения используются ровно два атрибута из перечисленных. Они выбираются в зависимости от уровня серьезности сообщения. Кроме того, компоненты-наследники UISelectOne и UISelectMany содержат атрибуты enabledClass и disabledClass для каждого из элементов списка. Компоненты <h:dataTable> и <h:panelGrid> поддерживают разные классы стилей для оформления самой таблицы, ее заголовка, нижней части, а также строк и столбцов. Ниже приведены примеры, демонстрирующие использование атрибутов CSS для вывода таблицы, показанной на рисунке 1.

Рисунок 1. Внешний вид TableStyles.jsp
Table styles example

Прежде всего для вывода компонента-таблицы необходима модель. В данном примере в качестве модели используется класс OrderBean, наследующий javax.faces.model.ArrayDataModel (листинг 7). В реальном приложении данные модели, скорее всего, брались бы из базы данных, но для простоты можно использовать обыкновенный массив.

Листинг 7. Класс OrderBean
package jsfcssjs;

import javax.faces.model.ArrayDataModel;

public class OrderBean extends ArrayDataModel {
    private static ItemBean items[] = new ItemBean[] {
        new ItemBean("Domain Name",        3,   7.99f),
        new ItemBean("SSL Certificate",    1, 119.00f),
        new ItemBean("Web Hosting",        1,  19.95f),
        new ItemBean("Email Box",         20,   0.15f),
        new ItemBean("E-Commerce Setup",   1,  25.00f),
        new ItemBean("Technical Support",  1,  50.00f)
    };

    public OrderBean() {
        super(items);
    }
    
    public float getTotalPrice() {
        float total = 0;
        for (int i = 0; i < items.length; i++)
            total += items[i].getItemPrice();
        return total;
    }
    
}

Класс ItemBean (код приведен в листинге 8) содержит три свойства, доступных для чтения и записи: description, quantity и unitPrice, а также одно свойство itemPrice, доступное только для чтения.

Листинг 8. Класс ItemBean
package jsfcssjs;

public class ItemBean implements java.io.Serializable {
    private String description;
    private int quantity;
    private float unitPrice;
    
    public ItemBean() {
    }
    
    public ItemBean(String description, int quantity, float unitPrice) {
        this.description = description;
        this.quantity = quantity;
        this.unitPrice = unitPrice;
    }
    
    ...

    public float getItemPrice() {
        return unitPrice * quantity;
    }
    
}

Стили, используемые для оформления таблицы, содержатся в файле TableStyles.css, показанном в листинге 9.

Листинг 9. Содержимое файла TableStyles.css
body     { font-family: Arial; }
.tablebg { background-color: #D0D0A0; }
.header  { font-weight: bold; }
.footer  { font-weight: bold; }
.text    { text-align: left; }
.number  { text-align: right; }
.graybg  { background-color: #DDDDDD; }
.whitebg { background-color: #FFFFFF; }

В листинге 10 приведена страница TableStyles.jsp, содержащая один JSF-компонент, использующий стили из файла TableStyles.css. Значение атрибута styleClass используется в качестве стиля для тега <table>, в то время как атрибуты headerClass и footerClass определяют стили для верхней (заголовочной) и нижней ячеек таблицы. В атрибуте columnClasses содержится список классов, разделенный запятыми. Эти стили применяются к элементам <td>, в которых находятся данные.

Список классов стилей, применяемых к строкам таблицы (элементы <tr>), задается с помощью атрибута rowClasses. В примере TableStyles.jsp таблица содержит шесть строк, но всего два стиля. Рендерер, генерирующий HTML-код для компонента <h:dataTable>, будет использовать классы graybg и whitebg поочередно, для нечетных и четных строк соответственно. С помощью атрибута var объявляется переменная row, через которую можно обращаться к объекту данных, соответствующему текущей строке таблицы. Изначально все данные содержатся в модели – объекте с именем orderBean, который связывается с таблицей через атрибут value.

Листинг 10. Код страницы TableStyles.jsp
<%@ taglib prefix="f" uri="http://java.sun.com/jsf/core" %>
<%@ taglib prefix="h" uri="http://java.sun.com/jsf/html" %>

<f:view>
<html>
<head>
    <title>Order Table</title>
    <link rel="stylesheet" type="text/css"
        href="<%=request.getContextPath()%>/styles/TableStyles.css">
</head>
<body>
    <h1>Order Table</h1>
    <h:dataTable id="table" var="row" value="#{orderBean}"
            styleClass="tablebg" headerClass="header" footerClass="footer"
            columnClasses="text,number,number,number"
            rowClasses="graybg,whitebg" border="0" cellpadding="5">
        <h:column>
            <f:facet name="header">
                <h:outputText value="Description"/>
            </f:facet>
            <h:outputText value="#{row.description}"/>
            <f:facet name="footer">
                <h:outputText value="Total Price"/>
            </f:facet>
        </h:column>
        <h:column>
            <f:facet name="header">
                <h:outputText value="Quantity"/>
            </f:facet>
            <h:outputText value="#{row.quantity}"/>
        </h:column>
        <h:column>
            <f:facet name="header">
                <h:outputText value="Unit Price"/>
            </f:facet>
            <h:outputText value="#{row.unitPrice}">
                <f:convertNumber type="currency" currencyCode="USD"
                    minFractionDigits="2" maxFractionDigits="2"/>
            </h:outputText>
        </h:column>
        <h:column footerClass="footer number">
            <f:facet name="header">
                <h:outputText value="Item Price"/>
            </f:facet>
            <h:outputText value="#{row.itemPrice}">
                <f:convertNumber type="currency" currencyCode="USD"
                    minFractionDigits="2" maxFractionDigits="2"/>
            </h:outputText>
            <f:facet name="footer">
                <h:outputText value="#{orderBean.totalPrice}">
                    <f:convertNumber type="currency" currencyCode="USD"
                        minFractionDigits="2" maxFractionDigits="2"/>
                </h:outputText>
            </f:facet>
        </h:column>
    </h:dataTable>
</body>
</html>
</f:view>

HTML-код, генерируемый при выводе страницы TableStyles.jsp, приведен в листинге 11:

Листинг 11. HTML-код, генерируемый страницей TableStyles.jsp
<html>
<head>
    <title>Order Table</title>
    <link rel="stylesheet" type="text/css"
        href="/jsf12css/styles/TableStyles.css">
</head>
<body>
    <h1>Order Table</h1>

<table id="table" class="tablebg" border="0" cellpadding="5">
<thead>
<tr>
<th class="header" scope="col">Description</th>
<th class="header" scope="col">Quantity</th>
<th class="header" scope="col">Unit Price</th>
<th class="header" scope="col">Item Price</th>
</tr>
</thead>
<tfoot>
<tr>
<td class="footer">Total Price</td>
<td class="footer"></td>
<td class="footer"></td>
<td class="footer number">$240.92</td>
</tr>
</tfoot>
<tbody>
<tr class="graybg">
<td class="text">Domain Name</td>
<td class="number">3</td>
<td class="number">$7.99</td>
<td class="number">$23.97</td>
</tr>
<tr class="whitebg">
<td class="text">SSL Certificate</td>
<td class="number">1</td>
<td class="number">$119.00</td>
<td class="number">$119.00</td>
</tr>
<tr class="graybg">
...
</tr>
<tr class="whitebg">
...
</tr>
<tr class="graybg">
...
</tr>
<tr class="whitebg">
...
</tr>
</tbody>
</table>

</body>
</html>

В атрибуте footerClass последнего компонента <h:column> таблицы на странице TableStyles.jsp указываются два класса для нижней ячейки, в которой содержится общая сумма заказа – footer и number. Этот атрибут появился в компоненте <h:column> только в JSF 1.2. В более ранних версиях приходилось помещать компонент <h:outputText>, выводящий сумму, внутрь элемента <div>, чтобы выровнять значение по правому краю (см. листинг 12). Теперь это делается с помощью класса number. В исходный код к данной статье включены примеры как для JSF 1.2, так и для JSF 1.1.

Листинг 12. Версия страницы TableStyles.jsp, совместимая с JSF 1.1
<%@ taglib prefix="f" uri="http://java.sun.com/jsf/core" %>
<%@ taglib prefix="h" uri="http://java.sun.com/jsf/html" %>

<f:view>
    ...
    <h:dataTable ...>
        ...
        <h:column>
            ...
            <f:facet name="footer">
                <h:panelGroup>
                    <f:verbatim><div class="number"></f:verbatim>
                    <h:outputText value="#{orderBean.totalPrice}">
                        <f:convertNumber type="currency" currencyCode="USD"
                            minFractionDigits="2" maxFractionDigits="2"/>
                    </h:outputText>
                    <f:verbatim></div></f:verbatim>
                </h:panelGroup>
            </f:facet>
        </h:column>
    </h:dataTable>
    ...
</f:view>

Установка стилей по умолчанию для компонентов JSF

Если приложение содержит множество страниц JSF, то устанавливать значения атрибута styleClass для каждого компонента вручную становится утомительно. Несмотря на то, что стандартные HTML-компоненты JSF не предоставляют стилей по умолчанию, их значения можно установить программно, перебрав все элементы дерева компонентов JSF. В данном разделе мы расскажем, как это сделать, объяснив попутно, как создаются деревья компонентов и как генерируется HTML-содержимое страницы.

Процесс создания деревьев JSF-компонентов

Все запросы к ресурсам JSF обрабатываются сервлетом Faces, который конфигурируется в файле web.xml – дескрипторе любого Web-приложения на JSF. При поступлении каждого запроса этот сервлет инициализирует объект javax.faces.context.FacesContext и вызывает методы execute() и render() экземпляра класса javax.faces.lifecycle.Lifecycle. Метод execute() ответственен за выполнение всех фаз цикла обработки запросов в JSF за исключением последней – фазы вывода результата (Render Response), за которую отвечает метод render().

Способы хранения состояния компонентов

Деревья компонентов могут храниться как на серверной стороне, так и на клиентской. Это регулируется параметром контекста javax.faces.STATE_SAVING_METHOD, который задается в файле web.xml. На сервере компоненты хранятся в области видимости сессии (session scope). Это достаточно эффективный подход, но, к сожалению, могут возникать проблемы, если при нажатии на кнопку "Back" в браузере происходит возврат к форме, данные которой были ранее отправлены на сервер. Поэтому лучше хранить состояния компонентов на стороне клиента. В этом случае используются скрытые переменные форм, содержащие сериализованные копии элементов дерева, соответствующего данной странице. Этот метод значительно надежнее использования сессионных переменных на стороне сервера, хотя он и приводит к увеличению трафика, а также требует дополнительных ресурсов процессора для сериализации и восстановления деревьев.

В первой фазе, называемой “восстановление представления” (Restore View), JSF обращается к обработчику представления – экземпляру класса javax.faces.application.ViewHandler – и вызывает его метод restoreView(), если происходит обработка postback-запроса. Примером такого запроса может служить нажатие на кнопку Submit формы. В этом случае дерево компонентов уже должно было быть создано на этапе обработки предыдущего запроса, обработанного сервлетом Faces (см. заметку о способах хранения состояния компонентов).

Если это не postback-запрос (например, если пользователь просто обратился по URL с префиксом /faces/), то JSF вызовет метод renderResponse() объекта FacesContext и метод createView() объекта ViewHandler. Вызов renderResponse() инициирует переход из текущей фазы, которой по-прежнему является “восстановление представления”, непосредственно к выводу результата. Вызов createView() стандартного обработчика представления приведет только к созданию компонента типа javax.faces.component.UIViewRoot. Остальные узлы дерева будут созданы на этапе вывода результата, когда JSF вызовет метод renderView() обработчика представления.

Вне зависимости от того, потребовалось ли создание дерева компонентов или оно было восстановлено из предыдущего состояния, в какой-то момент JSF передает обработку запроса соответствующей JSF-странице. Это происходит внутри метода renderView(). Например, если URI запроса был /MyApp/MyPage.faces или /MyApp/faces/MyPage.jsp, то будет вызвана страница по адресу /MyApp/MyPage.jsp.

В любой странице JSF содержатся теги, описывающие компоненты, например <f:view> и <h:form>. Каждому тегу соответствует свой обработчик – Java™-класс, реализующий интерфейсы из пакета javax.servlet.jsp.tagext. В процессе генерации HTML-кода JSP-контейнер создает экземпляры обработчиков и вызывает их методы. В случае компонентов JSF 1.2 все обработчики является наследниками класса javax.faces.webapp.UIComponentELTag, в отличие от JSF 1.1, где родительским классом являлся javax.faces.webapp.UIComponentTag или javax.faces.webapp.UIComponentBodyTag. Но, независимо от версии JSF, обработчики тегов инстанциируют компоненты на этапе вывода результата, и только в случае, если дерево компонентов не было восстановлено в первой фазе обработки запроса.

Может возникнуть соблазн создать класс, реализующий интерфейс javax.faces.event.PhaseListener, который занимался бы установкой стилей по умолчанию. К сожалению, этот подход не будет работать, если пользователь обратился к странице, нажав на ссылку или просто набрав URL в адресной строке браузера. В этом случае JSF не сможет восстановить дерево компонентов, которое должно быть создано на этапе Render Response. Таким образом, обработка проходит две фазы, но после первой дерева компонентов еще не существует, а после последней изменять стили компонентов уже поздно, потому что весь HTML-код страницы уже сгенерирован. Поэтому устанавливать стили необходимо в ходе этапа вывода результата, перед генерацией кода компонентов.

Обход дерева компонентов перед генерацией HTML

У компонентов JSF существует интересное свойство, доступное только для чтения, - rendersChildren. Если оно установлено в значение false, то в процессе отрисовки компонента будут вызваны только методы encodeBegin() и encodeEnd(). В противном случае будет также вызван метод encodeChildren(). Хотя компоненты JSF могут самостоятельно заниматься генерацией HTML-кода, эти методы, как правило, делегируют всю работу внешнему рендереру – классу, реализующему интерфейс javax.faces.render.Renderer.

В JSF 1.1 методы, генерирующие HTML, изначально вызываются обработчиками тегов JSP. В случае если компонент сам отвечает за отрисовку дочерних элементов (метод getRendersChildren() возвращает true), обработчики вложенных тегов эти методы не вызывают, полагаясь на родительский компонент. Процесс генерации HTML в JSF 1.1 был весьма эффективным благодаря минимальной буферизации, но при этом разработчикам приходилось включать HTML-код внутрь компонентов <f:verbatim>. В JSF 1.2 это больше не требуется, потому что генерация HTML происходит только после того, как дерево компонентов полностью создано.

И в JSF 1.1, и в JSF 1.2 все вложенные компоненты оказываются полностью проинициализированы к моменту вызова метода encodeChildren(). Поэтому именно в этот момент можно установить стили по умолчанию. Далее мы создадим новый компонент, который будет рекурсивно обходить все свои дочерние компоненты, устанавливая у них значения атрибута styleClass.

Этот подход можно использовать для изменения любых атрибутов и свойств JSF-компонентов, а не только стилей по умолчанию. Поэтому имеет смысл создать общий базовый класс, пригодный для многократного использования в разных ситуациях. Назовем его SetupComponent и сделаем наследником класса UIComponentBase. Кроме того, определим метод setup(), который будет вызываться для каждого дочернего компонента, HTML-представление которого необходимо сгенерировать. Рекурсивный обход дерева будет осуществляться в методе setupTree(), показанном в листинге 13.

Листинг 13. Методы setup() и setupTree() класса SetupComponent
package jsfcssjs;

import javax.faces.component.UIComponent;
import javax.faces.component.UIComponentBase;
import javax.faces.context.FacesContext;
...
public abstract class SetupComponent extends UIComponentBase {

    protected abstract void setup(FacesContext ctx, UIComponent comp);

    protected void setupTree(FacesContext ctx, UIComponent comp) {
        if (!comp.isRendered())
            return;
        setup(ctx, comp);
        for (UIComponent child : comp.getChildren())
            setupTree(ctx, child);
    }
    ...
}

Этот класс перегружает метод getRendersChildren(), унаследованный от класса UIComponentBase (листинг 14). Он возвращает true, что означает, что компонент будет сам управлять генерацией HTML-кода дочерних компонентов.

Листинг 14. Метод getRendersChildren() класса SetupComponent
public abstract class SetupComponent extends UIComponentBase {
    ...
    public boolean getRendersChildren() {
        return true;
    }
    ...
}

В листинге 15 показан перегруженный метод encodeChildren(), который вставляет вызов setupTree() перед вызовом encodeTree() для каждого дочернего компонента.

Листинг 15. Метод encodeChildren() класса SetupComponent
public abstract class SetupComponent extends UIComponentBase {
    ...
    public void encodeChildren(FacesContext ctx)
            throws IOException {
        if (!isRendered())
            return;
        setupTree(ctx, this);
        for (UIComponent child : getChildren())
            encodeTree(ctx, child);
    }
    ...
}

Метод encodeTree(), приведенный в листинге 16, вызывает методы для генерации кода текущего компонента в дереве. Кроме того, если свойство rendersChildren текущего компонента содержит false, то будут рекурсивно вызваны методы генерации кода для его дочерних компонентов.

Листинг 16. Метод encodeTree() класса SetupComponent
public abstract class SetupComponent extends UIComponentBase {
    ...
    protected void encodeTree(FacesContext ctx, UIComponent comp)
            throws IOException {
        if (!comp.isRendered())
            return;
        comp.encodeBegin(ctx);
        if (comp.getRendersChildren())
            comp.encodeChildren(ctx);
        else
            for (UIComponent child : comp.getChildren())
                encodeTree(ctx, child);
        comp.encodeEnd(ctx);
    }

}

Создание и конфигурирование собственного компонента JS

В листинге 17 показан класс DefaultStylesComponent, который, являясь наследником SetupComponent, переопределяет метод setup(). Этот метод устанавливает атрибут styleClass, формируя имя класса CSS из имени семейства компонентов (свойство family) и типа рендерера.

Листинг 17. Класс DefaultStylesComponent
package jsfcssjs;

import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;

import java.util.Map;

public class DefaultStylesComponent extends SetupComponent {

    protected void setup(FacesContext ctx, UIComponent comp) {
        Map<String, Object> attrMap = comp.getAttributes();
        if (attrMap.get("styleClass") != null)
            return;
        String familyName = getLastName(comp.getFamily());
        String rendererName = getLastName(comp.getRendererType());
        if (familyName == null || rendererName == null)
            return;
        String className = "Default" + familyName;
        if (!familyName.equals(rendererName))
            className += rendererName;
        attrMap.put("styleClass", className);
    }
    
    protected String getLastName(String fullName) {
        if (fullName == null)
            return null;
        int dotIndex = fullName.lastIndexOf('.');
        if (dotIndex != -1)
            return fullName.substring(dotIndex+1);
        else
            return fullName;
    }

    public String getFamily() {
        return "DefaultStyles";
    }

}

Класс DefaultStylesComponent конфигурируется в файле faces-config.xml, как показано в листинге 18.

Листинг 18. Конфигурирование класса DefaultStylesComponent в файле faces-config.xml
<faces-config ...>
    ...
    <component>
        <component-type>DefaultStylesComponent</component-type>
        <component-class>jsfcssjs.DefaultStylesComponent</component-class>
        <component-extension>
            <component-family>DefaultStyles</component-family>
        </component-extension>
    </component>

</faces-config>

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

В листинге 19 показан класс DefaultStylesTag, который расширяет стандартный класс UIComponentELTag из JSF 1.2 API и переопределяет его методы getComponentType() и getRendererType(), которые возвращают DefaultStylesComponent и null соответственно. В случае использования JSF 1.1 классы тегов должны наследовать UIComponentTag, а не UIComponentELTag.

Листинг 19. Класс DefaultStylesTag
package jsfcssjs;

import javax.faces.webapp.UIComponentELTag;

public class DefaultStylesTag extends UIComponentELTag {

    public String getComponentType() {
        return "DefaultStylesComponent";
    }

    public String getRendererType() {
        return null;
    }

}

В файле css.tld, приведенном в листинге 20, определяются имя (defaultStyles) и атрибуты нового тега. Атрибуты id, binding и rendered унаследованы от родительского класса UIComponentELTag.

Листинг 20. Файл css.tld в случае JSF 1.2 и JSP 2.1
<?xml version="1.0" encoding="UTF-8" ?>
<taglib xmlns="http://java.sun.com/xml/ns/javaee" ... version="2.1">
    <tlib-version>1.0</tlib-version>
    <short-name>css</short-name>
    <uri>/css.tld</uri>
    <tag>
        <name>defaultStyles</name>
        <tag-class>jsfcssjs.DefaultStylesTag</tag-class>
        <body-content>JSP</body-content>
        <attribute>
            <name>id</name>
            <required>false</required>
            <rtexprvalue>true</rtexprvalue>
        </attribute>
        <attribute>
            <name>binding</name>
            <required>false</required>
            <deferred-value>
                <type>jsfcssjs.DefaultStylesComponent</type>
            </deferred-value>
        </attribute>
        <attribute>
            <name>rendered</name>
            <required>false</required>
            <deferred-value>
                <type>boolean</type>
            </deferred-value>
        </attribute>
    </tag>
</taglib>

В JSF 1.1 синтаксис файлов TLD должен соответствовать спецификации JSP 1.2 (листинг 21).

Листинг 21. Файл css.tld в случае JSF 1.1 и JSP 1.2
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE taglib
    PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.2//EN"
    "http://java.sun.com/dtd/web-jsptaglibrary_1_2.dtd">
<taglib>
    <tlib-version>1.0</tlib-version>
    <jsp-version>1.2</jsp-version>
    <short-name>css</short-name>
    <uri>/css.tld</uri>
    <tag>
        <name>defaultStyles</name>
        <tag-class>jsfcssjs.DefaultStylesTag</tag-class>
        <body-content>JSP</body-content>
        <attribute>
            <name>id</name>
            <required>false</required>
            <rtexprvalue>false</rtexprvalue>
        </attribute>
        <attribute>
            <name>binding</name>
            <required>false</required>
            <rtexprvalue>false</rtexprvalue>
        </attribute>
        <attribute>
            <name>rendered</name>
            <required>false</required>
            <rtexprvalue>false</rtexprvalue>
        </attribute>
    </tag>
</taglib>

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

Центр материалов по Ajax на сайте developerWorks
Обратитесь к центру материалов по Ajax – единому собранию бесплатных утилит, кода и информации о разработке приложений Ajax. На активно действующем форуме сообщества Ajax, модерируемом экспертом Джеком Херрингтоном (Jack Herrington), вы сможете общаться с коллегами, возможно, знающими решение проблемы, над которой вы ломаете голову в данный момент.

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

Рисунок 2. Внешний вид страницы DefaultStyles.jsp
default styles example

В листинге 22 приведен файл DefaultStyles.css, в котором определяются несколько классов стилей, далее используемых на странице DefaultStyles.jsp.

Листинг 22. Содержимое файла DefaultStyles.css
.DefaultForm {}
.DefaultPanelGrid { background-color: #FFFFFF; }
.DefaultInputText { background-color: #FFDDDD;
    border-style: ridge; border-width: thin; }
.DefaultInputTextarea { background-color: #DDFFDD;
    border-style: groove; border-width: thick; }
.DefaultSelectOneMenu { background-color: #DDDDFF;
    border-style: inset; border-width: medium; }
.DefaultCommandButton { background-color: #DDDDDD;
    border-style: outset; border-width: medium; }
.SpecialInputText { background-color: #DDFFFF;
    border-style: double; border-width: thick; }
.EnabledOption { color: #FF0000; }
.DisabledOption { color: #0000FF; }

Страница DefaultStyles.jsp, показанная в листинге 23, содержит компонент <css:defaultStyles>, который устанавливает стили по умолчанию для JSF-компонентов, не имеющих собственных атрибутов styleClass. Для одного из компонентов используется CSS-класс SpecialInputText, а к элементам выпадающего списка применяются стили EnabledOption и DisabledOption.

Листинг 23. Страница DefaultStyles.jsp
<%@ taglib prefix="f" uri="http://java.sun.com/jsf/core" %>
<%@ taglib prefix="h" uri="http://java.sun.com/jsf/html" %>
<%@ taglib prefix="css" uri="/css.tld" %>

<f:view>
<html>
<head>
    <title>Default Styles</title>
    <link rel="stylesheet" type="text/css"
        href="<%=request.getContextPath()%>/styles/DefaultStyles.css">
</head>
<body>
    <h1>Default Styles</h1>
    <css:defaultStyles>
        <h:form id="form">
            <h:panelGrid columns="1" border="0" cellspacing="5">
                <h:inputText id="text" size="30" value="default text style"/>
                <h:inputText id="special" size="30" value="special text style"
                    styleClass="SpecialInputText"/>
                <h:selectOneMenu id="menu" enabledClass="EnabledOption"
                        disabledClass="DisabledOption">
                    <f:selectItem itemValue="First" itemLabel="First"/>
                    <f:selectItem itemValue="Second" itemLabel="Second"/>
                    <f:selectItem itemValue="Third" itemLabel="Third"
                        itemDisabled="true"/>
                </h:selectOneMenu>
                <h:inputTextarea id="area" rows="5" cols="30"
                    value="default text area style"/>
                <h:commandButton id="button" value="Submit"/>
            </h:panelGrid>
        </h:form>
    </css:defaultStyles>
</body>
</html>
</f:view>

Наконец, в листинге 24 приведен HTML-код, сгенерированный при выводе страницы DefaultStyles.jsp. Как видите, все элементы формы содержат атрибут class. Те имена классов CSS, которые начинаются с префикса Default, были установлены компонентом <css:defaultStyles>.

Листинг 24. HTML-код, сгенерированный страницей DefaultStyles.jsp
<html>
<head>
    <title>Default Styles</title>
    <link rel="stylesheet" type="text/css"
        href="/jsf12css/styles/DefaultStyles.css">
</head>
<body>
    <h1>Default Styles</h1>
    
<form id="form" ... class="DefaultForm" ...>
<input type="hidden" name="form" value="form" />
<table class="DefaultPanelGrid" border="0" cellspacing="5">
<tbody>
<tr>
<td><input id="form:text" type="text" ... 
    class="DefaultInputText" .../></td>
</tr>
<tr>
<td><input id="form:special" type="text" ... 
    class="SpecialInputText" .../></td>
</tr>
<tr>
<td><select id="form:menu" ... class="DefaultSelectOneMenu" ...>
    <option value="First" class="EnabledOption">First</option>
    <option value="Second" class="EnabledOption">Second</option>
    <option value="Third" disabled="disabled" 
        class="DisabledOption">Third</option>
</select></td>
</tr>
<tr>
<td><textarea id="form:area" ... 
    class="DefaultInputTextarea" ...> ... </textarea></td>
</tr>
<tr>
<td><input id="form:button" type="submit" ... 
    class="DefaultCommandButton" /></td>
</tr>
</tbody>
</table>
<input type="hidden" ... id="javax.faces.ViewState" value="..." />
</form>

</body>
</html>

Заключение

В данной статье рассматривалась поддержка CSS в стандартных компонентах JSF. Наиболее простые из них содержат два необязательных атрибута: style и styleClass. Более сложные компоненты, такие как сетки и таблицы данных, предоставляют дополнительные атрибуты, содержащие стили CSS для своих граней и элементов. В этой части серии также объяснялось, как JSF создает деревья компонентов и каким образом можно перебрать компоненты в дереве непосредственно перед началом генерации HTML-кода. Благодаря этой возможности можно изменить представление компонентов, например, установить стили по умолчанию. Оставайтесь с нами, и в следующей части серии мы расскажем, как можно еще улучшить функциональность форм JSF с помощью JavaScript.


Загрузка

ОписаниеИмяРазмер
Примеры исходного кода к статьеjsfcssjs_part1_src.zip25KБ

Ресурсы

Научиться

Получить продукты и технологии

  • Скачайте JSF RI вместе с исходным кодом с сайта javaserverfaces.dev.java.net. (EN)
  • Попробуйте в деле проект Apache MyFaces – одну из популярных реализаций JSF. (EN)

Обсудить

Комментарии

developerWorks: Войти

Обязательные поля отмечены звездочкой (*).


Нужен IBM ID?
Забыли Ваш IBM ID?


Забыли Ваш пароль?
Изменить пароль

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Профиль создается, когда вы первый раз заходите в developerWorks. Информация в вашем профиле (имя, страна / регион, название компании) отображается для всех пользователей и будет сопровождать любой опубликованный вами контент пока вы специально не укажите скрыть название вашей компании. Вы можете обновить ваш IBM аккаунт в любое время.

Вся введенная информация защищена.

Выберите имя, которое будет отображаться на экране



При первом входе в developerWorks для Вас будет создан профиль и Вам нужно будет выбрать Отображаемое имя. Оно будет выводиться рядом с контентом, опубликованным Вами в developerWorks.

Отображаемое имя должно иметь длину от 3 символов до 31 символа. Ваше Имя в системе должно быть уникальным. В качестве имени по соображениям приватности нельзя использовать контактный e-mail.

Обязательные поля отмечены звездочкой (*).

(Отображаемое имя должно иметь длину от 3 символов до 31 символа.)

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Вся введенная информация защищена.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Web-архитектура, Технология Java
ArticleID=333411
ArticleTitle=Создание Ajax-приложений с помощью JSF, CSS и JavaScript: Часть 1. Улучшение внешнего вида JSP-страниц
publish-date=08282008