JSF 2 fu: Часть 2. Шаблоны и составные компоненты

Создаем расширяемые пользовательские интерфейсы с помощью JavaServer Faces 2

Java™Server Faces (JSF) 2 позволяет создавать пользовательские интерфейсы, которые легко изменять и расширять с помощью двух мощных средств: шаблонов и составных компонентов. В этой, второй из трех статей цикла о новой функциональности JSF 2, член экспертной группы JSF 2 Дэвид Джири покажет, как наилучшим образом использовать в своих Web-приложениях механизм шаблонов и составные компоненты.

Дэвид Джири, президент, Clarity Training, Inc.

David GearyАвтор, лектор и консультант Дэвид Джири является президентом компании Clarity Training, Inc., Он обучает разработчиков создавать Web-приложения с использованием JSF и Google Web Toolkit (GWT). Он участвовал в экспертных группах JSTL 1.0 и JSF 1.0/2.0, был соавтором сертификационного экзамена Web Developer Certification Exam компании Sun, а также принимал участие в проектах с открытым кодом, в том числе Apache Struts и Apache Shale. Книга Дэвида Graphic Java Swing стала одной из самых продаваемых книг о Java, а Core JSF (в соавторстве с Кэем Хорстманом) - одна из самых продаваемых книг о JSF. Дэвид регулярно выступает на конференциях и встречах пользовательских групп. Он является регулярным участником конференций NFJS с 2003 года, проводил курсы в университете Java и дважды удостаивался звания JavaOne rock star.



14.01.2011

В далеком 2000 году, будучи активным участником рассылки JavaServer Pages (JSP), я познакомился с Крейгом Маккланаханом (Craig McClanahan), который работал над новой Web-инфраструктурой под названием Struts. Потом, когда я переходил от Swing к Java-программированию на серверной стороне, я написал небольшую инфраструктуру, отделявшую разметку JSP-представления от его содержимого, напоминавшую по духу имевшиеся в Swing менеджеры разметки. Крейг спросил меня, не желаю ли я включить мою библиотеку шаблонов в Struts, на что я с радостью согласился. Библиотека шаблонов Struts (Struts Template Library) входившая в Struts 1.0, стала основой популярной библиотеки Tiles для Struts, которая в итоге стала популярной инфраструктурой в Apache.

Вернемся в наши дни. По умолчанию в JSF 2 используется технология отображения Facelets, которая представляет собой инфраструктуру шаблонов, во многом основанную на Tiles. JSF 2 также предоставляет мощный механизм так называемых составных компонентов,построенных на основе функциональности шаблонов Facelets и позволяющих создавать собственные компоненты без использования кода Java или XML-конфигурации. В этой статье мы познакомимся с механизмом шаблонов и составными компонентами, а также с тремя советами по максимально эффективной работе с JSF 2:

  • Совет 1: придерживайтесь принципа DRY
  • Совет 2: разделяйте функциональность
  • Совет 3: мыслите в терминах конструкторов LEGO

Facelets и JSF 2

Стандартизируя открытую реализацию Facelets, экспертная группа JSF 2 сделала несколько изменений в API Facelets, однако с помощью библиотеки тегов они все же сохранили обратную совместимость. Это означает, что существующие представления, созданные с использованием версии Facelets с открытым кодом, также должны работать и с JSF 2.

Узнать больше о множестве возможностей Facelets можно в статьях Рика Хайтауэра "Facelets Fits JSF like a Glove" и "Advanced Facelets programming."

Совет 1: придерживайтесь принципа DRY

На моем первом месте работы программистом я разрабатывал графический пользовательский интерфейс для основанных на UNIX ® систем компьютерной поддержки проектирования и изготовления (CAD/CAM) систем.

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

Если бы я следовал в этом проекте принципу DRY— Don't Repeat Yourself (не повторяйся) — я мог бы избавить себя от множества проблем. Принцип DRY, сформулированный Дейвом Томасом и Энди Хантом (см. Ресурсы), гласит:

Каждый фрагмент знания должен иметь в системе единственное, однозначное и официальное представление.

Мое CAD/CAM приложение не соответствовало принципу DRY — в нем было слишком много взаимозависимостей, поэтому изменения в одном месте приводили к неожиданным изменениям в другом.

JSF 1 нарушала принцип DRY в нескольких аспектах. Например, она обязывала предоставлять два представления управляемых bean-компонентов — одно в XML, а другое в Java-коде. Потребность в более чем одном представлении усложняло создание и изменение управляемых bean-компонентов. Как мы видели в части 1, JSF 2 для конфигурирования управляемых bean-компонентов позволяет использовать аннотации вместо XML, обеспечивая одно официальное представление управляемых bean-компонентов.

Управляемые bean-компоненты сами по себе, даже в таких, казалось бы, безобидных случаях, как включение одной и той же таблицы стилей во все представления — нарушают принцип DRY, что чревато неприятностями. Если вы изменяете имя таблицы стилей, вы должны поменять его во множестве представлений. Включение таблицы стилей по возможности лучше инкапсулировать.

Принцип DRY применим и к проектированию кода. Если у вас есть несколько методов, содержащих, допустим, код для обхода дерева, хорошо бы инкапсулировать этот алгоритм обхода дерева, например в подклассе.

Особенно важно придерживаться принципа DRY при реализации пользовательского интерфейса, где зачастую большинство изменений происходит уже во время разработки.

Механизм шаблонов JSF 2

Одним из множества способов, которыми JSF 2 поддерживает принцип DRY, является механизм шаблонов. Шаблоны инкапсулируют общую функциональность различных представлений приложения, так что эту функциональность приходится описывать лишь однажды. Один шаблон используется в нескольких композициях для создания представлений приложения JSF 2.

В приложении places, с которым мы познакомились в части 1, имеется три представления. Они показаны на рисунке 1:

Рисунок 1. Представления приложения places: Login, source viewer и places
The places application's views

The places application's iconsThe places application's icons

Как во множестве Web-приложений, в приложении places имеется несколько представлений, использующих одну и ту же разметку. Механизм шаблонов JSF позволяет инкапсулировать в шаблонах эту разметку, а также другие совместно используемые элементы, например, сценарии JavaScript и каскадные таблицы стилей (CSS - Cascading Style Sheets). В листинге 1 приведен шаблон для трех представлений, показанных на рисунке 1:

Листинг 1. Шаблон places: /templates/masterLayout.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:h="http://java.sun.com/jsf/html"
   xmlns:ui="http://java.sun.com/jsf/facelets">

  <h:head>
    <title>
      <ui:insert name="windowTitle">
        #{msgs.placesWindowTitle}
      </ui:insert>
    </title> 
  </h:head>
  
  <h:body>  
    <h:outputScript library="javascript" name="util.js" target="head"/>      
    <h:outputStylesheet library="css" name="styles.css" target="body"/>	    
    
    <div class="pageHeading">
      <ui:insert name="heading">
        #{msgs.placesHeading}
      </ui:insert>     
    </div> 
      
    <div class="menuAndContent"> 
      <div class="menuLeft"> 
        <ui:insert name="menuLeft"/>
      </div>    
	    
      <div class="content" style="display: #{places.showContent}">
        <ui:insert name="content"/>
      </div> 
	    
      <div class="menuRight">
        <ui:insert name="menuRight">
          <ui:include src="/sections/shared/sourceViewer.xhtml"/>
        </ui:insert>
      </div> 
    </div>  
  </h:body> 
</html>

Шаблон из листинга 1 предоставляет всем представлениям приложения следующую инфраструктуру:

  • HTML-элементы <head>, <body> и <title>
  • название по умолчанию (которое можно переопределить в композициях, использующих этот шаблон)
  • таблицу стилей CSS
  • некоторый полезный JavaScript-код
  • разметку в форме <div>-элементов и соответствующих им CSS-классов
  • содержимое по умолчанию для заголовка (которое можно переопределить)
  • содержимое по умолчанию для правого меню (которое можно переопределить)

Как показано в листинге 1, шаблоны вставляют содержимое в свою разметку с помощью тега <ui:insert>.

Если для тега <ui:insert> указать тело, как это сделано в листинге 1 для названия окна, заголовка и меню справа, JSF будет использовать данное тело тега в качестве содержимого по умолчанию. Композиции, которые используют этот шаблон, могут определить содержимое или переопределить содержимое по умолчанию, используя тег <ui:define>. Это проиллюстрировано в листинге 2, в котором приведена разметка для представления login:

Листинг 2. Представление login
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
   xmlns:ui="http://java.sun.com/jsf/facelets"
   template="/templates/masterLayout.xhtml">
   
  <ui:define name="menuLeft">
    <ui:include src="/sections/login/menuLeft.xhtml"/>
  </ui:define>

  <ui:define name="content"> 
    <ui:include src="/sections/login/content.xhtml"/>           
  </ui:define>
     
</ui:composition>

В представлении login используется определенное в шаблоне содержимое по умолчанию для названия окна, заголовка и меню справа. В представлении login определяется лишь специфичная для него функциональность: раздел с содержимым и меню слева.

Используя тег <ui:define> для названия окна, заголовка или меню справа, можно переопределить содержимое по умолчанию, определенное в шаблоне. Например, в листинге 3 показано представление source-viewer (картинка в середине на рисунке 1):

Листинг 3. Представление source-viewer
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
   xmlns:ui="http://java.sun.com/jsf/facelets"
   template="/templates/masterLayout.xhtml">

  <ui:define name="content">
    <ui:include src="/sections/showSource/content.xhtml"/>
  </ui:define>

  <ui:define name="menuLeft">
    <ui:include src="/sections/showSource/menuLeft.xhtml"/>      
  </ui:define>
     
  <ui:define name="menuRight">
    <ui:include src="/sections/showSource/menuRight.xhtml"/>      
  </ui:define>

</ui:composition>

В представлении source-viewer определяется содержимое для раздела с содержимым и для меню справа. В нем также переопределяется содержимое по умолчанию, определенное в шаблоне из листинга 1 для меню слева.

В листинге 4 показано представление places (снизу на рисунке 1):

Листинг 4. Представление places
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
   xmlns:ui="http://java.sun.com/jsf/facelets"
   template="/templates/masterLayout.xhtml">

   <ui:define name="menuLeft">
    <ui:include src="/sections/places/menuLeft.xhtml"/>
   </ui:define>

  <ui:define name="content">
    <ui:include src="/sections/places/content.xhtml"/>
  </ui:define>

</ui:composition>

Механизм шаблонов JSF 2

Концепция, лежащая в основе механизма шаблонов, довольно проста. Вы определяете один шаблон, инкапсулирующий функциональность, совместно используемую несколькими представлениями. Каждое представление состоит из композиции и шаблона.

При создании представления JSF загружает композицию шаблона и затем вставляет определяемое композицией содержимое в шаблон.

Обратите внимание на схожесть кода из листингов 2, 3 и 4. Во всех трех представлениях указывается шаблон и определяется содержимое. Также обратите внимание, насколько легко можно создавать новые представления, так как большая часть инфраструктуры представления инкапсулируется в шаблоне и подключаемых файлах.

Еще одним интересным аспектом использования механизма шаблонов JSF является то, что представления, подобные показанным в листингах 2, 3 и 4, мало изменяются с течением времени, поэтому значительная часть кода представлений практически не нуждается в сопровождении.

Шаблоны, так же как и использующие их представления, обычно меняются очень мало. И поскольку мы инкапсулировали много общей функциональности в коде, практически не требующем сопровождения, можно сосредоточиться собственно на содержимом представлений — например, на том, что происходит в левом меню страницы login. Именно о непосредственном содержимом представлений и будет наш следующий совет.


Совет 2: разделяйте функциональность

Вскоре после выпуска моего графического интерфейса для CAD/CAM системы я провел несколько месяцев, работая над совсем другим проектом с моим коллегой по имени Боб. В основе проекта лежал его код, и, к моему удивлению, мы легко могли делать в нем изменения и исправлять ошибки.

Я быстро понял, что единственным существенным различием моего кода и кода Боба было то, что он писал крошечные методы — длиной, как правило, от 5 до 15 строк кода. Вся система была собрана из таких методов. В то время как в моем предыдущем проекте я пытался модифицировать большие методы с большим количеством функций. Боб ловко составлял маленькие методы с атомарной функциональностью. Мой код и код Боба различались в сопровождении и расширяемости как ночь и день. С того времени я стал убежденным сторонником маленьких методов.

Хотя ни Боб, ни я тогда этого не знали, но мы использовали шаблон проектирования из языка Smalltalk, называемый Составной Метод (Composed Method, см. Ресурсы):

Разделяйте свое приложение на методы, выполняющие одну идентифицируемую задачу на одном уровне абстракции.

Преимущества использования шаблона Составной Метод хорошо документированы (cм. подробное объяснение в статье Нила Форда "Evolutionary architecture and emergent design: Composed method and SLAP"). Здесь мы сосредоточимся на использовании шаблона Составной Метод в представлениях JSF.

JSF 2 поощряет составление представлений из фрагментов меньшего размера. Система шаблонов инкапсулирует общую функциональность, тем самым разделяя представления на части меньшего размера. В JSF 2 также имеется тег <ui:include>, который мы уже видели в предыдущих листингах. Этот тег позволяет разделять представления на кусочки функциональности еще меньшего размера. Например, на рисунке 2 показано левое меню страницы входа приложения places:

Рисунок 2. Левое меню на странице входа
The login view's left menu

А в листинге 5 показан файл, в котором определяется содержимое меню:

Листинг 5. Реализация левого меню страницы входа
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
  xmlns:h="http://java.sun.com/jsf/html"
  xmlns:ui="http://java.sun.com/jsf/facelets">

  <div class="menuLeftText">
    #{msgs.welcomeGreeting}

    <div class="welcomeImage">
      <h:graphicImage library="images" name="cloudy.gif"/>
    </div>
  </div>
    
</html>

Разметка из листинга 5 довольно проста, что делает файл легким для чтения, понимания, сопровождения и расширения. Если бы тот же самый код находился в одной большой XHTML-странице, содержащей все, что нужно для реализации страницы входа, его было бы непросто изменять.

На рисунке 3 показано меню слева в представлении places:

Рисунок 3. Меню слева в представлении places
The places view's left menu

Реализация меню слева в представлении places показана в листинге 6:

Листинг 6. Реализация меню слева в представлении places
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
    xmlns:h="http://java.sun.com/jsf/html"
    xmlns:ui="http://java.sun.com/jsf/facelets"
    xmlns:util="http://java.sun.com/jsf/composite/components/util">

  <div class="placesSearchForm"> 
    <div class="placesSearchFormHeading">
      #{msgs.findAPlace}
    </div>    
	
    <h:form prependId="false">
      <h:panelGrid columns="2">
	   
        #{msgs.streetAddress}
        <h:inputText value="#{place.streetAddress}" size="15"/>
	     
        #{msgs.city}  <h:inputText value="#{place.city}"  size="10"/>
        #{msgs.state} <h:inputText value="#{place.state}" size="3"/>
        #{msgs.zip}   <h:inputText value="#{place.zip}"   size="5"/>
  	     
        <h:commandButton value="#{msgs.goButtonText}" 
          style="font-family:Palatino;font-style:italic"
          action="#{place.fetch}"/>
	       	            
      </h:panelGrid>	        
    </h:form>
  </div>
	
  <util:icon image="#{resource['images:back.jpg']}"
    actionMethod="#{places.logout}"
    style="border: thin solid lightBlue"/>

</ui:composition>

В листинге 6 реализуется форма, а также используется компонент icon. (Компонент icon мы обсудим ниже, а пока достаточно знать , что автор страницы может ассоциировать изображение и метод с какой-либо пиктограммой.) Изображение для значка выхода показано в нижней части рисунка 3, а связанный с ним метод - places.logout() показан в листинге 7:

Листинг 7. Метод Places.logout()
package com.clarity;
...
@ManagedBean()
@SessionScoped

public class Places {
  private ArrayList<Place> places = null;
  ...  
  private static SelectItem[] zoomLevelItems = {
  ... 
  public String logout() {
    FacesContext fc = FacesContext.getCurrentInstance();     
    ELResolver elResolver = fc.getApplication().getELResolver();
	     
    User user = (User)elResolver.getValue(
      fc.getELContext(), null, "user");
	
    user.setName("");
    user.setPassword("");
	   
    setPlacesList(null);

    return "login"; 
  }
}

На мой взгляд, реализация меню слева в представлении places, показанная в листинге 6 , слишком близка к пороговому значению «слишком много кода», составляющему 30 строк разметки. Этот листинг довольно трудно читать, и в этом коде есть две вещи, которые можно выделить в отдельные файлы: это форма и пиктограмма. В листинге 8 показана переработанная версия кода из листинга 6, в которой форма и пиктограмма инкапсулируются в отдельных XHTML-файлах:

Листинг 8. Переработанное меню слева в представлении places
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
    xmlns:ui="http://java.sun.com/jsf/facelets">

  <div class="placesSearchForm"> 
    <div class="placesSearchFormHeading">
      #{msgs.findAPlace}
    </div>    

    <ui:include src="addressForm.xhtml">	
    <ui:include src="logoutIcon.xhtml">	
  </div>    

</ui:composition>

В листинге 9 показан файл addressForm.xhtml:

Листинг 9. addressForm.xhtml
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
    xmlns:h="http://java.sun.com/jsf/html"
    xmlns:ui="http://java.sun.com/jsf/facelets">
    
  <h:form prependId="false">
    <h:panelGrid columns="2">
    
      #{msgs.streetAddress}
      <h:inputText value="#{place.streetAddress}" size="15"/>
      
      #{msgs.city}  <h:inputText value="#{place.city}"  size="10"/>
      #{msgs.state} <h:inputText value="#{place.state}" size="3"/>
      #{msgs.zip}   <h:inputText value="#{place.zip}"   size="5"/>
      
      <h:commandButton value="#{msgs.goButtonText}" 
        style="font-family:Palatino;font-style:italic"
        action="#{place.fetch}"/>
                     
    </h:panelGrid>         
  </h:form>
  
</ui:composition>

В листинге 10 показан файл logoutIcon.xhtml:

Листинг 10. logoutIcon.xhtml
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
    xmlns:ui="http://java.sun.com/jsf/facelets"
    xmlns:util="http://java.sun.com/jsf/composite/components/util">

  <util:icon image="#{resource['images:back.jpg']}"
    actionMethod="#{places.logout}"
    style="border: thin solid lightBlue"/>

</ui:composition>

Когда вы составляете представления из множества маленьких файлов, вы пользуетесь преимуществами шаблона Составной Метод языка Smalltalk. Кроме того, можно организовать свои файлы так, чтобы было легче реагировать на изменения. Например, на рисунке 4 показаны файлы, составляющие три представления приложения places:

Рисунок 4. Представления приложения places
The places application's views

Я разместил XHTML-файлы, используемые для реализации представлений приложения places, в трех директориях — views, sections и templates. Файлы в директориях views и templates изменяются редко, поэтому я могу сосредоточиться на директории sections. Например, если я захочу изменить пиктограмму в меню слева на странице login, я точно знаю, в каком файле это можно сделать: sections/login/menuLeft.xhtml.

Вы можете организовать свои XHTML-файлы в любую структуру директорий. Логичная структура облегчает нахождение кода, который нужно изменить.

Помимо следования принципу DRY и использования шаблона Составной Метод, также полезно инкапсулировать функциональность пользовательских компонентов. Компоненты – это мощный механизм организации повторного использования кода. Его возможностями следует пользоваться. В отличие от JSF 1, в JSF 2 можно пользовательские компоненты реализовывать легко.


Совет 3: мыслите в терминах конструкторов LEGO

Когда я был маленьким, моими любимыми игрушками были набор для химических опытов и конструкторы LEGO. И то, и другое позволяло мне создавать что-то из фундаментальных строительных блоков. Я испытываю интерес к этому процессу (который приобрел форму разработки ПО) до сих пор.

Силой JSF всегда была компонентная модель, однако полностью эту силу удалось осознать только сейчас, так как в JSF 1 было сложно создавать собственные компоненты. Для этого приходилось писать Java-код, создавать XML-конфигурацию и хорошо понимать жизненный цикл JSF. А в JSF 2 можно создавать собственные компоненты:

  • Без XML или какой-либо другой конфигурации.
  • Без Java-кода.
  • С возможностью добавления функциональности.
  • Быстро развертываемые после изменения.

В заключительной части статьи мы рассмотрим реализацию трех пользовательских компонентов приложения places: пиктограммы, панели входа и панели, отображающей карту и информацию о погоде для определенного адреса. Однако сначала мы познакомимся с предлагаемыми JSF 2 составными компонентами.

Реализация пользовательских компонентов

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

Составные компоненты создаются в виде XHTML-файлов где-либо в директории resources, после чего они в соответствии с соглашением связываются с пространством имен и тегом. На рисунке 5 показано, как я организовал составные компоненты для приложения places:

Рисунок 5. Компоненты приложения places
The places application's components

Для работы с составными компонентами нужно объявить пространство имен и использовать теги. Пространство имен – это всегда http://java.sun.com/jsf/composite плюс имя поддиректории в директории resources, в которой находится данный компонент. Само имя компонента – это имя его XHTML-файла без расширения .xhtml. Это соглашение делает необязательным использование какой-либо конфигурации. Например, для использования в приложении places компонента login нужно сделать следующее:

<html xmlns="http://www.w3.org/1999/xhtml"
    ...
    xmlns:util="http://java.sun.com/jsf/composite/component/util">
  ...
  <util:login.../>
  ...
<html>

А для использования компонента icon нужно сделать следующее:

<html xmlns="http://www.w3.org/1999/xhtml"
    ...
    xmlns:util="http://java.sun.com/jsf/composite/components/util">
  ...
  <util:icon.../>
  ...
<html>

И, наконец, работа с компонентом place выглядит так:

<html xmlns="http://www.w3.org/1999/xhtml"
    ...
    xmlns:util="http://java.sun.com/jsf/composite/components/places">
  ...
  <places:place.../>
  ...
<html>

Компонент icon: элементарный составной компонент

В приложении places используется две пиктограммы, показанные на рисунке 6:

Рисунок 6. Пиктограммы приложения places
The places application's icons

The places application's icons

У каждой пиктограммы есть ссылка. Когда пользователь нажимает пиктограмму, показанную слева на рисунке 6, JSF отображает разметку текущего представления, тогда как нажатие кнопки, показанной справа, приводит к выходу пользователя из приложения.

Для этих ссылок можно указать имя CSS-класса, изображение, а также прикрепить методы. При нажатии на ссылку JSF будет вызывать привязанный к ней метод.

В листинге 11 показано, как компонент icon используется в приложении places для отображения разметки:

Листинг 11. Используем компонент icon для отображения разметки
<html xmlns="http://www.w3.org/1999/xhtml"
    xmlns:util="http://java.sun.com/jsf/composite/components/util">

  <util:icon actionMethod="#{sourceViewer.showSource}" 
                      image="#{resource['images:disk-icon.jpg']}"/>
  ...
</html>

В листинге 12 показано, как компонент icon используется для выполнения выхода из приложения:

Листинг 12. Используем компонент icon для выхода из системы
<html xmlns="http://www.w3.org/1999/xhtml"
    xmlns:util="http://java.sun.com/jsf/composite/components/util">

  <util:icon actionMethod="#{places.logout}" 
                      image="#{resource['images:back-arrow.jpg']}"/>
  ...
</html>

В листинге 13 показан код компонента icon:

Листинг 13. Компонент icon
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
<html xmlns="http://www.w3.org/1999/xhtml"
    xmlns:h="http://java.sun.com/jsf/html"
    xmlns:composite="http://java.sun.com/jsf/composite">
    
  <!- INTERFACE ->
  <composite:interface>
    <composite:attribute name="image"/>
    <composite:attribute name="actionMethod" 
             method-signature="java.lang.String action()"/>        
  </composite:interface>

  <!- IMPLEMENTATION ->          
    <composite:implementation>
    <h:form>  
      <h:commandLink action="#{cc.attrs.actionMethod}" immediate="true">

      <h:graphicImage value="#{cc.attrs.image}"
                styleClass="icon"/>

      </h:commandLink>
    </h:form>
  </composite:implementation>
</html>

Как и все составные компоненты, компонент icon, показанный в листинге 13, состоит из двух частей: <composite:interface> и <composite:implementation>. В секции <composite:interface> определяется интерфейс, который можно использовать для конфигурирования компонента. У компонента icon есть два атрибута: image, который определяет, как выглядит этот компонент, и actionMethod, который определяет его поведение.

В секции <composite:implementation> содержится реализация компонента. В ней для доступа к атрибутам, определенным в интерфейсе компонента, используется выражение #{cc.attrs.ATTRIBUTE_NAME}. (cc - composite component (составной компонент) - это зарезервированное ключевое слово языка выражений JSF 2.)

Обратите внимание, что в компоненте icon из листинга 13 определяется CSS-класс его изображения с помощью атрибута styleClass тега <h:graphicImage>. Это имя жестко закодировано и имеет значение icon, поэтому можно было бы просто создать CSS-класс с этим именем, и JSF будет использовать его для всех иконок в приложении. Но что, если мы хотим переопределить это имя класса CSS? В этом случае можно добавить еще один атрибут для имени класса CSS и задать ему значение по умолчанию, которое JSF будет использовать, если этот атрибут не указан. В листинге 14 показано, как может выглядеть подобный атрибут:

Листинг 14. Переработанный компонент icon
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
<html xmlns="http://www.w3.org/1999/xhtml"
    ...
    xmlns:composite="http://java.sun.com/jsf/composite">
    
    <composite:interface>
      ...
      <composite:attribute name="styleClass" default="icon" required="false"/>
      ...
    </composite:interface>

    <composite:implementation>
      ...
      <h:graphicImage value="#{cc.attrs.image}"
                 styleClass="#{cc.attrs.styleClass}"/>
      ...
    </composite:implementation>
</html>

В листинге 14я добавил в интерфейс компонента атрибут с именем styleClass и использовал этот атрибут в реализации компонента. Сделав это изменение можно по желанию указывать CSS-класс изображения пиктограммы следующим образом:

<util:icon actionMethod="#{places.logout}" 
                  image="#{resource['images:back-arrow.jpg']}"
            styleClass="customIconClass"/>

Если не указать атрибут styleClass, JSF будет использовать значение по умолчанию, icon.

Компонент login: полностью настраиваемый компонент

С помощью JSF 2 можно создавать полностью настраиваемые составные компоненты. Например, в приложении places имеется компонент входа login, показанный на рисунке 7:

Рисунок 7. Компонент login приложения places
The places application's login component

В листинге 15 показано, как в приложении places используется компонент login:

Листинг 15. Использование компонента login
<!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:comp="http://java.sun.com/jsf/composite/component/util">

  <util:login loginPrompt="#{msgs.loginPrompt}"
                namePrompt="#{msgs.namePrompt}"
            passwordPrompt="#{msgs.passwordPrompt}"
               loginAction="#{user.login}"
           loginButtonText="#{msgs.loginButtonText}"
               managedBean="#{user}">
                 
    <f:actionListener for="loginButton" 
                        type="com.clarity.LoginActionListener"/>
                            
  </util:login>
  ...
</html>

В листинге 15 не только задаются параметрами атрибуты компонента login,например, надписи для полей имени и пароля, но также к кнопке Log In прикрепляется слушатель действия. Эта кнопка доступна в интерфейсе компонента login, как показано в листинге 16:

Листинг 16. Компонент login
<!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:f="http://java.sun.com/jsf/core"
    xmlns:h="http://java.sun.com/jsf/html"
    xmlns:composite="http://java.sun.com/jsf/composite">

  <!- INTERFACE ->
  <composite:interface>
    <composite:actionSource name="loginButton" targets="form:loginButton"/>
    <composite:attribute name="loginButtonText" default="Log In" required="true"/>
    <composite:attribute name="loginPrompt"/>
    <composite:attribute name="namePrompt"/>
    <composite:attribute name="passwordPrompt"/>
    <composite:attribute name="loginAction" 
      method-signature="java.lang.String action()"/>
    <composite:attribute name="managedBean"/>
  </composite:interface>
    
  <!- IMPLEMENTATION ->
  <composite:implementation>
   <h:form id="form" prependId="false">

     <div class="prompt">
       #{cc.attrs.loginPrompt}
     </div>

     <panelGrid columns="2">
       #{cc.attrs.namePrompt}
       <h:inputText id="name" value="#{cc.attrs.managedBean.name}"/>

       #{cc.attrs.passwordPrompt} 
       <h:inputSecret id="password" value="#{cc.attrs.managedBean.password}" />

     </panelGrid>

     <p>
       <h:commandButton id="loginButton"
                     value="#{cc.attrs.loginButtonText}" 
                    action="#{cc.attrs.loginAction}"/>
     </p>
   </h:form>
   
   <div class="error" style="padding-top:10px;">
     <h:messages layout="table"/>
   </div>
  </composite:implementation>
</html>

В интерфейсе компонента login мы выставляем кнопку Log In в виде элемента с именем loginButton. Это имя соответствует кнопке Log In, находящейся на форме с именем form, поэтому атрибут targets этого элемента имеет значение form:loginButton.

Слушатель действия, ассоциированный с кнопкой Log In из листинга 16 показан в листинге 17:

Листинг 17. Слушатель действия кнопки Log In
package com.clarity;

import javax.faces.event.AbortProcessingException;
import javax.faces.event.ActionEvent;
import javax.faces.event.ActionListener;

public class LoginActionListener implements ActionListener {
  public void processAction(ActionEvent e) 
    throws AbortProcessingException {
    System.out.println("logging in ...........");
  }
}

Слушатель действия из листинга 17 приведен здесь исключительно в целях иллюстрации — когда пользователь входит в приложение, я просто записываю сообщение в файл журнала. Однако вы должны уловить идею: используя JSF 2, можно создавать полностью конфигурируемые компоненты и прикреплять к ним функциональность без единой строки Java-кода или XML-конфигурации. Это мощный прием.

Компонент place: вложенные составные компоненты

JSF 2 позволяет создавать полностью конфигурируемые компоненты без написания Java-кода или конфигурации. Также составные компоненты можно вкладывать друг в друга, что позволяет разбивать сложные компоненты на более управляемые части меньшего размера. Например, на рисунке 8 показан компонент place, показывающий карту и информацию о погоде для заданного адреса:

Рисунок 8. Компонент place приложения places
The place component

В листинге 18 показано, как в приложении places используется компонент place:

Листинг 18. Используем компонент place
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
    xmlns:h="http://java.sun.com/jsf/html"
    xmlns:ui="http://java.sun.com/jsf/facelets"
    xmlns:places="http://java.sun.com/jsf/composite/components/places">

  <h:form id="form"> 
    <ui:repeat value="#{places.placesList}" var="place">
      <places:place location="#{place}"/>
    </ui:repeat>
  </h:form>
</ui:composition>

Код компонента place показан в листинге 19:

Listing 19. The place component
<!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:composite="http://java.sun.com/jsf/composite"
    xmlns:places="http://java.sun.com/jsf/composite/components/places">

  <!-  INTERFACE ->
  <composite:interface>
    <composite:attribute name="location" required="true"/>     
  </composite:interface>
         
  <!- IMPLEMENTATION ->
  <composite:implementation>
    <div class="placeHeading">

      <places:map     title="Map"/>
     <places:weather title="Weather"/>

    </div>
  </composite:implementation>    

</html>

В компоненте place, показанном в листинге 19, используется два вложенных компонента: <places:map> и <places:weather>. В листинге 20 показан компонент map:

Листинг 20. Компонент map
<!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:f="http://java.sun.com/jsf/core"
    xmlns:h="http://java.sun.com/jsf/html"
    xmlns:ui="http://java.sun.com/jsf/facelets"
    xmlns:composite="http://java.sun.com/jsf/composite">
    
   <!- INTERFACE ->
   <composite:interface>
     <composite:attribute name="title"/>
   </composite:interface>
        
   <!- IMPLEMENTATION -> 
   <composite:implementation>
      <div class="map">
      <div style="padding-bottom: 10px;">
        <h:outputText value="#{cc.attrs.title}"
                      style="color: blue"/>
      </div>
        
      <h:panelGrid columns="1">
       <h:panelGroup>
         <div style="padding-left: 5px;">
           <i>
             <h:outputText value="#{cc.parent.attrs.location.streetAddress}, "/>
           </i>
            
           <h:outputText value=" #{cc.parent.attrs.location.city}" />
           <h:outputText value="#{cc.parent.attrs.location.state}"/><hr/>
         </div>
       </h:panelGroup>
 
       <h:panelGrid columns="2">
         <div style="padding-right: 10px;margin-bottom: 10px;font-size:14px">
           #{msgs.zoomPrompt}
         </div>
 
         <h:selectOneMenu onchange="submit()"
                        value="#{cc.parent.attrs.location.zoomIndex}"
          valueChangeListener="#{cc.parent.attrs.location.zoomChanged}"
                        style="font-size:13px;font-family:Palatino">
 
           <f:selectItems value="#{cc.parent.attrs.location.zoomLevelItems}"/>
 
         </h:selectOneMenu>
       </h:panelGrid>
 
       <h:graphicImage url="#{cc.parent.attrs.location.mapUrl}" 
        style="border: thin solid gray"/>
 
     </h:panelGrid>
     </div>
   </composite:implementation>
</html>

Рефакторинг составных компонентов

На мой взгляд, разметка компонента map, показанная в листинге 20 , чересчур велика. Ее довольно трудно понять с первого взгляда, и ее сложность может в будущем привести к проблемам.

Разметку из листинга 20 можно легко разбить на несколько более управляемых файлов, как мы уже делали раньше при рефакторинге левого меню представления places в листингах 8, 9, и 10. В данном случае я оставлю выполнение рефакторинга для вас в качестве упражнения.

Обратите внимание на использование в листинге 20 выражения #{cc.parent.attrs.location.ATTRIBUTE_NAME}. Атрибут parent составного компонента позволяет получить доступ к атрибутам родительского компонента, что очень упрощает работу с вложенными компонентами.

Однако нет необходимости полагаться во вложенных компонентах на атрибуты родителей. Как я уже сделал в компоненте place в листинге 19, можно передавать атрибуты, например, название карты из родительского компонента во вложенный компонент, точно так же как при передаче атрибутов в любой другой компонент.

Это немного примитивно, но тем не менее в листинге 21 показан компонент weather:

Листинг 21. Компонент weather
<!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:h="http://java.sun.com/jsf/html"
    xmlns:composite="http://java.sun.com/jsf/composite">

  <!- INTERFACE ->
  <composite:interface>
    <composite:attribute name="title"/>
  </composite:interface>
          
  <!- IMPLEMENTATION ->
  <composite:implementation>
    
   <div class="weather">
     <div style="padding-bottom: 10px;">
       <h:outputText value="#{cc.attrs.title}"
         style="color: blue"/>
     </div>
        
     <div style="margin-top: 10px;width:250px;">
       <h:outputText style="font-size: 12px;"
                     value="#{cc.parent.attrs.location.weather}"
                    escape="false"/>
     </div>
   </div>   
       
   </composite:implementation>
</html>

В компоненте weather, так же как и в компоненте map, используется и родительский атрибут (HTML разметка с информацией о погоде, полученная от Web-сервиса weather), и атрибут, специфичный для компонента (title). (О том, как приложение получает карту и информацию о погоде для определенного места, рассказано в части 1).

Таким образом, при создании вложенных компонентов у вас есть выбор. Можно позволить вложенному компоненту воспользоваться атрибутами родительского компонента, либо можно обязать родительский компонент явно передавать атрибуты во вложенный компонент. Например, компонент place из листинга 19 явно передает во вложенные компоненты атрибут title, и в то же время вложенные компоненты полагаются на родительские атрибуты, такие как URL карты и HTML-разметка с информацией о погоде.

Выбор между реализацией явных атрибутов компонента или использованием родительских атрибутов является компромиссом между связыванием и удобством. В данном случае компоненты map и weather тесно связаны со своим родительским компонентом (place), так как они оба используют атрибуты родительского компонента. Я бы мог отделить компоненты map и weather от компонента place, обозначив все атрибуты компонентов map и weather как явно задаваемые. Однако это было бы несколько неудобно, так как компоненту place пришлось бы явно передавать все свои атрибуты в компоненты map и weather.


А дальше?

В этой статье мы рассмотрели, как с помощью JSF 2 создавать пользовательские интерфейсы, которые легко сопровождать и расширять посредством шаблонов и составных компонентов. В последней статье этого цикла мы узнаем, как использовать в составных компонентах JavaScript-код, как работать с новой событийной моделью JSF 2 и как пользоваться встроенной в JSF 2 поддержкой Ajax.


Загрузка

ОписаниеИмяРазмер
Исходный код примеровjsf2fu2.zip7.4 МБ

Ресурсы

Научиться

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

  • JSF: загрузите JSF 2.0.(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=Технология Java
ArticleID=608167
ArticleTitle=JSF 2 fu: Часть 2. Шаблоны и составные компоненты
publish-date=01142011