2. Java Server Faces
Тепер ми розглянемо фреймворк Java Server Faces. Ми використовуватимемо версію 2, але приклади переважно демонструють особливості версії 1. Щодо версії 2, ми розглянемо лише ті особливості, які необхідні для прикладу додатка, що буде наведено далі.
2.1. Місце JSF у веб-додатку
Насамперед визначимо місце JSF у процесі розробки веб-додатка. Найчастіше такий додаток будується на багатошаровій архітектурі, наприклад, такій:
![]() |
- шар [web] — це шар, що взаємодіє з користувачем веб-додатка. Користувач взаємодіє з веб-додатком через веб-сторінки, що відображаються у браузері. Саме в цьому шарі, і лише в ньому, розташований JSF,
- шар [métier] реалізує правила управління додатком, такі як розрахунок заробітної плати або рахунку-фактури. Цей шар використовує дані, що надходять від користувача через шар [web] та з СГБД через шар [DAO],
- шар [DAO] (Data Access Objects), шар [jpa] (Java Persistence API) та драйвер JDBC керують доступом до даних СГБД. Рівень [jpa] виконує функцію ORM (об'єктно-реляційний мапер). Він слугує мостом між об'єктами, що обробляються рівнем [DAO], та рядками й стовпцями даних реляційної бази даних,
- інтеграція шарів може здійснюватися за допомогою контейнера Spring або EJB3 (Enterprise Java Bean).
Приклади, наведені далі для ілюстрації JSF, використовуватимуть лише один рівень — рівень [web]:
![]() |
Опанувавши основи JSF, ми будемо створювати багаторівневі Java-додатки EE.
2.2. Модель розробки MVC на основі JSF
JSF реалізує архітектурну модель, відому як MVC (Модель – Представлення – Контролер), таким чином:
![]() |
Ця архітектура реалізує шаблон проектування MVC (Модель, Представлення, Контролер). Обробка запиту клієнта відбувається за такими чотирма етапами:
- запит — браузер клієнта надсилає запит до контролера [Faces Servlet]. Цей контролер обробляє всі запити клієнтів. Це вхідна точка додатка. Це «C» у MVC,
- обробка — контролер C обробляє цей запит. Для цього він використовує обробники подій, специфічні для написаного додатка [2a]. Цим обробникам може знадобитися допомога бізнес-шару [2b]. Після обробки запиту клієнта можуть бути сформовані різні відповіді. Класичним прикладом є:
- сторінка з повідомленням про помилку, якщо запит не вдалося обробити належним чином;
- сторінка підтвердження в іншому випадку,
- навігація — контролер обирає відповідь (= вигляд), яку слід надіслати клієнту. Вибір відповіді, яку слід надіслати клієнту, вимагає кількох етапів:
- вибрати Facelet, який згенерує відповідь. Це те, що називається поданням V, де V — це MVC. Цей вибір, як правило, залежить від результату виконання дії, яку запросив користувач;
- надати цьому Facelet дані, необхідні для генерації цієї відповіді. Адже вона найчастіше містить інформацію, обчислену контролером. Ця інформація утворює так звану модель M подання, M у MVC,
Отже, етап 3 полягає у виборі подання V та побудові моделі M, необхідної для нього.
- Відповідь — контролер C надсилає запит обраному Facelet на відображення. Facelet використовує модель M, підготовлену контролером C, для ініціалізації динамічних частин відповіді, яку він повинен надіслати клієнту. Точна форма відповіді може бути різною: це може бути потік HTML, PDF, Excel тощо.
У проєкті JSF:
- контролер C — це сервлет [javax.faces.webapp.FacesServlet]. Він міститься у бібліотеці [javaee.jar],
- представлення V реалізовані сторінками, що використовують технологію Facelets,
- моделі M та обробники подій реалізовані класами Java, які часто називають «backing beans» або, простіше кажучи, «beans».
Тепер уточнимо зв’язок між веб-архітектурою MVC та багаторівневою архітектурою. Це дві різні концепції, які іноді плутають. Розглянемо однорівневий веб-додаток JSF:
![]() |
Якщо ми реалізуємо шар [web] за допомогою JSF, ми отримаємо веб-архітектуру MVC, але не багатошарову архітектуру. У цьому випадку шар [web] буде відповідати за все: представлення, бізнес-логіку, доступ до даних. У випадку з JSF цю роботу виконуватимуть біни.
Тепер розглянемо багаторівневу веб-архітектуру:
![]() |
Рівень [web] можна реалізувати без фреймворку та без дотримання моделі MVC. У цьому випадку ми маємо багаторівневу архітектуру, але веб-рівень не реалізує модель MVC.
У MVC ми зазначили, що модель M відповідає представленню V, c.a.d, — сукупності даних, що відображаються представленням V. Часто наводиться інше визначення моделі M з MVC:
![]() |
Багато авторів вважають, що те, що знаходиться праворуч від шару [web], утворює модель M для MVC. Щоб уникнути двозначностей, ми будемо говорити про:
- модель домену, коли мається на увазі все, що знаходиться праворуч від шару [web],
- модель виду — коли йдеться про дані, що відображаються у вигляді V.
Надалі термін «модель M» позначатиме виключно модель подання V.
2.3. Приклад mv-jsf2-01: елементи проєкту JSF
Перші приклади будуть зведені лише до веб-шару, реалізованого за допомогою JSF 2:
![]() |
Коли ми засвоїмо основи, ми розглянемо більш складні приклади з багатошаровими архітектурами.
2.3.1. Створення проєкту
Ми створюємо наш перший проєкт JSF2 за допомогою NetBeans 7.
![]() |
- у [1], створюємо новий проєкт,
- у [2] вибираємо категорію [Maven] та тип проєкту [Web Application],
![]() |
- у [3], вкажіть батьківську папку для папки нового проєкту,
- у [4] — вкажіть назву проекту,
- у [5] — вибрати сервер. У NetBeans 7 можна вибрати між серверами Apache Tomcat і Glassfish. Різниця між ними полягає в тому, що Glassfish підтримує EJB (Enterprise Java Bean), а Tomcat — ні. У наших прикладах JSF ми не будемо використовувати EJB. Тому тут можна вибрати будь-який сервер,
- у [6] ми обираємо версію Java EE 6 Web,
- у [7] — згенерований проєкт.
Розглянемо елементи проекту та пояснимо роль кожного з них.
![]() |
- у [1]: різні гілки проекту:
- [Web Pages]: міститиме веб-сторінки (.xhtml, .jsp, .html), ресурси (зображення, різні документи), конфігурацію веб-шару, а також конфігурацію фреймворку JSF;
- [Source packages]: Java-класи проекту;
- [Dependencies]: архіви .jar, необхідні для проекту та керовані фреймворком Maven;
- [Java Dependencies]: архіви .jar, необхідні для проекту та не керовані фреймворком Maven;
- [Project Files]: файл конфігурації Maven та NetBeans,
![]() |
- у [2]: гілка [Web Pages],
Вона містить таку сторінку [index.jsp]:
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/HTML4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>JSP Page</title>
</head>
<body>
<h1>Hello World!</h1>
</body>
</html>
Це веб-сторінка, яка відображає рядок «Hello World» великими літерами.
Файл [META-INF/context.xml] має такий вигляд:
<?xml version="1.0" encoding="UTF-8"?>
<Context antiJARLocking="true" path="/mv-jsf2-01"/>
У рядку 2 вказано, що контекст додатка (або його назва) — /mv-jsf2-01. Це означає, що веб-сторінки проєкту будуть запитуватися через URL у форматі http://machine:port/mv-jsf2-01/page. За замовчуванням контекстом є назва проєкту. Нам не доведеться змінювати цей файл.
![]() |
- у [3], гілка [Source Packages],
Ця гілка містить вихідний код Java-класів проекту. Тут у нас немає жодного класу. NetBeans створив пакет за замовчуванням, який можна видалити: [4].
![]() |
- в [5], гілка [Dependencies],
Ця гілка відображає всі бібліотеки, необхідні для проєкту та керовані Maven. Усі бібліотеки, перелічені тут, будуть автоматично завантажені Maven. Саме тому проєкт Maven потребує доступу до Інтернету. Завантажені бібліотеки будуть зберігатися локально. Якщо іншому проєкту потрібна бібліотека, яка вже є локально, вона не буде завантажуватися. Ми побачимо, що цей список бібліотек, а також репозиторії, де їх можна знайти, визначені у файлі конфігурації проекту Maven.
![]() |
- у [6] — бібліотеки, необхідні для проєкту, які не управляються Maven,
![]() |
- у [7] — файли конфігурації проекту Maven:
- [nb-configuration.xml] — це файл конфігурації NetBeans. Ми не будемо на ньому зупинятися.
- [pom.xml] — файл конфігурації Maven. POM означає Project Object Model. Іноді нам доведеться вносити зміни безпосередньо в цей файл.
Створений файл [pom.xml] має такий вигляд:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>istia.st</groupId>
<artifactId>mv-jsf2-01</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<name>mv-jsf2-01</name>
<properties>
<endorsed.dir>${project.build.directory}/endorsed</endorsed.dir>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>javax</groupId>
<artifactId>javaee-web-api</artifactId>
<version>6.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.3.2</version>
<configuration>
<source>1.6</source>
<target>1.6</target>
<compilerArguments>
<endorseddirs>${endorsed.dir}</endorseddirs>
</compilerArguments>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>2.1.1</version>
<configuration>
<failOnMissingWebXml>false</failOnMissingWebXml>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>2.1</version>
<executions>
<execution>
<phase>validate</phase>
<goals>
<goal>copy</goal>
</goals>
<configuration>
<outputDirectory>${endorsed.dir}</outputDirectory>
<silent>true</silent>
<artifactItems>
<artifactItem>
<groupId>javax</groupId>
<artifactId>javaee-endorsed-api</artifactId>
<version>6.0</version>
<type>jar</type>
</artifactItem>
</artifactItems>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
- рядки 5–8 визначають об’єкт (артефакт) Java, який буде створено в рамках проекту Maven. Ця інформація походить із майстра, який використовувався під час створення проекту:
![]() |
Об’єкт Maven визначається чотирма властивостями:
- [groupId]: інформація, що нагадує ім’я пакета. Так, бібліотеки фреймворку Spring мають groupId=org.springframework, бібліотеки фреймворку JSF мають groupId=javax.faces,
- [artifactId] — ім’я об’єкта Maven. У групі [org.springframework] містяться такі artifactId: spring-context, spring-core, spring-beans, ... У групі [javax.faces] містяться artifactId та jsf-api,
- [version]: номер версії артефакту Maven. Отже, артефакт org.springframework.spring-core має такі версії: 2.5.4, 2.5.5, 2.5.6, 2.5.6.SECO1, ...
- [packaging]: формат артефакту, найчастіше war або jar.
Отже, наш проект Maven згенерує [war] (рядок 8) у групі [istia.st] (рядок 5), що має назву [mv-jsf2-01] (рядок 6) та версію [1.0-SNAPSHOT] (рядок 7). Ці чотири елементи інформації повинні однозначно визначати артефакт Maven.
У рядках 17–24 перелічено залежності проекту Maven, тобто список бібліотек, необхідних для проекту. Кожна бібліотека визначається чотирма елементами (groupId, artifactId, версія, упаковка). Коли елемент packaging відсутній, як у цьому випадку, використовується файл jar з packaging. Додається ще один параметр — scope, який визначає, на яких етапах життєвого циклу проєкту потрібна бібліотека. Значенням за замовчуванням є compile, що вказує на те, що бібліотека потрібна як під час компіляції, так і під час виконання. Значення provided означає, що бібліотека потрібна під час компіляції, але не під час виконання. У даному випадку під час виконання її надаватиме сервер Tomcat 7.
2.3.2. Виконання проекту
Запускаємо проект:
![]() |
У файлі [1] виконується проект Maven. При цьому запускається сервер Tomcat, якщо він ще не запущений. Також запускається браузер, і з контексту проекту запитується файл URL — [2]. Оскільки жоден документ не запитується, використовується сторінка index.html, index.jsp, index.xhtml, якщо вона існує. У даному випадку це буде сторінка [index.jsp].
2.3.3. Файлова система проекту Maven
![]() |
- [1]: файлова система проєкту знаходиться на вкладці [Files],
- [2]: вихідні коди Java знаходяться у папці [src / main / java],
- [3]: веб-сторінки знаходяться у папці [src / main / webapp],
- [4]: папка [target] створюється під час збірки (build) проєкту,
- [5]: у цьому випадку під час компіляції проекту було створено архів [mv-jsf2-01-1.0-SNAPSHOT.war]. Саме цей архів було запущено сервером Tomcat.
2.3.4. Налаштування проєкту для JSF
Наш поточний проєкт не є проєктом JSF. У ньому відсутні бібліотеки фреймворку JSF. Щоб перетворити поточний проєкт на проєкт JSF, слід виконати такі дії:
![]() |
- у [1] відкриваємо властивості проекту,
- у [2] вибираємо категорію [Frameworks],
- у [3] додаємо фреймворк,
![]() |
- у [4] вибираємо Java Server Faces,
- у [5] NetBeans пропонує нам версію 2.1 фреймворку. Ми погоджуємося,
- у файлі [6] проект доповнюється новими залежностями.
Файл [pom.xml] було оновлено відповідно до цієї нової конфігурації:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>istia.st</groupId>
<artifactId>mv-jsf2-01</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<name>mv-jsf2-01</name>
...
<dependencies>
<dependency>
<groupId>com.sun.faces</groupId>
<artifactId>jsf-api</artifactId>
<version>2.1.1-b04</version>
</dependency>
<dependency>
<groupId>com.sun.faces</groupId>
<artifactId>jsf-impl</artifactId>
<version>2.1.1-b04</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.1.2</version>
</dependency>
<dependency>
<groupId>taglibs</groupId>
<artifactId>standard</artifactId>
<version>1.1.2</version>
</dependency>
<dependency>
<groupId>javax</groupId>
<artifactId>javaee-web-api</artifactId>
<version>6.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
...
</build>
<repositories>
<repository>
<URL>http://download.java.net/maven/2/</URL>
<id>jsf20</id>
<layout>default</layout>
<name>Repository for library Library[jsf20]</name>
</repository>
<repository>
<URL>http://repo1.maven.org/maven2/</URL>
<id>jstl11</id>
<layout>default</layout>
<name>Repository for library Library[jstl11]</name>
</repository>
</repositories>
</project>
У рядках 14–33 додано нові залежності. Maven завантажує їх автоматично. Він шукає їх у так званих репозиторіях. Автоматично використовується центральний репозиторій (Central Repository). Можна додати інші репозиторії за допомогою тегу <repository>. Тут додано два репозиторії:
- рядки 46–51: репозиторій для бібліотеки JSF 2,
- рядки 52–57: репозиторій для бібліотеки JSTL 1.1.
Проєкт також поповнився новою веб-сторінкою:
![]() |
Сторінка [index.HTML] виглядає так:
<?xml version='1.0' encoding='UTF-8' ?>
<!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">
<h:head>
<title>Facelet Title</title>
</h:head>
<h:body>
Hello from Facelets
</h:body>
</html>
Тут ми бачимо файл XML (рядок 1). У ньому містяться теги з HTML, але у форматі XML. Це називається XHTML. Технологія, що використовується для створення веб-сторінок за допомогою JSF 2, називається Facelets. Тому сторінку XHTML іноді називають сторінкою Facelet.
У рядках 3–4 визначається тег <html> із простором імен XML (xmlns=XML Name Space).
- у рядку 3 визначається основний простір імен http://www.w3.org/1999/xhtml,
- у рядку 4 визначається простір імен http://java.sun.com/jsf/html для тегів HTML. До цих тегів буде додано префікс h:, як вказано в xmlns:h. Ці теги містяться в рядках 5, 7, 8 та 10.
Зустрівши оголошення простору імен, веб-сервер просканує папки [META-INF] та Classpath додатка у пошуках файлів із розширенням .tld (TagLib Definition). Тут він знайде їх в архіві [jsf-impl.jar] та [1,2]:
![]() |
Розглянемо файл [3] та [HTML_basic.tld]:
- у рядку 19 — URI бібліотеки тегів,
- у рядку 16 — її скорочена назва.
Визначення різних тегів <h:xx> містяться в цьому файлі. Ці теги обробляються класами Java, які також містяться в артефакті [jsf-impl.jar].
Повернемося до нашого проєкту JSF. Він поповнився новою гілкою:
![]() |
Гілка [Other Sources] [1] містить файли, які повинні бути в Classpath проекту і які не є кодом Java. Це стосується файлів повідомлень у JSF. Ми бачили, що без додавання фреймворку JSF до проєкту ця гілка відсутня. Щоб її створити, достатньо створити папку [src / main / resources] [3] на вкладці [Files] [2].
Нарешті, у гілці [Web Pages] з’явилася нова папка:
![]() |
Була створена папка [WEB-INF], у якій міститься файл [web.xml] . Цей файл налаштовує веб-додаток:
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
<context-param>
<param-name>javax.faces.PROJECT_STAGE</param-name>
<param-value>Development</param-value>
</context-param>
<servlet>
<servlet-name>Faces Servlet</servlet-name>
<servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>Faces Servlet</servlet-name>
<URL-pattern>/faces/*</URL-pattern>
</servlet-mapping>
<session-config>
<session-timeout>
30
</session-timeout>
</session-config>
<welcome-file-list>
<welcome-file>faces/index.xhtml</welcome-file>
</welcome-file-list>
</web-app>
- рядки 7–10 визначають сервлет c.a.d — клас Java, здатний обробляти запити клієнтів. Додаток JSF працює наступним чином:
![]() |
Ця архітектура реалізує шаблон проектування MVC (Модель, Вигляд, Контролер). Нагадаємо те, що вже було сказано вище. Обробка запиту клієнта відбувається за такими чотирма етапами:
1 — запит — браузер клієнта надсилає запит до контролера [Faces Servlet]. Він обробляє всі запити клієнтів. Це вхідні ворота додатка. Це «C» у MVC,
2 — обробка — контролер C обробляє цей запит. Для цього він використовує обробники подій, специфічні для написаного додатка [2a]. Цим обробникам може знадобитися допомога бізнес-шару [2b]. Після обробки запиту клієнта можуть бути сформовані різні відповіді. Класичним прикладом є:
- сторінка з повідомленням про помилку, якщо запит не вдалося обробити належним чином;
- сторінка підтвердження в іншому випадку,
3 — навігація — контролер обирає відповідь (= вигляд), яку слід надіслати клієнту. Вибір відповіді, яку слід надіслати клієнту, вимагає кількох етапів:
- вибір Facelet, який згенерує відповідь. Це те, що називається поданням V, де V — це MVC. Цей вибір, як правило, залежить від результату виконання дії, яку запросив користувач;
- надання цьому Facelet даних, необхідних для генерації цієї відповіді. Адже вона найчастіше містить інформацію, обчислену контролером. Ця інформація утворює так звану модель M подання, де M — це MVC,
Отже, етап 3 полягає у виборі подання V та побудові моделі M, необхідної для нього.
4 — відповідь — контролер C надсилає запит обраному Facelet на відображення. Facelet використовує модель M, підготовлену контролером C, для ініціалізації динамічних частин відповіді, яку він повинен надіслати клієнту. Точна форма відповіді може бути різною: це може бути потік HTML, PDF, Excel тощо.
У проєкті JSF:
- контролер C — це сервлет [javax.faces.webapp.FacesServlet],
- представлення V реалізовані сторінками, що використовують технологію Facelets,
- моделі M та обробники подій реалізовані класами Java, які часто називають «backing beans» або, простіше кажучи, Beans.
Повернемося до вмісту файлу [web.xml):
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
<context-param>
<param-name>javax.faces.PROJECT_STAGE</param-name>
<param-value>Development</param-value>
</context-param>
<servlet>
<servlet-name>Faces Servlet</servlet-name>
<servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>Faces Servlet</servlet-name>
<URL-pattern>/faces/*</URL-pattern>
</servlet-mapping>
<session-config>
<session-timeout>
30
</session-timeout>
</session-config>
<welcome-file-list>
<welcome-file>faces/index.xhtml</welcome-file>
</welcome-file-list>
</web-app>
- рядки 12–15: тег <servlet-mapping> слугує для прив’язки сервлета до URL, запитуваного клієнтським браузером. Тут вказано, що запити URL у форматі [/faces/*] мають оброблятися сервлетом із назвою [Faces Servlet]. Цей сервлет визначено у рядках 7–10. Оскільки в файлі немає інших тегів <servlet-mapping>, це означає, що сервлет [Faces Servlet] оброблятиме лише запити URL у формі [/faces/*]. Ми бачили, що контекст додатка називався [/mv-jsf2-01]. Отже, запити клієнтів у форматі URL, які обробляє сервлет [Faces Servlet], матимуть вигляд [http://machine:port/mv-jsf2-01/faces/*]. Сторінки .html та .jsp за замовчуванням обробляються самим контейнером сервлетів, а не конкретним сервлетом. Адже контейнер сервлетів знає, як з ними працювати,
- рядки 7–10: визначають сервлет [Faces Servlet]. Оскільки всі прийняті запити URL перенаправляються до нього, він є контролером C моделі MVC,
- рядок 10: вказує, що сервлет має завантажуватися в пам’ять одразу після запуску веб-сервера. За замовчуванням сервлет завантажується лише після отримання першого запиту, що надходить до нього,
- рядки 3–6: визначають параметр, призначений для сервлета [Faces Servlet]. Параметр javax.faces.PROJECT_STAGE визначає етап, на якому перебуває проект, що виконується. На етапі Development сервлет [Faces Servlet] відображає повідомлення про помилки, корисні для налагодження. На етапі Production ці повідомлення більше не відображаються,
- рядки 17–19: тривалість сеансу в хвилинах. Клієнт взаємодіє з додатком через послідовність циклів «запит/відповідь». Кожен цикл використовує власне з’єднання TCP-IP, яке створюється заново з кожним новим циклом. Крім того, якщо клієнт C надсилає два запити D1 та D2, сервер S не має можливості дізнатися, що обидва запити належать одному й тому ж клієнту C. Сервер S не має інформації про клієнта. Це зумовлено використовуваним протоколом HTTP (HyperText Transport Protocol): клієнт взаємодіє з сервером через послідовність циклів «запит клієнта — відповідь сервера», використовуючи щоразу нове з’єднання TCP-IP. Такий протокол називають безстатусним. В інших протоколах, таких як, наприклад, FTP (File Transfer Protocol), клієнт C використовує одне й те саме з’єднання протягом усього діалогу з сервером S. Отже, з’єднання пов’язане з конкретним клієнтом. Сервер S завжди знає, з ким має справу. Щоб розпізнати, що запит належить певному клієнту, веб-сервер може використовувати техніку сеансу:
- під час першого запиту клієнта сервер S надсилає йому очікувану відповідь разом із токеном — випадковим набором символів, унікальним для цього клієнта;
- під час кожного наступного запиту клієнт C надсилає серверу S отриманий токен, що дозволяє серверу S його впізнати.
Тепер додаток має можливість попросити сервер зберегти інформацію, пов’язану з конкретним клієнтом. Це називається клієнтською сесією. У рядку 18 вказано, що тривалість сесії становить 30 хвилин. Це означає, що якщо клієнт C не надсилає нового запиту протягом 30 хвилин, його сесія припиняється, а інформація, що містилася в ній, втрачається. Під час наступного запиту все відбуватиметься так, ніби це новий клієнт, і розпочнеться нова сесія,
- рядки 21–23: список сторінок, які слід відобразити, коли користувач запитує контекст, не вказавши конкретну сторінку, наприклад, тут [http://machine:port/mv-jsf2-01]. У цьому випадку веб-сервер (а не сервлет) перевіряє, чи визначив додаток тег <welcome-file-list>. Якщо так, він відображає першу сторінку, знайдену у списку. Якщо її немає, — другу сторінку, і так далі, доки не буде знайдено існуючу сторінку. Тут, коли клієнт запитує URL [http://machine:port/mv-jsf2-01], йому буде надано URL [http://machine:port/mv-jsf2-01/index.xhtml].
2.3.5. Запуск проєкту
Під час виконання нового проєкту у браузері відображається такий результат:
![]() |
- у [1] контекст було запитано без вказівки документа,
- у [2], як уже пояснювалося, подається головна сторінка (welcome-file) [index.xhtml].
Можна з цікавістю поглянути на отриманий вихідний код [3]:
Ми отримали файл HTML. Усі теги <h:xx> з index.xhtml були перетворені на відповідні теги в HTML.
2.3.6. Локальне сховище Maven
Ми вже згадували, що Maven завантажує необхідні для проєкту залежності та зберігає їх локально. Цей локальний репозиторій можна переглянути:
![]() |
- у [1] вибираємо опцію [Window / Other / Maven Repository Browser],
- у [2] відкривається вкладка [Maven Repositories],
- у [3] містяться дві гілки: одна для локального репозиторію, інша — для центрального. Останній є надзвичайно великим. Щоб переглянути його вміст, потрібно оновити його індекс [4]. Це оновлення триває кілька десятків хвилин.
![]() |
- у [5] — бібліотеки локального репозиторію,
- в [6] міститься гілка [istia.st], яка відповідає [groupId] нашого проєкту,
- У [7] можна перейти до властивостей локального репозиторію,
- у [8] вказано шлях до локального репозиторію. Це корисно знати, оскільки іноді (рідко) Maven перестає використовувати останню версію проєкту. Ми вносимо зміни і помічаємо, що вони не враховуються. Тоді можна вручну видалити гілку з локального репозиторію, що відповідає нашому [groupId]. Це змушує Maven відтворити гілку на основі останньої версії проєкту.
2.3.7. Пошук артефакту за допомогою Maven
Тепер навчимося шукати артефакт за допомогою Maven. Почнемо зі списку поточних залежностей файлу [pom.xml]:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>istia.st</groupId>
<artifactId>mv-jsf2-01</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<name>mv-jsf2-01</name>
...
<dependencies>
<dependency>
<groupId>com.sun.faces</groupId>
<artifactId>jsf-api</artifactId>
<version>2.1.1-b04</version>
</dependency>
<dependency>
<groupId>com.sun.faces</groupId>
<artifactId>jsf-impl</artifactId>
<version>2.1.1-b04</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.1.2</version>
</dependency>
<dependency>
<groupId>taglibs</groupId>
<artifactId>standard</artifactId>
<version>1.1.2</version>
</dependency>
<dependency>
<groupId>javax</groupId>
<artifactId>javaee-web-api</artifactId>
<version>6.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
...
</build>
<repositories>
<repository>
<url>http://download.java.net/maven/2/</url>
<id>jsf20</id>
<layout>default</layout>
<name>Repository for library Library[jsf20]</name>
</repository>
<repository>
<url>http://repo1.maven.org/maven2/</url>
<id>jstl11</id>
<layout>default</layout>
<name>Repository for library Library[jstl11]</name>
</repository>
</repositories>
</project>
У рядках 13–40 визначено залежності, а в рядках 45–58 — репозиторії, де їх можна знайти, окрім центрального репозиторію, який завжди використовується. Ми змінимо залежності, щоб використовувати бібліотеки в їхніх найновіших версіях.
![]() |
Спочатку видалимо поточні залежності [1]. Після цього файл [pom.xml] буде змінено:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
...
<dependencies>
<dependency>
<groupId>javax</groupId>
<artifactId>javaee-web-api</artifactId>
<version>6.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
...
<repositories>
<repository>
<url>http://download.java.net/maven/2/</url>
<id>jsf20</id>
<layout>default</layout>
<name>Repository for library Library[jsf20]</name>
</repository>
<repository>
<url>http://repo1.maven.org/maven2/</url>
<id>jstl11</id>
<layout>default</layout>
<name>Repository for library Library[jstl11]</name>
</repository>
</repositories>
</project>
У рядках 5–12 видалені залежності більше не з’являються у файлі [pom.xml]. Тепер пошукаємо їх у репозиторіях Maven.
![]() |
- у [1] додається залежність до проєкту,
- у [2] потрібно вказати інформацію про шуканий артефакт (groupId, artifactId, версія, тип упаковки (Type) та область застосування (scope)). Спочатку вказуємо [groupId] [3],
- у полі [4] вводимо [espace], щоб відобразити список можливих артефактів. Тут це [jsf-api] та [jsf-impl]. Вибираємо [jsf-api],
- а в [5], діючи таким самим чином, вибираємо найновішу версію. Тип пакета — jar.
Таким чином ми діємо для всіх артефактів:
![]() | ![]() | ![]() | ![]() |
![]() |
У файлі [6] додані залежності відображаються в проєкті. Файл [pom.xml] відображає ці зміни:
<dependencies>
<dependency>
<groupId>com.sun.faces</groupId>
<artifactId>jsf-api</artifactId>
<version>2.1.7</version>
<type>jar</type>
</dependency>
<dependency>
<groupId>com.sun.faces</groupId>
<artifactId>jsf-impl</artifactId>
<version>2.1.7</version>
<type>jar</type>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
<type>jar</type>
</dependency>
<dependency>
<groupId>taglibs</groupId>
<artifactId>standard</artifactId>
<version>1.1.2</version>
<type>jar</type>
</dependency>
<dependency>
<groupId>javax</groupId>
<artifactId>javaee-web-api</artifactId>
<version>6.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
Припустимо, що ми не знаємо [groupId] потрібного артефакту. Наприклад, ми хочемо використовувати Hibernate як ORM (об’єктно-реляційний мапер), і це все, що ми знаємо. Тоді можна перейти на сайт [http://mvnrepository.com/]:
![]() |
У [1] можна ввести ключові слова. Введемо hibernate і запустимо пошук.
![]() |
- у [2] виберемо [groupId], org.hibernate та [artifactId], hibernate-core,
- з [3] виберемо версію 4.1.2-Final,
- з [4] ми отримуємо код Maven, який потрібно вставити у файл [pom.xml]. Ми це робимо.
<dependencies>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>4.1.2.Final</version>
</dependency>
<dependency>
<groupId>com.sun.faces</groupId>
<artifactId>jsf-api</artifactId>
<version>2.1.7</version>
<type>jar</type>
</dependency>
...
</dependencies>
Зберігаємо файл [pom.xml]. Після цього Maven починає завантажувати нові залежності. Проєкт змінюється наступним чином:
![]() |
- у [5], залежність [hibernate-core-4.1.2-Final]. У репозиторії, де його було знайдено, цей [artifactId] також описаний файлом [pom.xml]. Цей файл було прочитано, і Maven виявив, що [artifactId] має залежності. Він також завантажує їх. Він зробить це для кожного завантаженого [artifactId]. У підсумку у файлі [6] ми знаходимо залежності, які не запитували безпосередньо. Вони позначені іконкою, відмінною від тієї, що використовується для основного файлу [artifactId].
У цьому документі ми використовуємо Maven головним чином саме через цю особливість. Це позбавляє нас необхідності знати всі залежності бібліотеки, яку ми хочемо використовувати. Ми доручаємо їх управління Maven. Крім того, обмінюючись файлом [pom.xml] між розробниками, ми маємо гарантію, що кожен розробник використовує саме ті самі бібліотеки.
У наступних прикладах ми обмежимося наведенням використовуваного файлу [pom.xml]. Читачеві достатньо буде скористатися ним, щоб відтворити умови, описані в цьому документі. Крім того, проекти Maven підтримуються основними середовищами розробки Java (Eclipse, NetBeans, IntelliJ, JDeveloper). Тому читач зможе використовувати своє улюблене середовище для тестування прикладів.
2.4. Приклад mv-jsf2-02: обробник подій – інтернаціоналізація — навігація між сторінками
2.4.1. Додаток
Додаток виглядає так:
![]() |
- у [1] — головна сторінка додатка,
- на [2] — два посилання для зміни мови сторінок додатка,
- на [3] — посилання для переходу на іншу сторінку,
- при натисканні на [3] відображається сторінка [4],
- посилання [5] дозволяє повернутися на головну сторінку.
![]() |
- на головній сторінці [1] посилання [2] дозволяють змінити мову,
- на сторінці [3] — головна сторінка англійською мовою.
2.4.2. Проєкт NetBeans
Створимо новий веб-проект, як описано в розділі 2.3.1. Назвемо його mv-jsf2-02:
![]() |
- у [1] — це згенерований проєкт,
- на [2] видалено пакет [istia.st.mvjsf202] та файл [index.jsp],
- У файлі [3] було додано залежності Maven за допомогою наступного файлу [pom.xml]:
<dependencies>
<dependency>
<groupId>com.sun.faces</groupId>
<artifactId>jsf-api</artifactId>
<version>2.1.7</version>
</dependency>
<dependency>
<groupId>com.sun.faces</groupId>
<artifactId>jsf-impl</artifactId>
<version>2.1.7</version>
</dependency>
<dependency>
<groupId>javax</groupId>
<artifactId>javaee-web-api</artifactId>
<version>6.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
Додані залежності належать до фреймворку JSF. Достатньо скопіювати наведені вище рядки у файл [pom.xml] замість старих залежностей.
![]() |
- у [4, 5]: створюємо папку [src / main / resources] у вкладці [Files],
- у [6], у вкладці [Projects], що призвело до створення гілки [Other Sources].
Тепер у нас є проєкт JSF. У ньому ми створимо різні типи файлів:
- веб-сторінки у форматі XHTML,
- класи Java,
- файли повідомлень,
- файл конфігурації проєкту JSF.
Давайте розглянемо, як створити кожен із цих типів файлів:
![]() |
- у [1] ми створюємо сторінку JSF
- у [2] ми створюємо сторінку [index.xhtml] у форматі [Facelets] [3],
- у [4] було створено два файли: [index.xhtml] та [WEB-INF / web.xml].
Файл [web.xml] налаштовує додаток JSF. Він має такий вигляд:
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
<context-param>
<param-name>javax.faces.PROJECT_STAGE</param-name>
<param-value>Development</param-value>
</context-param>
<servlet>
<servlet-name>Faces Servlet</servlet-name>
<servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>Faces Servlet</servlet-name>
<URL-pattern>/faces/*</URL-pattern>
</servlet-mapping>
<session-config>
<session-timeout>
30
</session-timeout>
</session-config>
<welcome-file-list>
<welcome-file>faces/index.xhtml</welcome-file>
</welcome-file-list>
</web-app>
Ми вже розглядали цей файл у розділі 2.3.4. Нагадаємо його основні властивості:
- усі URL типу faces/* обробляються сервлетом [javax.faces.webapp.FacesServlet],
- сторінка [index.xhtml] є головною сторінкою додатка.
Створений файл [index.xhtml] має такий вигляд:
<?xml version='1.0' encoding='UTF-8' ?>
<!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">
<h:head>
<title>Facelet Title</title>
</h:head>
<h:body>
Hello from Facelets
</h:body>
</html>
Ми вже зустрічали цей файл у розділі 2.3.4.
Тепер створимо клас Java:
![]() |
- у [1] створюємо клас Java у гілці [Source Packages],
- у [2] ми надаємо йому ім’я та розміщуємо його в пакеті [3],
- у [4] створений клас з’являється у проєкті.
Код створеного класу є каркасом класу:
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package istia.st;
/**
*
* @author Serge Tahé
*/
public class Form {
}
Нарешті, створимо файл повідомлень:
- у [1], створення файлу [Properties],
- у [2] вказуємо ім’я файлу, а в [3] — його папку,
- у [4] файл [messages.properties] було створено.
Іноді для налаштування проєкту JSF необхідно створити файл [WEB-INF/faces-config.xml]. Цей файл був обов’язковим для JSF 1. Він є необов’язковим для JSF 2. Однак він необхідний, якщо сайт JSF є інтернаціоналізованим. Це буде мати місце надалі. Тому зараз ми покажемо, як створити цей файл конфігурації.
![]() |
- у [1] ми створюємо файл конфігурації JSF,
- у [2] вказуємо його ім’я, а в [3] — його папку,
- у [4] — створений файл.
Створений файл [faces-config.xml] має такий вигляд:
<?xml version='1.0' encoding='UTF-8'?>
<!-- =========== FULL CONFIGURATION FILE ================================== -->
<faces-config version="2.0"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_2_0.xsd">
</faces-config>
Кореневим тегом є <faces-config>. Тіло цього тегу порожнє. Нам доведеться його заповнити.
Тепер у нас є всі елементи для створення проекту JSF. У наступних прикладах ми представимо повний проект JSF, а потім детально розглянемо його елементи один за одним. Тепер ми представимо проект для пояснення таких понять:
- управління подіями у формі,
- інтернаціоналізації сторінок сайту JSF,
- навігації між сторінками.
Проєкт [mv-jsf2-02] виглядає наступним чином. Читач може знайти його на сайті з прикладами (див. параграф 1.2).
![]() |
- у [1], файли конфігурації проекту JSF,
- у [2] — сторінки JSF цього проєкту,
- у [3] — єдиний клас Java,
- у [4] — файли повідомлень.
2.4.3. Сторінка [index.xhtml]
Файл [index.xhtml] [1] надсилає сторінку [2] до браузера клієнта:
![]() |
Код, що генерує цю сторінку, такий:
<?xml version='1.0' encoding='UTF-8' ?>
<!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:f="http://java.sun.com/jsf/core">
<f:view locale="#{changeLocale.locale}">
<head>
...
</head>
<body>
....
</body>
</f:view>
</html>
- рядки 7–9: простори імен / бібліотеки тегів, що використовуються на сторінці. Теги з префіксом h є тегами HTML, тоді як теги з префіксом f є тегами, властивими JSF,
- рядок 10: тег <f:view> слугує для обмеження коду, який має обробити движок JSF, тобто того, в якому зустрічаються теги <f:xx>. Атрибут locale дозволяє вказати мову відображення сторінки. Тут ми використаємо дві мови — англійську та французьку. Значення атрибута `local` подається у вигляді виразу EL (Expression Language) #{expression}. Форма виразу може бути різною. Найчастіше ми будемо подавати його у формі `bean['clé']` або `bean.champ`. У наших прикладах bean буде або класом Java, або файлом повідомлень. У JSF 1 ці bean мали бути оголошені у файлі [faces-config.xml]. У JSF 2 це вже не є обов’язковим для класів Java. Тепер можна використовувати анотації, які перетворюють клас Java на bean, відомий JSF 2. Файл повідомлень повинен бути оголошений у файлі конфігурації [faces-config.xml].
2.4.4. Bean [changeLocale]
У виразі EL #{changeLocale.locale}:
- changeLocale — це ім’я біна, у даному випадку — класу Java ChangeLocale,
- locale — це поле класу ChangeLocale. Вираз обчислюється за допомогою [ChangeLocale].getLocale(). Загалом вираз #{bean.champ} обчислюється як [Bean].getChamp(), де [Bean] — це екземпляр класу Java, якому присвоєно ім’я bean та getChamp, гетер, пов’язаний з полем champ біна.
Клас ChangeLocale має такий вигляд:
package utils;
import java.io.Serializable;
import javax.faces.bean.ManagedBean;
import javax.enterprise.context.SessionScoped;
@ManagedBean
@SessionScoped
public class ChangeLocale implements Serializable{
// локалізація сторінок
private String locale="fr";
public ChangeLocale() {
}
...
public String getLocale() {
return locale;
}
}
- рядок 11: поле locale,
- рядок 17: його геттер,
- рядок 7: анотація ManagedBean робить клас Java ChangeLocale біном, який розпізнається JSF. Bean ідентифікується за іменем. Воно може бути задане атрибутом name анотації: @ManagedBean(name= "xx "). За відсутності атрибута name використовується ім’я класу, причому його перший символ перетворюється на малу літеру. Отже, ім’я біна ChangeLocale — changeLocale. Зверніть увагу на те, що анотація ManagedBean належить до пакета javax.faces.bean.ManagedBean, а не до пакета javax.annotations.ManagedBean.
- рядок 8: анотація SessionScoped визначає область дії біна. Існує кілька таких анотацій. Ми найчастіше використовуватимемо три наступні:
- RequestScoped: термін існування біна збігається з циклом «запит браузера — відповідь сервера». Якщо для обробки нового запиту від того самого або іншого браузера цей бін знову знадобиться, його буде створено заново,
- SessionScoped: термін існування біна дорівнює тривалості сеансу конкретного клієнта. Бін спочатку створюється для обробки одного із запитів цього клієнта. Потім він залишається в пам’яті протягом сеансу цього клієнта. Такий бін зазвичай зберігає дані, що належать конкретному клієнту. Він буде знищений, коли сеанс клієнта буде завершено,
- ApplicationScoped: термін існування біна дорівнює терміну існування самої програми. Бін із таким терміном існування найчастіше є спільним для всіх клієнтів програми. Зазвичай він ініціалізується на початку роботи програми.
Ці анотації існують у двох пакетах: javax.enterprise.context.SessionScoped (JSF 2) та javax.faces.bean.SessionScoped (JSF 1). Тут ми використовуємо пакет JSF 2. Це вимагає створення файлу [WEB-INF / beans.xml]:
![]() |
Цей файл автоматично генерується NetBeans під час імпортування пакета [javax.enterprise.context.SessionScoped]. Його вміст такий:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/beans_1_0.xsd">
</beans>
За межами кореневого тегу <beans> файл порожній. Цього достатньо. Потрібна лише його наявність.
Нарешті, слід зазначити, що клас [ChangeLocale] реалізує інтерфейс [Serializable]. Це є обов’язковою вимогою для бінів з областю дії Session, які веб-сервер може серіалізувати у файли. Пізніше ми повернемося до біна [ChangeLocale].
2.4.5. Файл повідомлень
Повернемося до файлу [index.xhtml]:
<?xml version='1.0' encoding='UTF-8' ?>
<!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:f="http://java.sun.com/jsf/core">
<f:view locale="#{changeLocale.locale}">
<head>
<title><h:outputText value="#{msg['welcome.titre']}" /></title>
</head>
<body>
...
</body>
</f:view>
</html>
- рядок 8: тег <h:outputText> відображає значення виразу EL #{msg['welcome.titre']} у формі #{bean['champ']}. bean — це або ім’я класу Java, або ім’я файлу повідомлень. У цьому випадку це ім’я файлу повідомлень. Останній має бути оголошений у файлі конфігурації [faces-config.xml]. Bean msg оголошується наступним чином:
<?xml version='1.0' encoding='UTF-8'?>
<!-- =========== FULL CONFIGURATION FILE ================================== -->
<faces-config version="2.0"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_2_0.xsd">
<application>
<resource-bundle>
<base-name>
messages
</base-name>
<var>msg</var>
</resource-bundle>
</application>
</faces-config>
- рядки 11–18: тег <application> слугує для налаштування додатка JSF,
- рядки 12–17: тег <resource-bundle> використовується для визначення ресурсів додатка, у даному випадку — файлу повідомлень,
- рядки 13–15: тег <base-name> визначає ім’я файлу повідомлень,
- рядок 14: файл матиме назву messages[_CodeLangue][_CodePays].properties. Тег <base-name> визначає лише першу частину імені. Решта є неявним. Може існувати кілька файлів повідомлень, по одному для кожної мови:
![]() |
- у [1] ми бачимо чотири файли повідомлень, що відповідають базовій назві messages, визначеній у [faces-config.xml],
- messages_fr.properties: містить повідомлення французькою мовою (код fr);
- messages_en.properties: містить повідомлення англійською мовою (код en);
- messages_es_ES.properties: містить повідомлення іспанською мовою (код es) Іспанії (код ES). Існують й інші варіанти іспанської мови, наприклад, болівійська (es_BO);
- messages.properties: використовується сервером, коли для мови комп’ютера, на якому він працює, немає відповідного файлу повідомлень. Наприклад, він використовувався б, якби програма працювала на комп’ютері в Німеччині, де мовою за замовчуванням є німецька (de). Оскільки файлу [messages_de.properties] не існує, програма використовуватиме файл [messages.properties],
- у [2]: коди мов підпадають під міжнародний стандарт,
- у [3]: те саме стосується кодів країн.
Ім'я файлу повідомлень визначається у рядку 14. Його шукатимуть у файлі Classpath цього проєкту. Якщо він знаходиться всередині пакета, то його потрібно вказати у рядку 14, наприклад ressources.messages, якщо файл [messages.properties] знаходиться у папці [ressources] проекту Classpath. Оскільки ім’я у рядку 14 не містить назви пакета, файл [messages.properties] слід розмістити в кореневій папці [src / main / resources]:
![]() |
У [1], на вкладці [Projects] проекту NetBeans, файл [messages.properties] представлений як список різних визначених версій повідомлень. Версії позначаються послідовністю від одного до трьох кодів [codeLangue_codePays_codeVariante]. У [1] використовувався лише код [codeLangue]: en — для англійської, fr — для французької. Кожна версія зберігається в окремому файлі у файловій системі.
У нашому прикладі файл повідомлень французькою мовою [messages_fr.properties] міститиме такі елементи:
welcome.titre=Tutoriel JSF (JavaServer Faces)
welcome.langue1=Fran\u00e7ais
welcome.langue2=Anglais
welcome.page1=Page 1
page1.titre=page1
page1.entete=Page 1
page1.welcome=Page d'accueil
А файл [messages_en.properties] матиме такий вигляд:
welcome.titre=JSF (JavaServer Faces) Tutorial
welcome.langue1=French
welcome.langue2=English
welcome.page1=Page 1
page1.titre=page1
page1.entete=Page 1
page1.welcome=Welcome page
Файл [messages.properties] ідентичний файлу [messages_en.properties]. У підсумку браузер клієнта матиме можливість вибору між сторінками французькою та англійською мовами.
Повернемося до файлу [faces-config.xml], який визначає файл повідомлень:
...
<application>
<resource-bundle>
<base-name>
messages
</base-name>
<var>msg</var>
</resource-bundle>
</application>
</faces-config>
У рядку 8 вказано, що на рядок у файлі повідомлень буде посилатися ідентифікатор msg на сторінках JSF. Цей ідентифікатор використовується у розглянутому файлі [index.xhtml]:
<?xml version='1.0' encoding='UTF-8' ?>
<!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:f="http://java.sun.com/jsf/core">
<f:view locale="#{changeLocale.locale}">
<head>
<title><h:outputText value="#{msg['welcome.titre']}" /></title>
</head>
<body>
...
</body>
</f:view>
</html>
Тег <h:outputText> у рядку 8 відобразить значення повідомлення (наявність ідентифікатора msg) з ключем welcome.titre. Це повідомлення шукається та знаходить у файлі [messages.properties] для мови, яка наразі є активною. Наприклад, для французької мови:
welcome.titre=Tutoriel JSF (JavaServer Faces)
Повідомлення має вигляд «ключ=значення». Рядок 8 файлу [index.xhtml] після обчислення виразу #{msg['welcome.titre']} набуває такого вигляду:
<title><h:outputText value="Tutoriel JSF (JavaServer Faces)" /></title>
Цей механізм файлів повідомлень дозволяє легко змінювати мову сторінок проекту JSF. Про це говорять як про інтернаціоналізацію проекту або, частіше, використовують її абревіатуру i18n, оскільки слово «інтернаціоналізація» починається на «i» і закінчується на «n», а між «i» і «n» є 18 літер.
2.4.6. Форма
Продовжимо вивчати вміст файлу [index.xhtml]:
<?xml version='1.0' encoding='UTF-8' ?>
<!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:f="http://java.sun.com/jsf/core">
<f:view locale="#{changeLocale.locale}">
<head>
<title><h:outputText value="#{msg['welcome.titre']}" /></title>
</head>
<body>
<h:form id="formulaire">
<h:panelGrid columns="2">
<h:commandLink value="#{msg['welcome.langue1']}" action="#{changeLocale.setFrenchLocale}"/>
<h:commandLink value="#{msg['welcome.langue2']}" action="#{changeLocale.setEnglishLocale}"/>
</h:panelGrid>
<h1><h:outputText value="#{msg['welcome.titre']}" /></h1>
<h:commandLink value="#{msg['welcome.page1']}" action="page1"/>
</h:form>
</body>
</f:view>
</html>
- рядки 11–18: тег <h:form> позначає початок форми. Форма зазвичай складається з:
- тегів полів введення (текст, перемикачі, прапорці, списки елементів тощо);
- тегів перевірки форми (кнопки, посилання). Саме за допомогою кнопки або посилання користувач надсилає введені дані на сервер, який їх оброблятиме,
Будь-який тег JSF можна ідентифікувати за допомогою атрибута id. Найчастіше можна обійтися без нього, і саме так зроблено в більшості тегів JSF, що використовуються тут. Проте цей атрибут є корисним у деяких випадках. У рядку 17 форма ідентифікується за ідентифікатором id. У цьому прикладі ідентифікатор форми не використовуватиметься, тому його можна було б опустити.
- рядки 18–21: тег <h:panelGrid> тут визначає таблицю HTML із двома стовпцями. Він породжує тег HTML <table>,
- форма містить три посилання, що запускають її обробку, у рядках 19, 20 та 23. Тег <h:commandLink> має щонайменше два атрибути:
- value: текст посилання;
- action: або рядок символів C, або посилання на метод, який після виконання повертає рядок символів C. Цей рядок символів C може бути:
- або ім’ям сторінки JSF у проєкті,
- або ім’ям, визначеним у правилах навігації файлу [faces-config.xml] та пов’язаним із сторінкою JSF проекту;
В обох випадках сторінка JSF відображається після виконання дії, визначеної атрибутом action.
Розглянемо механізм обробки форм на прикладі посилання в рядку 13:
<h:commandLink value="#{msg['welcome.langue1']}" action="#{changeLocale.setFrenchLocale}"/>}"/>
Спочатку використовується файл повідомлень, щоб замінити вираз #{msg['welcome.langue1']} на його значення. Після обчислення тег набуває такого вигляду:
<h:commandLink value="Français" action="#{changeLocale.setFrenchLocale}"/>}"/>
Переклад HTML цього тегу JSF буде таким:
<a href="<a href="view-source:http://localhost:8080/mv-jsf2-02/faces/page1.xhtml#">#</a>" onclick="mojarra.jsfcljs(document.getElementById('formulaire'),{'formulaire:j_idt8':'formulaire:j_idt8'},'');return false">Français</a>
що дасть такий візуальний вигляд:
![]() |
Зверніть увагу на атрибут onclick тегу HTML <a>. Коли користувач натисне на посилання [Français], буде виконано код JavaScript. Цей код вбудовано у сторінку, яку отримав браузер, і саме браузер його виконує. Код JavaScript широко використовується в технологіях JSF та AJAX (Asynchronous JavaScript And XML). Зазвичай його метою є покращення зручності користування та швидкості реагування веб-додатків. Найчастіше він генерується автоматично за допомогою програмних інструментів, і в такому разі розуміти його немає потреби. Однак іноді розробнику може знадобитися додати код JavaScript на свої сторінки JSF. У такому випадку знання JavaScript є необхідним.
Тут немає потреби розбиратися в коді JavaScript, згенерованому для тегу JSF <h:commandLink>. Проте можна відзначити два моменти:
- код JavaScript використовує ідентифікатор форми, який ми присвоїли тегу JSF <h:form>,
- JSF генерує автоматичні ідентифікатори для всіх тегів, у яких атрибут id не було визначено. Приклад цього можна побачити тут: j_idt8. Присвоєння тегам зрозумілих ідентифікаторів дозволяє краще зрозуміти згенерований код JavaScript, якщо це стане необхідним. Це, зокрема, стосується випадків, коли розробник повинен самостійно додати код JavaScript, який маніпулює компонентами сторінки. У такому разі йому потрібно знати ідентифікатори id цих компонентів.
Що відбудеться, коли користувач натисне на посилання [Français] на сторінці вище? Розглянемо архітектуру додатка JSF:
![]() |
Контролер [Faces Servlet] отримає запит від браузера клієнта у такому вигляді: HTTP:
- рядки 1–2: браузер запитує URL [http://localhost:8080/mv-jsf2-02/faces/index.xhtml]. Так відбувається завжди: дані, введені у форму JSF, спочатку отриману за допомогою URL та URLFormulaire, надсилаються до цієї самої URL. У браузера є два способи надсилання введених значень: GET та POST. За допомогою методу GET введені значення надсилаються браузером у запитуваний URL. У наведеному вище прикладі браузер міг би надіслати такий перший рядок:
GET /mv-jsf2-02/faces/index.xhtml?formulaire=formulaire&javax.faces.ViewState=-9139703055324497810%3A8197824608762605653&formulaire%3Aj_idt8=formulaire%3Aj_idt8 HTTP/1.1
За допомогою методу POST, який використовується тут, браузер надсилає серверу введені значення за допомогою рядка 6.
- рядок 3: вказує формат кодування значень форми,
- рядок 4: вказує розмір у байтах рядка 6,
- рядок 5: порожній рядок, що вказує на кінець заголовків HTTP та початок 126 байтів значень форми,
- рядок 6: значення форми у форматі element1=значення1&element2=значення2& ..., формат кодування визначений рядком 3. У цьому форматі кодування деякі символи замінюються на їхні шістнадцяткові значення. Це стосується останнього елемента:
formulaire=formulaire&javax.faces.ViewState=...&formulaire%3Aj_idt8=formulaire%3Aj_idt8
де %3A позначає символ :. Отже, на сервер надсилається рядок «форма:j_idt8=форма:j_idt8». Можливо, ви пам’ятаєте, що ми вже зустрічали ідентифікатор j_idt8, коли розглядали код HTML, згенерований для тегу
<h:commandLink value="#{msg['welcome.langue1']}" action="#{changeLocale.setFrenchLocale}"/>
Він був автоматично згенерований JSF. Для нас тут важливо те, що наявність цього ідентифікатора в рядку значень, надісланих клієнтським браузером, дозволяє JSF дізнатися, що на посилання [Français] було натиснуто. Тоді він використає наведений вище атрибут action, щоб вирішити, як обробити отриманий рядок. Атрибут action="#{changeLocale.setFrenchLocale}" вказує JSF, що запит клієнта має оброблятися методом [setFrenchLocale] об’єкта з назвою changeLocale. Нагадаємо, що цей bean був визначений за допомогою анотацій у класі Java [ChangeLocale]:
@ManagedBean
@SessionScoped
public class ChangeLocale implements Serializable{
Ім'я біна визначається атрибутом name анотації @ManagedBean. За відсутності цього атрибута як ім'я біна використовується ім'я класу, причому перший символ перетворюється на малу літеру.
Повернемося до запиту браузера:
![]() |
та до тегу <h:commandLink>, який згенерував посилання [Français], на яке ми натиснули:
<h:commandLink value="#{msg['welcome.langue1']}" action="#{changeLocale.setFrenchLocale}"/>
Контролер передасть запит браузера обробнику подій, визначеному атрибутом action тегу <h:commandLink>. Обробник подій M, на який посилається атрибут action команди <h:commandLink>, повинен мати такий сигнатур:
- він не приймає жодних параметрів. Ми побачимо, що він, тим не менш, може мати доступ до запиту клієнта,
- він повинен повернути результат C типу String. Цей символьний рядок C може бути:
- або ім’ям сторінки JSF у проєкті;
- або ім'ям, визначеним у правилах навігації файлу [faces-config.xml] і пов'язаним із сторінкою JSF проекту;
- або нульовим покажчиком, якщо клієнтський браузер не повинен переходити на іншу сторінку,
У наведеній вище архітектурі JSF контролер [Faces Servlet] використовуватиме рядок C, повернутий обробником подій, та, за необхідності, свій файл конфігурації [faces-config.xml], щоб визначити, яку сторінку JSF йому слід надіслати у відповідь клієнту [4].
У тезі
<h:commandLink value="#{msg['welcome.langue1']}" action="#{changeLocale.setFrenchLocale}"/>
обробник події «клацнути на посилання» [Français] — це метод [changeLocale.setFrenchLocale], де changeLocale — це екземпляр класу [utils.ChangeLocale] , який ми вже розглядали:
package utils;
import java.io.Serializable;
import javax.enterprise.context.SessionScoped;
import javax.faces.bean.ManagedBean;
@ManagedBean
@SessionScoped
public class ChangeLocale implements Serializable{
// локаль сторінок
private String locale="fr";
public ChangeLocale() {
}
public String setFrenchLocale(){
locale="fr";
return null;
}
public String setEnglishLocale(){
locale="en";
return null;
}
public String getLocale() {
return locale;
}
}
Метод setFrenchLocale дійсно має сигнатуру обробників подій. Нагадаємо, що обробник подій повинен обробити запит клієнта. Оскільки він не отримує параметрів, як він може отримати доступ до цього запиту? Існують різні способи зробити це:
- Bean B, який містить обробник подій сторінки JSF P, часто є тим самим, що містить модель M цієї сторінки. Це означає, що bean B містить поля, які будуть ініціалізовані значеннями, введеними на сторінці P. Це буде зроблено контролером [Faces Servlet] до того, як буде викликано обробник подій bean B. Отже, цей обробник матиме доступ, через поля bean B, до якого він належить, до значень, введених клієнтом у форму, і зможе їх обробляти.
- Статичний метод [FacesContext.getCurrentInstance()] типу [FacesContext] надає доступ до контексту виконання поточного запиту JSF, який є об’єктом типу [FacesContext]. Отриманий таким чином контекст виконання запиту дозволяє отримати доступ до параметрів, надісланих на сервер клієнтським браузером, за допомогою такого методу:
Якщо параметри, надіслані (POST) клієнтським браузером, є такими:
метод getRequestParameterMap() поверне такий словник:
ключ | значення |
форма | форма |
javax.faces.ViewState | ... |
форма:j_id_id21 | форма:j_id_id21 |
У тезі
<h:commandLink value="#{msg['welcome.langue1']}" action="#{changeLocale.setFrenchLocale}"/>
що очікується від обробника подій locale.setFrenchLocale? Ми хочемо, щоб він встановив мову, яку використовує додаток. У термінології Java це називається «локалізацією» додатка. Ця локалізація використовується тегом <f:view> на сторінці JSF [index.xhtml]:
<f:view locale="#{changeLocale.locale}">
...
</f:view>
Щоб перевести сторінку на французьку мову, достатньо, щоб атрибут locale мав значення fr. Щоб перевести її на англійську, потрібно задати йому значення en. Значення атрибута locale отримується за допомогою виразу [ChangeLocale].getLocale(). Цей вираз повертає значення поля locale класу [ChangeLocale]. Звідси випливає код методу [ChangeLocale].setFrenchLocale(), який повинен переводити сторінки на французьку мову:
public String setFrenchLocale(){
locale="fr";
return null;
}
Ми вже пояснювали, що обробник подій повинен повернути рядок символів типу C, який буде використаний методом [Faces Servlet] для пошуку сторінки JSF, яку потрібно надіслати у відповідь клієнтському браузеру. Якщо сторінка, яку потрібно повернути, є тією самою, що й та, яка зараз обробляється, обробник подій може просто повернути значення null. Саме це робиться тут у рядку 3: ми хочемо повернути ту саму сторінку [index.xhtml], але іншою мовою.
Повернемося до архітектури обробки запиту:
![]() |
Обробник подій changeLocale.setFrenchLocale був виконаний і повернув значення null контролеру [Faces Servlet]. Отже, контролер знову відобразить сторінку [index.xhtml]. Давайте ще раз розглянемо її:
<?xml version='1.0' encoding='UTF-8' ?>
<!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:f="http://java.sun.com/jsf/core">
<f:view locale="#{changeLocale.locale}">
<head>
<title><h:outputText value="#{msg['welcome.titre']}" /></title>
</head>
<body>
<h:form id="formulaire">
<h:panelGrid columns="2">
<h:commandLink value="#{msg['welcome.langue1']}" action="#{changeLocale.setFrenchLocale}"/>
<h:commandLink value="#{msg['welcome.langue2']}" action="#{changeLocale.setEnglishLocale}"/>
</h:panelGrid>
<h1><h:outputText value="#{msg['welcome.titre']}" /></h1>
<h:commandLink value="#{msg['welcome.page1']}" action="page1"/>
</h:form>
</body>
</f:view>
</html>
Кожного разу, коли обчислюється значення типу #{msg['...']}, використовується один із файлів повідомлень [messages.properties]. Використовується той файл, який відповідає «локалізації» сторінки (рядок 6). Оскільки обробник подій changeLocale.setFrenchLocale визначає цю локалізацію як fr, буде використано файл [messages_fr.properties]. Клік на посилання [Anglais] (рядок 14) змінить локалізацію на en (див. метод changeLocale.setEnglishLocale). Тоді буде використано файл [messages_en.properties], і сторінка з’явиться англійською мовою:
![]() | ![]() |
Кожного разу, коли відображається сторінка [index.xhtml], виконується тег <f:view>:
<f:view locale="#{changeLocale.locale}">
і, отже, метод [ChangeLocale].getLocale() виконується знову. Оскільки ми задали для нашого біна область дії «Сесія»:
@ManagedBean
@SessionScoped
public class ChangeLocale implements Serializable{
локалізація, виконана під час запиту, зберігається для наступних запитів.
Нам залишилося розглянути останній елемент сторінки [index.xhtml]:
<?xml version='1.0' encoding='UTF-8' ?>
<!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:f="http://java.sun.com/jsf/core">
<f:view locale="#{changeLocale.locale}">
<head>
<title><h:outputText value="#{msg['welcome.titre']}" /></title>
</head>
<body>
<h:form id="formulaire">
<h:panelGrid columns="2">
<h:commandLink value="#{msg['welcome.langue1']}" action="#{changeLocale.setFrenchLocale}"/>
<h:commandLink value="#{msg['welcome.langue2']}" action="#{changeLocale.setEnglishLocale}"/>
</h:panelGrid>
<h1><h:outputText value="#{msg['welcome.titre']}" /></h1>
<h:commandLink value="#{msg['welcome.page1']}" action="page1"/>
</h:form>
</body>
</f:view>
</html>
Тег <h:commandLink> у рядку 17 має атрибут action, значення якого дорівнює символьному рядку. У цьому випадку для обробки сторінки не викликається жоден обробник подій. Відразу відбувається перехід на сторінку [page1.xhtml]. Розглянемо роботу додатка в цьому випадку використання:
![]() |
Користувач натискає на посилання [Page 1]. Форма надсилається до контролера [Faces Servlet]. Він розпізнає в отриманому запиті, що було натиснуто на посилання [Page 1]. Він перевіряє відповідний тег:
<h:commandLink value="#{msg['welcome.page1']}" action="page1"/>
З посиланням не пов’язано жодного обробника подій. Контролер [Faces Servlet] одразу переходить до вищезазначеного етапу [3] і відображає сторінку [page1.xhtml]:
![]() | ![]() |
2.4.7. Сторінка JSF [page1.xhtml]
Сторінка [page1.xhtml] надсилає клієнтському браузеру такий потік:
![]() |
Код, що генерує цю сторінку, такий:
<?xml version='1.0' encoding='UTF-8' ?>
<!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:f="http://java.sun.com/jsf/core">
<f:view locale="#{changeLocale.locale}">
<head>
<title><h:outputText value="#{msg['page1.titre']}"/></title>
</head>
<body>
<h1><h:outputText value="#{msg['page1.entete']}"/></h1>
<h:form>
<h:commandLink value="#{msg['page1.welcome']}" action="index"/>
</h:form>
</body>
</f:view>
</html>
На цій сторінці немає нічого, що б не було вже пояснено. Читач зможе встановити відповідність між кодом JSF та сторінкою, надісланою до браузера клієнта. Посилання для повернення на головну сторінку:
<h:commandLink value="#{msg['page1.welcome']}" action="index"/>
приведе до відображення сторінки [index.xhtml].
2.4.8. Виконання проекту
Наш проект тепер завершений. Ми можемо його скомпілювати (Clean and Build):
![]() |
- компіляція проекту створює у вкладці [Files] папку [target]. У ній знаходиться архів проекту [mv-jsf2-02-1.0-SNAPSHOT.war]. Саме цей архів розгортається на сервері,
- у [WEB-INF / classes] та [2] містяться скомпільовані класи з папки [Source Packages] проекту, а також файли, що знаходилися у гілці [Other Sources] (у даному випадку — файли повідомлень),
- у [WEB-INF / lib] [3] містяться бібліотеки проєкту,
- у кореневому каталозі гілок [WEB-INF] та [4] знаходяться файли конфігурації проєкту,
![]() |
- у кореневому каталозі архіву [5] містяться сторінки JSF, які були у гілці [Web Pages] проекту,
- після побудови проекту його можна запустити [6]. Він буде запущений відповідно до його конфігурації виконання [7],
- сервер Tomcat буде запущений, якщо він ще не запущений ([8]),
- архів [mv-jsf2-02-1.0-SNAPSHOT.war] буде завантажено на сервер. Це називається розгортанням проекту на сервері додатків,
- у [9] під час виконання з’являється запит на запуск браузера. Він запитає контекст додатка [10], c.a.d. URL, [http://localhost:8080/mv-jsf2-02]. Згідно з правилами файлу [web.xml] (див. стор. 44), саме файл [faces/index.xhtml] буде надано клієнтському браузеру. Оскільки файл URL має формат [/faces/*], його оброблятиме контролер [Faces Servlet] (див. [web.xml], стор. 44). Цей контролер обробить сторінку та надішле такий потік HTML:
![]() |
- Контролер [Faces Servlet] оброблятиме події, що відбуватимуться на цій сторінці.
2.4.9. Файл конфігурації [faces-config.xml]
Ми використовували такий файл [faces-config.xml]:
<?xml version='1.0' encoding='UTF-8'?>
<!-- =========== FULL CONFIGURATION FILE ================================== -->
<faces-config version="2.0"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_2_0.xsd">
<application>
<resource-bundle>
<base-name>
messages
</base-name>
<var>msg</var>
</resource-bundle>
</application>
</faces-config>
Це мінімальний файл для інтернаціоналізованого додатка JSF 2. Тут ми використали нові можливості JSF 2 порівняно з JSF 1:
- декларація бінів та їхньої області дії за допомогою анотацій @ManagedBean, @RequestScoped, @SessionScoped, @ApplicationScoped,
- переходити між сторінками, використовуючи як ключі навігації назви сторінок XHTML без суфікса xhtml.
Можливо, ви не захочете використовувати ці можливості та оголосите ці елементи проєкту JSF у [faces-config.xml] так само, як у JSF 1. У цьому випадку файл [faces-config.xml] може виглядати так:
<?xml version='1.0' encoding='UTF-8'?>
<!-- =========== FULL CONFIGURATION FILE ================================== -->
<faces-config version="2.0"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_2_0.xsd">
<!-- додаток -->
<application>
<resource-bundle>
<base-name>
messages
</base-name>
<var>msg</var>
</resource-bundle>
</application>
<!-- керовані компоненти -->
<managed-bean>
<managed-bean-name>changeLocale</managed-bean-name>
<managed-bean-class>utils.ChangeLocale</managed-bean-class>
<managed-bean-scope>session</managed-bean-scope>
</managed-bean>
<!-- навігація -->
<navigation-rule>
<description/>
<from-view-id>/index.xhtml</from-view-id>
<navigation-case>
<from-outcome>p1</from-outcome>
<to-view-id>/page1.xhtml</to-view-id>
</navigation-case>
</navigation-rule>
<navigation-rule>
<description/>
<from-view-id>/page1.xhtml</from-view-id>
<navigation-case>
<from-outcome>welcome</from-outcome>
<to-view-id>/index.xhtml</to-view-id>
</navigation-case>
</navigation-rule>
</faces-config>
- рядки 20–24: оголошення біна changeLocale:
- рядок 21: ім’я біна;
- рядок 22: повна назва класу, пов’язаного з біном;
- рядок 23: область дії біна. Можливі значення: request, session, application,
- рядки 27–34: оголошення правила навігації:
- рядок 28: можна описати правило. Тут цього не зроблено;
- рядок 29: сторінка, з якої здійснюється перехід (відправна точка);
- рядки 30–33: випадок навігації. Їх може бути кілька;
- рядок 31: ключ навігації;
- рядок 32: сторінка, на яку здійснюється перехід.
Правила навігації можна відобразити у більш наочній формі. Під час редагування файлу [faces-config.xml] можна скористатися вкладкою [PageFlow]:
![]() |
Припустимо, що ми використовуємо попередній файл [faces-config.xml]. Як зміниться наш додаток?
- у класі [ChangeLocale] анотації @ManagedBean та @SessionScoped зникнуть, оскільки тепер бін оголошено у [faces-config],
- перехід за посиланням з [index.xhtml] на [page1.xhtml] виглядатиме так:
<h:commandLink value="#{msg['welcome.page1']}" action="p1"/>
Атрибуту action присвоюється ключ навігації p1, визначений у [faces-config],
- перехід за посиланням з [page1.xhtml] на [index.xhtml] матиме вигляд:
<h:commandLink value="#{msg['page1.welcome']}" action="welcome"/>
Атрибуту action присвоюється ключ навігації welcome, визначений у [faces-config],
- методи setFrenchLocale та setEnglishLocale, які повинні повертати ключ навігації, не потребують змін, оскільки вони повертали значення null, щоб вказати, що користувач залишається на тій самій сторінці.
2.4.10. Висновок
Повернемося до проекту NetBeans, який ми написали:
![]() |
Цей проєкт має таку архітектуру:
![]() |
У кожному проєкті JSF ми знайдемо такі елементи:
- сторінки JSF [A], які надсилаються [4] до браузерів клієнтів за допомогою контролера [Faces Servlet] [3],
- файли повідомлень [C], які дозволяють змінювати мову сторінок JSF,
- класів Java [B], які обробляють події, що відбуваються у клієнтському браузері [2a, 2b], та/або слугують шаблонами для сторінок JSF [3]. Найчастіше шари [métier] та [DAO] розробляються та тестуються окремо. У такому разі шар [web] тестується разом із фіктивним шаром [métier]. Якщо шари [métier] та [DAO] доступні, найчастіше працюють з їхніми архівами .jar.
- конфігураційні файли [D] для зв’язування цих різних елементів між собою. Файл [web.xml] було описано на сторінці 44, і його рідко змінюватимуть. Те саме стосується файлу [faces-config], де ми завжди використовуватимемо спрощену версію.
2.5. Приклад mv-jsf2-03: форма введення даних — компоненти JSF
Відтепер ми більше не будемо показувати процес створення проєкту. Ми презентуємо готові проєкти та пояснюємо їхнє функціонування. Читач може завантажити всі приклади з веб-сайту цього документа (див. параграф 1.2).
2.5.1. Додаток
Додаток має єдиний вигляд:
![]() |
Додаток демонструє основні компоненти JSF, які можна використовувати у формі введення даних:
- у стовпці [1] вказано назву використовуваного тегу JSF / HTML,
- у стовпці [2] наведено приклад введення даних для кожного з тегів,
- у стовпці [3] відображаються значення біна, що слугує шаблоном для сторінки,
- введені дані у стовпці [2] перевіряються за допомогою кнопки [4]. Ця перевірка лише оновлює шаблонний bean сторінки. Потім повертається та сама сторінка. Тому після підтвердження у стовпці [3] відображаються нові значення шаблону-біна, що дозволяє користувачеві перевірити вплив введених даних на шаблон сторінки.
2.5.2. Проєкт NetBeans
Проект NetBeans для цього додатка виглядає так:
![]() |
- у [1] — файли конфігурації проекту JSF,
- у [2] — єдина сторінка проєкту: index.xhtml,
- у [3] — таблицю стилів [styles.css] для налаштування зовнішнього вигляду сторінки [index.xhtml]
- у [4] — класи Java проекту,
- у [5] — файл повідомлень додатка двома мовами: французькою та англійською.
2.5.3. Файл [pom.xml]
Ми наводимо лише залежності:
<dependencies>
<dependency>
<groupId>com.sun.faces</groupId>
<artifactId>jsf-api</artifactId>
<version>2.1.7</version>
</dependency>
<dependency>
<groupId>com.sun.faces</groupId>
<artifactId>jsf-impl</artifactId>
<version>2.1.7</version>
</dependency>
<dependency>
<groupId>javax</groupId>
<artifactId>javaee-web-api</artifactId>
<version>6.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
Це залежності, необхідні для проекту JSF. У наступних прикладах цей файл буде представлено лише тоді, коли він зміниться.
2.5.4. Файл [web.xml]
Файл [web.xml] було налаштовано так, щоб сторінка [index.xhtml] була головною сторінкою проєкту:
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
<context-param>
<param-name>javax.faces.STATE_SAVING_METHOD</param-name>
<param-value>client</param-value>
</context-param>
<context-param>
<param-name>javax.faces.PROJECT_STAGE</param-name>
<param-value>Development</param-value>
</context-param>
<context-param>
<param-name>javax.faces.FACELETS_SKIP_COMMENTS</param-name>
<param-value>true</param-value>
</context-param>
<servlet>
<servlet-name>Faces Servlet</servlet-name>
<servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>Faces Servlet</servlet-name>
<url-pattern>/faces/*</url-pattern>
</servlet-mapping>
<session-config>
<session-timeout>
30
</session-timeout>
</session-config>
<welcome-file-list>
<welcome-file>faces/index.xhtml</welcome-file>
</welcome-file-list>
</web-app>
- рядок 30: сторінка [index.xhtml] є головною сторінкою,
- рядки 11–14: параметр для сервлету [Faces Servlet]. Він вимагає, щоб коментарі у фаселеті такого типу:
<!-- мови -->
ігнорувалися. Без цього параметра коментарі спричиняють проблеми, які важко зрозуміти,
- рядки 3–6: параметр для сервлету [Faces Servlet], який буде пояснено трохи далі.
2.5.5. Файл [faces-config.xml]
Файл [faces-config.xml] цього додатка має такий вигляд:
<?xml version='1.0' encoding='UTF-8'?>
<!-- =========== FULL CONFIGURATION FILE ================================== -->
<faces-config version="2.0"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_2_0.xsd">
<application>
<resource-bundle>
<base-name>
messages
</base-name>
<var>msg</var>
</resource-bundle>
</application>
</faces-config>
- рядки 11–16: налаштовують файл повідомлень додатка.
2.5.6. Файл повідомлень [messages.properties]
Файли повідомлень (див. [5] на скріншоті проєкту) мають такий вигляд:
[messages_fr.properties]
form.langue1=Fran\u00e7ais
form.langue2=Anglais
form.titre=Java Server Faces - les tags
form.headerCol1=Type
form.headerCol2=Champs de saisie
form.headerCol3=Valeurs du modèle de la page
form.loginPrompt=login :
form.passwdPrompt=mot de passe :
form.descPrompt=description :
form.selectOneListBox1Prompt=choix unique :
form.selectOneListBox2Prompt=choix unique :
form.selectManyListBoxPrompt=choix multiple :
form.selectOneMenuPrompt=choix unique :
form.selectManyMenuPrompt=choix multiple :
form.selectBooleanCheckboxPrompt=marié(e) :
form.selectManyCheckboxPrompt=couleurs préférées :
form.selectOneRadioPrompt=moyen de transport préféré :
form.submitText=Valider
form.buttonRazText=Raz
Ці повідомлення відображаються в таких місцях сторінки:
![]() |
Англійська версія повідомлень така:
[messages_en.properties]
form.langue1=French
form.langue2=English
form.titre=Java Server Faces - the tags
form.headerCol1=Input Type
form.headerCol2=Input Fields
form.headerCol3=Page Model Values
form.loginPrompt=login :
form.passwdPrompt=password :
form.descPrompt=description :
form.selectOneListBox1Prompt=unique choice :
form.selectOneListBox2Prompt=unique choice :
form.selectManyListBoxPrompt=multiple choice :
form.selectOneMenuPrompt=unique choice :
form.selectManyMenuPrompt=multiple choice :
form.selectBooleanCheckboxPrompt=married :
form.selectManyCheckboxPrompt=preferred colors :
form.selectOneRadioPrompt=preferred transport means :
form.submitText=Submit
form.buttonRazText=Reset
2.5.7. Шаблон [Form.java] сторінки [index.xhtml]
![]() |
У наведеному вище проєкті клас [Form.java] слугуватиме шаблоном або бекінгом-біном для сторінок JSF та [index.xhtml]. Проілюструємо це поняття шаблону на прикладі, взятому зі сторінки [index.xhtml]:
<!-- рядок 1 -->
<h:outputText value="inputText" styleClass="info"/>
<h:panelGroup>
<h:outputText value="#{msg['form.loginPrompt']}"/>
<h:inputText id="inputText" value="#{form.inputText}"/>
</h:panelGroup>
<h:outputText value="#{form.inputText}"/>
Під час першого запиту сторінки [index.xhtml] наведений вище код генерує 2-й рядок таблиці введених даних:
![]() |
У рядку 2 відображається поле [1], у рядках 3–6 — поле [2], у рядку 7 — поле [3].
У рядках 5 і 7 використовується вираз EL, що задіює форму-бін, визначену в класі [Form.java], таким чином:
package forms;
import javax.enterprise.context.RequestScoped;
import javax.faces.bean.ManagedBean;
@ManagedBean
@RequestScoped
public class Form {
- у рядку 7 визначається безіменний bean. Отже, це буде назва класу, що починається з малої літери: form,
- біан має область дії request. Це означає, що в циклі «запит клієнта — відповідь сервера» він створюється, коли це потрібно для запиту, і видаляється після надання відповіді клієнту.
У наведеному нижче коді сторінки [index.xhtml]:
<!-- рядок 1 -->
<h:outputText value="inputText" styleClass="info"/>
<h:panelGroup>
<h:outputText value="#{msg['form.loginPrompt']}"/>
<h:inputText id="inputText" value="#{form.inputText}"/>
</h:panelGroup>
<h:outputText value="#{form.inputText}"/>
у рядках 5 та 7 використовується значення inputText біну form. Щоб зрозуміти зв’язки між сторінкою P та її моделлю M, слід повернутися до циклу «запит клієнта — відповідь сервера», що є характерною рисою веб-додатку:
![]() |
Слід розрізняти випадок, коли сторінка P надсилається у відповідь браузеру (етап 4), наприклад під час початкового запиту сторінки, та випадок, коли користувач викликав подію на сторінці P, яка обробляється контролером [Faces Servlet] (етап 1).
Ці два випадки можна розрізнити, розглядаючи їх з точки зору браузера:
- під час початкового запиту сторінки браузер виконує операцію GET над URL сторінки,
- під час відправлення значень, введених на сторінці, браузер виконує операцію POST над URL сторінки.
В обох випадках запитується одна й та сама операція URL. Залежно від характеру запиту GET або POST, що надходить від браузера, обробка запиту буде відрізнятися.
[cas 1 – demande initiale de la page P]
Браузер запитує URL сторінки з GET. Контролер [Faces Servlet] перейде безпосередньо до етапу [4] формування відповіді, і сторінка [index.xhtml] буде надіслана клієнту. Контролер JSF вимагатиме відображення кожного тегу на сторінці. Розглянемо приклад із рядка 5 коду [index.xhtml]:
<h:inputText id="inputText" value="#{form.inputText}"/>
Тег JSF <h:inputText value="значення"/> породжує тег HTML <input type="text" value="значення"/>. Клас, відповідальний за обробку цього тегу, зустрічає вираз #{form.inputText}, який він повинен обчислити:
- якщо bean-форма ще не існує, її створюють шляхом інстанціювання класу forms.Form,
- вираз #{form.inputText} обчислюється шляхом виклику методу form.getInputText(),
- текст <input id="formulaire:inputText" type="text" name="formulaire:inputText" value="текст" /> вставляється у потік HTML, який буде надіслано клієнту, якщо припустити, що метод form.getInputText() повернув рядок «текст». Крім того, JSF присвоїть ім’я (name) компоненту HTML, розміщеному у потоці. Ця назва формується на основі ідентифікаторів id проаналізованого компонента JSF та його батьківських компонентів, у даному випадку тегу <h:form id="formulaire"/>.
Слід зауважити, що якщо на сторінці P використовується вираз #{M.champ}, де M — це bean-модель сторінки P, то він повинен мати публічний метод getChamp(). Тип, що повертається цим методом, повинен мати можливість бути перетвореним у тип String. Можливим і поширеним варіантом моделі M є наступний:
де T — це тип, який можна перетворити на тип String, можливо, за допомогою методу toString.
Також у випадку відображення сторінки P обробка рядка:
<h:outputText value="#{form.inputText}"/>
буде аналогічним, і буде створено такий потік HTML:
Внутрішньо на сервері сторінка P представлена у вигляді дерева компонентів, що відображає дерево тегів сторінки, надісланої клієнту. Це дерево ми називатимемо «видом» або « » стану сторінки. Цей стан зберігається в пам’яті. Це може відбуватися двома способами, залежно від налаштувань у файлі [web.xml] додатка:
<web-app ...>
...
<context-param>
<param-name>javax.faces.STATE_SAVING_METHOD</param-name>
<param-value>client</param-value>
</context-param>
<servlet>
<servlet-name>Faces Servlet</servlet-name>
<servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
...
</web-app>
Рядки 7–11 визначають контролер [Faces Servlet]. Його можна налаштувати за допомогою різних тегів <context-param>, зокрема тих, що містяться в рядках 3–6, які вказують, що стан сторінки має зберігатися на стороні клієнта (у браузері). Інше можливе значення, у рядку 5, — «server», що вказує на збереження стану на сервері. Це значення є значенням за замовчуванням.
Коли стан сторінки зберігається на стороні клієнта, контролер JSF додає до кожної сторінки HTML, яку він надсилає, приховане поле, значенням якого є поточний стан сторінки. Це приховане поле має такий вигляд:
<input type="hidden" name="javax.faces.ViewState" id="javax.faces.ViewState" value="H4sIAAAAAAAAANV...Bnoz8dqAAA=" />
Його значення у кодованому вигляді відображає стан сторінки, надісланої клієнту. Важливо розуміти, що це приховане поле є частиною форми сторінки і, отже, буде включено до значень, які браузер надсилає під час відправлення форми. На основі цього прихованого поля контролер JSF здатний відновити вигляд сторінки таким, яким вона була надіслана клієнту.
Коли стан сторінки зберігається на сервері, стан сторінки, надісланої клієнту, зберігається в сесії клієнта. Коли браузер клієнта надсилатиме значення, введені у форму, він також надсилатиме свій сесійний токен. На основі цього контролер JSF відновить стан сторінки, надісланої клієнту, та відобразить її.
Для кодування стану сторінки JSF може знадобитися кілька сотень байтів. Оскільки цей стан зберігається для кожного користувача додатка, при великій кількості користувачів можуть виникнути проблеми з пам’яттю. З цієї причини ми вирішили зберігати стан сторінки на стороні клієнта (див. [web.xml], параграф 2.5.4, сторінка 66).
[cas 2 – traitement de la page P]
![]() |
Ми перебуваємо на етапі [1], описаному вище, де контролер [Faces Servlet] отримає запит POST від браузера клієнта, якому він раніше надіслав сторінку [index.xhtml]. Маємо справу з обробкою події на сторінці. Перш ніж подія зможе бути оброблена в [2a], відбудеться кілька етапів. Цикл обробки запиту POST контролером JSF є таким:
FEDCBA

- у [A] завдяки прихованому полю javax.faces.ViewState відтворюється вигляд, який спочатку був надісланий до браузера клієнта. Тут компоненти сторінки відновлюють значення, які вони мали на надісланій сторінці. Наш компонент inputText відновлює своє значення «текст»,
- у [B] значення, надіслані клієнтським браузером, використовуються для оновлення компонентів подання. Отже, якщо у полі введення HTML, названому inputText, користувач ввів «jean», значення «jean» замінює значення «текст». Відтепер представлення відображає сторінку в тому вигляді, в якому її змінив користувач, а не в тому, в якому вона була надіслана до браузера,
- у [C] перевіряються відправлені значення. Припустимо, що попередній компонент inputText є полем введення віку. Введене значення має бути цілим числом. Значення, відправлені браузером, завжди мають тип String. Їхній кінцевий тип у моделі M, пов’язаній зі сторінкою P, може бути зовсім іншим. У такому разі відбувається перетворення типу String в інший тип T. Це перетворення може завершитися невдачею. У цьому випадку цикл запит/відповідь завершується, і сторінка P, побудована у форматі [B], повертається клієнтському браузеру разом із повідомленнями про помилки, якщо автор сторінки P їх передбачив. Слід зазначити, що користувач бачить сторінку саме такою, якою він її ввів, без жодних зусиль з боку розробника. У іншій технології, наприклад JSP, розробник повинен самостійно відтворити сторінку P із значеннями, введеними користувачем. Значення компонента також може пройти процес перевірки. Знову ж таки, на прикладі компонента inputText, який є полем для введення віку, введене значення має бути не просто цілим числом, а цілим числом, що входить у діапазон [1,N]. Якщо введене значення проходить етап перетворення, воно може не пройти етап перевірки. У цьому випадку цикл «запит/відповідь» також завершується, і сторінка P, побудована за шаблоном [B], повертається до браузера клієнта,
- У [D], якщо всі компоненти сторінки P пройдуть етап перетворення та перевірки, їхні значення будуть присвоєні моделі M сторінки P. Якщо значення поля введення, згенерованого на основі наступного тегу:
<h:inputText value="#{form.inputText}"/>
дорівнює «jean», то це значення буде присвоєно формі сторінки шляхом виконання коду form.setInputText("jean"). Слід зауважити, що в моделі M сторінки P приватні поля M, які зберігають значення поля введення P, повинні мати метод set,
- після оновлення моделі M сторінки P значеннями, відправленими через POST, подія, що викликала POST на сторінці P, може бути оброблена. Це етап [E]. Слід зауважити, що якщо обробник цієї події належить до біну M, він має доступ до значень форми P, які були збережені в полях цього самого біну.
- На етапі [E] контролеру JSF буде передано ключ навігації. У наших прикладах це завжди буде назва сторінки XHTML, яку потрібно відобразити, без суфікса .xhtml. Це етап [F]. Інший спосіб полягає у поверненні ключа навігації, який буде шукатися у файлі [faces-config.xml]. Ми вже описали цей випадок.
З вищесказаного можна зробити висновок, що:
- сторінка P відображає поля C своєї моделі M за допомогою методів [M].getC(),
- поля C моделі M сторінки P ініціалізуються значеннями, введеними на сторінці P, за допомогою методів [M].setC(saisie). На цьому етапі можуть відбуватися процеси перетворення та перевірки, які можуть завершитися невдачею. У цьому випадку подія, що спричинила виклик POST на сторінці P, не обробляється, і сторінка повертається клієнту в тому вигляді, в якому він її ввів.
Шаблон [Form.java] для сторінки [index.xhtml] матиме такий вигляд:
package forms;
import javax.enterprise.context.RequestScoped;
import javax.faces.bean.ManagedBean;
@ManagedBean
@RequestScoped
public class Form {
/** Створює новий екземпляр форми */
public Form() {
}
// поля форми
private String inputText="texte";
private String inputSecret="secret";
private String inputTextArea="ligne1\nligne2\n";
private String selectOneListBox1="2";
private String selectOneListBox2="3";
private String[] selectManyListBox=new String[]{"1","3"};
private String selectOneMenu="1";
private String[] selectManyMenu=new String[]{"1","2"};
private String inputHidden="initial";
private boolean selectBooleanCheckbox=true;
private String[] selectManyCheckbox=new String[]{"1","3"};
private String selectOneRadio="2";
// події
public String submit(){
return null;
}
// методи getter та setter
...
}
Поля рядків 16–27 використовуються в таких місцях форми:
![]() |
2.5.8. Сторінка [index.xhtml]
Сторінка [index.xhtml], яка генерує попередній вигляд, має такий вигляд:
<?xml version='1.0' encoding='UTF-8' ?>
<!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:f="http://java.sun.com/jsf/core">
<f:view locale="#{changeLocale.locale}">
<h:head>
<title>JSF</title>
<h:outputStylesheet library="css" name="styles.css"/>
</h:head>
<h:body style="background-image: url('${request.contextPath}/resources/images/standard.jpg');">
<h:form id="formulaire">
<!-- мови -->
<h:panelGrid columns="2">
<h:commandLink value="#{msg['form.langue1']}" action="#{changeLocale.setFrenchLocale}"/>
<h:commandLink value="#{msg['form.langue2']}" action="#{changeLocale.setEnglishLocale}"/>
</h:panelGrid>
<h1><h:outputText value="#{msg['form.titre']}"/></h1>
<h:panelGrid columnClasses="col1,col2,col3" columns="3" border="1">
<!-- заголовки -->
<h:outputText value="#{msg['form.headerCol1']}" styleClass="entete"/>
<h:outputText value="#{msg['form.headerCol2']}" styleClass="entete"/>
<h:outputText value="#{msg['form.headerCol3']}" styleClass="entete"/>
<!-- рядок 1 -->
...
<!-- рядок 2 -->
...
<!-- рядок 3 -->
...
<!-- рядок 4 -->
...
<!-- рядок 5 -->
...
<!-- рядок 6 -->
...
<!-- рядок 7 -->
...
<!-- рядок 8 -->
...
<!-- рядок 9 -->
...
<!-- рядок 10 -->
...
<!-- рядок 11 -->
...
<!-- рядок 12 -->
...
</h:panelGrid>
<p>
<h:commandButton type="submit" id="submit" value="#{msg['form.submitText']}"/>
</p>
</h:form>
</h:body>
</f:view>
</html>
Ми послідовно розглянемо основні компоненти цієї сторінки. Звернемо увагу на загальну структуру форми JSF:
<?xml version='1.0' encoding='UTF-8' ?>
<!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:f="http://java.sun.com/jsf/core">
<f:view ...>
<h:head>
...
</h:head>
<h:body ...>
<h:form id="formulaire">
...
<h:commandButton type="submit" id="submit" value="#{msg['form.submitText']}"/>
...
</h:form>
</h:body>
</f:view>
</html>
Компоненти форми повинні знаходитися всередині тегу <h:form> (рядки 12–16). Тег <f:view> (рядки 7–18) необхідний у разі інтернаціоналізації додатка. Крім того, форма повинна мати спосіб відправки (POST), найчастіше це посилання або кнопка, як у рядку 14. Відправлення може також відбуватися за допомогою різних подій (зміна вибору у списку, зміна активного поля, введення символу у поле введення тощо).
2.5.9. Стиль форми
Щоб зробити стовпці таблиці форми більш читабельними, до неї додається таблиця стилів:
<f:view locale="#{changeLocale.locale}">
<h:head>
<title>JSF</title>
<h:outputStylesheet library="css" name="styles.css"/>
</h:head>
- рядок 4: таблиця стилів сторінки визначається всередині тегу HTML <head> за допомогою тегу:
<h:outputStylesheet library="css" name="styles.css"/>
Таблиця стилів буде шукатися у папці [resources]:
![]() |
У тезі:
<h:outputStylesheet library="css" name="styles.css"/>
- library — це назва папки, що містить таблицю стилів,
- name — це назва таблиці стилів.
Давайте розглянемо приклад використання цього стильового аркуша:
<h:panelGrid columnClasses="col1,col2,col3" columns="3" border="1">
Тег <h:panelGrid columns="3"/> визначає таблицю з трьома стовпцями. Атрибут columnClasses дозволяє задати стиль для цих стовпців. Значення col1, col2, col3 атрибута columnClasses позначають відповідні стилі стовпців 1, 2 та 3 таблиці. Ці стилі шукаються у таблиці стилів сторінки:
.info{
font-family: Arial,Helvetica,sans-serif;
font-size: 14px;
font-weight: bold
}
.col1{
background-color: #ccccff
}
.col2{
background-color: #ffcccc
}
.col3{
background-color: #ffcc66
}
.entete{
font-family: 'Times New Roman',Times,serif;
font-size: 14px;
font-weight: bold
}
- рядки 7–9: стиль із назвою col1,
- рядки 11–13: стиль із назвою col2,
- рядки 15–17: стиль із назвою col3,
Ці три стилі визначають колір фону кожного зі стовпців.
- рядки 19–23: стиль «entete» використовується для визначення стилю тексту в першому рядку таблиці:
<!-- заголовки -->
<h:outputText value="#{msg['form.headerCol1']}" styleClass="entete"/>
<h:outputText value="#{msg['form.headerCol2']}" styleClass="entete"/>
<h:outputText value="#{msg['form.headerCol3']}" styleClass="entete"/>
- рядки 1–5: стиль «info» використовується для визначення стилю тексту в першому стовпці таблиці:
<!-- рядок 1 -->
<h:outputText value="inputText" styleClass="info"/>
Ми не будемо детально зупинятися на використанні таблиць стилів, оскільки ця тема заслуговує на окрему книгу, а їх розробкою часто займаються фахівці. Проте ми вирішили використати одну, мінімалістичну, щоб нагадати, що їх використання є необхідним.
Тепер давайте подивимося, як було визначено фонове зображення сторінки:
<h:body style="background-image: url('${request.contextPath}/resources/images/standard.jpg');">
Фонове зображення задається атрибутом style тегу <h:body>. Цей атрибут дозволяє задавати елементи стилю. Фонове зображення знаходиться у папці [resources/images/standard.jpg]:
![]() |
Це зображення отримується за допомогою URL [/mv-jsf2-03/resources/images/standard.jpg]. Отже, можна написати:
<h:body style="background-image: url('mv-jsf2-03/resources/images/standard.jpg');">
/mv-jsf2-03 — це контекст додатка. Цей контекст встановлюється адміністратором веб-сервера і тому може змінюватися. Цей контекст можна отримати за допомогою виразу EL ${request.contextPath}. Тому краще використовувати такий атрибут style:
style="background-image: url('${request.contextPath}/resources/images/standard.jpg');"
який буде дійсним незалежно від контексту.
2.5.10. Два цикли «запит клієнта — відповідь сервера» для форми
Повернімося до того, що вже було пояснено в параграфі 2.5.7 у загальному випадку, і застосуймо це до розглянутої форми. Її буде протестовано в класичному середовищі JSF:
![]() |
Тут не буде ні обробників подій, ні шару [métier]. Отже, етапи [2x] не існуватимуть. Розрізнятимемо випадок, коли форма F спочатку запитується браузером, та випадок, коли користувач викликав подію у формі F, і ця подія обробляється контролером [Faces Servlet]. Існують два різних цикли «запит клієнта — відповідь сервера».
- перший, що відповідає початковому запиту сторінки, ініціюється операцією GET браузера над елементом URL форми,
- другий, що відповідає відправленню значень, введених на сторінці, ініціюється операцією POST щодо тієї самої операції URL.
Залежно від типу запиту GET або POST, що надходить від браузера, обробка запиту контролером [Faces Servlet] відрізняється.
[cas 1 – demande initiale du formulaire F]
Браузер запитує URL сторінки з GET. Контролер [Faces Servlet] перейде безпосередньо до етапу [4] формування відповіді. Форма [index.xhtml] буде ініціалізована за допомогою її шаблону [Form.java] і надіслана клієнту, який отримає таке зображення:

Обмін даними між клієнтом і сервером HTTP у цьому випадку відбувається наступним чином:
Запит HTTP від клієнта:
У рядку 1 бачимо GET від браузера.
Відповідь HTTP від сервера:
Хоча це тут не показано, за рядком 7 йде порожній рядок і код HTML з форми. Саме цей код браузер інтерпретує та відображає.
[cas 2 – traitement des valeurs saisies dans le formulaire F]
Користувач заповнює форму та підтверджує її натисканням кнопки [Valider]. Після цього браузер надсилає запит на URL форми разом із POST. Контролер [Faces Servlet] обробляє цей запит, оновлює модель [Form.java] форми [index.xhtml] і знову відправляє форму [index.xhtml], оновлену за цією новою моделлю. Розглянемо цей цикл на прикладі:

На малюнку вище користувач ввів дані та підтвердив їх. У відповідь він отримує таке вікно:

Обмін даними між клієнтом і сервером за кодом HTTP у цьому випадку виглядає наступним чином:
Запит HTTP від клієнта:
У рядку 1 — запит POST, зроблений браузером. У рядку 14 — значення, введені користувачем. Наприклад, тут можна побачити текст, введений у поле введення:
У рядку 14 було відправлено приховане поле javax.faces.ViewState. Це поле представляє у закодованому вигляді стан форми, таким, яким вона була спочатку надіслана браузеру під час його початкового запиту GET.
Відповідь сервера HTTP:
Хоча це тут не показано, за рядком 6 йде порожній рядок і код HTML оновленої форми, створеної на основі нового шаблону, отриманого з POST.
Тепер розглянемо різні компоненти цієї форми.
2.5.11. Тег <h:inputText>
Тег <h:inputText> генерує тег HTML <input type="text" ...>.
Розглянемо такий код:
<!-- рядок 1 -->
<h:outputText value="inputText" styleClass="info"/>
<h:panelGroup>
<h:outputText value="#{msg['form.loginPrompt']}"/>
<h:inputText id="inputText" value="#{form.inputText}"/>
</h:panelGroup>
<h:outputText value="#{form.inputText}"/>
та його шаблон [Form.java]:
private String inputText="texte";
public String getInputText() {
return inputText;
}
public void setInputText(String inputText) {
this.inputText = inputText;
}
Коли сторінка [index.html] запитується вперше, отримуємо таку сторінку:
- рядок 2 коду XHTML генерує [1],
- тег <h:panelGroup> (рядки 3–6) дозволяє об’єднати кілька елементів в одну комірку таблиці, згенерованої тегом <h:panelGrid> у рядку 20 повного коду сторінки (див. параграф 2.5.8). Текст [2] генерується рядком 4. Поле введення [3] генерується рядком [5]. Тут для генерації тексту поля введення було використано метод getInputText з [Form.java] (рядки 3–5 коду Java),
- а рядок 7 коду XHTML генерує [4]. Знову використовується метод getInputText з [Form.java] для генерації тексту [4].
Потік HTML, згенерований сторінкою XHTML, має такий вигляд:
<tr>
<td class="col1"><span class="info">inputText</span></td>
<td class="col2">login : <input id="formulaire:inputText" type="text" name="formulaire:inputText" value="texte" /></td>
<td class="col3">texte</td>
</tr>
Теги HTML <tr> та <td> генеруються тегом <h:panelGrid>, який використовується для створення таблиці форми.
Тепер введімо значення у поле введення [1] та підтвердімо форму за допомогою кнопки [Valider] [2]. У відповідь ми отримаємо сторінку [3, 4]:
![]() |
Значення поля [1] надсилається таким чином:
У [2] форма підтверджується за допомогою такої кнопки:
<h:commandButton id="submit" type="submit" value="#{msg['form.submitText']}"/>
Тег <h:commandButton> не має атрибута action. У цьому випадку не викликається жоден обробник подій і не застосовується жодне правило навігації. Після обробки повертається та сама сторінка. Давайте ще раз розглянемо цикл її обробки:
ABCDEF

- у [A] сторінка P відновлюється такою, якою вона була надіслана. Це означає, що компонент з ідентифікатором inputText відновлюється зі своїм початковим значенням «текст»,
- у [B] значення, надіслані браузером (введені користувачем), присвоюються компонентам сторінки P. Тут компонент з ідентифікатором inputText отримує значення «новий текст»,
- у [C] відбуваються перетворення та перевірки. Тут їх немає. У моделі M поле, пов’язане з компонентом з ідентифікатором inputText, є таким:
private String inputText="texte";
Оскільки введені значення мають тип String, перетворення не потрібно. Крім того, жодних правил перевірки не було створено. Ми створимо їх пізніше.
- у [D] введені значення присвоюються шаблону. Поле inputText з [Form.java] отримує значення «новий текст»,
- у [E] нічого не відбувається, оскільки до кнопки [Valider] не було прив’язано жодного обробника подій.
- У [F] сторінка P знову надсилається клієнту, оскільки кнопка [Valider] не має атрибута action. Після цього виконуються наступні рядки з [index.xhtml]:
<!-- рядок 1 -->
<h:outputText value="inputText" styleClass="info"/>
<h:panelGroup>
<h:outputText value="#{msg['form.loginPrompt']}"/>
<h:inputText id="inputText" value="#{form.inputText}"/>
</h:panelGroup>
<h:outputText value="#{form.inputText}"/>
У рядках 5 і 7 використовується значення поля inputText шаблону, яке тепер має вигляд «новий текст». Звідси й отримане відображення:
![]()
2.5.12. Тег <h:inputSecret>
Тег <h:inputSecret> генерує тег HTML <input type="password" ...>. Це поле введення, аналогічне полю тегу JSF <h:inputText>, за винятком того, що кожен символ, введений користувачем, візуально замінюється символом *.
Розглянемо такий код:
<!-- рядок 2 -->
<h:outputText value="inputSecret" styleClass="info"/>
<h:panelGroup>
<h:outputText value="#{msg['form.passwdPrompt']}"/>
<h:inputSecret id="inputSecret" value="#{form.inputSecret}"/>
</h:panelGroup>
<h:outputText value="#{form.inputSecret}"/>
та його шаблон у [Form.java]:
private String inputSecret="secret";
Коли сторінка [index.xhtml] запитується вперше, отримуємо таку сторінку:
- рядок 2 коду XHTML генерує [1]
- Текст [2] генерується рядком 4. Поле введення [3] генерується рядком [5]. Зазвичай для генерації тексту поля введення слід було б використати метод getInputSecret з [Form.java]. Виняток становить випадок, коли це поле є полем типу «пароль». Тег <h:inputSecret> призначений лише для зчитування введених даних, а не для їхнього відображення.
- Рядок 7 коду XHTML генерує [4]. Тут метод getInputSecret з [Form.java] було використано для генерації тексту [4] (див. рядок 1 коду Java).
Потік HTML, згенерований сторінкою XHTML, має такий вигляд:
<tr>
<td class="col1"><span class="info">inputSecret</span></td>
<td class="col2">mot de passe : <input id="formulaire:inputSecret" type="password" name="formulaire:inputSecret" value="" /></td>
<td class="col3">secret</td>
</tr>
- рядок 3: тег HTML <input type= "password " .../>, згенерований тегом JSF <h:inputSecret>
Тепер введімо значення у поле введення [1] та підтвердімо форму за допомогою кнопки [Valider] [2]. У відповідь ми отримаємо сторінку [3]:
![]() |
Значення поля [1] надсилається таким чином:
Перевірка форми за допомогою [2] призвела до оновлення шаблону [Form.java] за допомогою запису [1]. Поле inputSecret у [Form.java] отримало значення mdp. Оскільки у формі [index.xhtml] не було визначено жодних правил навігації та жодних обробників подій, вона знову відображається після оновлення її шаблону. У результаті ми повертаємося до відображення, яке було під час початкового запиту сторінки [index.xhtml], де просто значення поля inputSecret у шаблоні змінилося на [3].
2.5.13. Тег <h:inputTextArea>
Тег <h:inputTextArea> генерує тег HTML <textarea ...>текст</textarea>. Це поле введення, аналогічне полю тегу JSF <h:inputText>, за винятком того, що тут можна вводити кілька рядків тексту.
Розглянемо такий код:
<!-- рядок 3 -->
<h:outputText value="inputTextArea" styleClass="info"/>
<h:panelGroup>
<h:outputText value="#{msg['form.descPrompt']}"/>
<h:inputTextarea id="inputTextArea" value="#{form.inputTextArea}" rows="4"/>
</h:panelGroup>
<h:outputText value="#{form.inputTextArea}"/>
та його шаблон у [Form.java]:
private String inputTextArea="ligne1\nligne2\n";
Коли сторінка [index.xhtml] запитується вперше, отримуємо таку сторінку:
![]() |
- рядок 2 коду XHTML генерує [1],
- текст [2] генерується рядком 4. Поле введення [3] генерується рядком [5]. Його вміст було згенеровано шляхом виклику методу getInputTextArea моделі, який повернув значення, визначене у рядку 1 наведеного вище коду Java,
- рядок 7 коду XHTML генерує [4]. Тут знову було використано метод getInputTextArea з [Form.java]. Рядок «рядок1\nрядок2» містив символи перенесення рядка \n. Вони там залишилися. Але, вставлені в потік HTML, вони відображаються браузерами як пробіли. Тег HTML <textarea>, який відображає [3], правильно інтерпретує символи перенесення рядка.
Потік HTML, згенерований сторінкою XHTML, має такий вигляд:
<tr>
<td class="col1"><span class="info">inputTextArea</span></td>
<td class="col2">description : <textarea id="formulaire:inputTextArea" name="formulaire:inputTextArea" rows="4">ligne1
ligne2
</textarea></td>
<td class="col3">ligne1
ligne2
</td>
</tr>
- рядки 3–5: тег HTML <textarea>...</textarea>, згенерована тегом JSF <h:inputTextArea>
Тепер введімо значення в поле введення [1] і підтвердімо форму за допомогою кнопки [Valider] [2]. У відповідь ми отримаємо сторінку [3]:
![]() |
Значення відправленого поля [1] таке:
Перевірка форми за допомогою [2] призвела до оновлення шаблону [Form.java] за допомогою запису [1]. Поле textArea у [Form.java] отримало значення «Посібник JSF\nчастина1». Повторне відображення [index.xhtml] показує, що поле textArea шаблону було успішно оновлено до [3].
2.5.14. Тег <h:selectOneListBox>
Тег <h:selectOneListBox> генерує тег HTML <select>...</select>. Візуально він створює випадаючий список або список із смугою прокрутки.
Розглянемо такий код:
<!-- рядок 4 -->
<h:outputText value="selectOneListBox (size=1)" styleClass="info"/>
<h:panelGroup>
<h:outputText value="#{msg['form.selectOneListBox1Prompt']}"/>
<h:selectOneListbox id="selectOneListBox1" value="#{form.selectOneListBox1}" size="1">
<f:selectItem itemValue="1" itemLabel="un"/>
<f:selectItem itemValue="2" itemLabel="deux"/>
<f:selectItem itemValue="3" itemLabel="trois"/>
</h:selectOneListbox>
</h:panelGroup>
<h:outputText value="#{form.selectOneListBox1}"/>
та його шаблон у [Form.java]:
private String selectOneListBox1="2";
Коли сторінка [index.xhtml] запитується вперше, отримуємо таку сторінку:
![]() |
- рядок 2 коду XHTML генерує [1]
- Текст [2] генерується рядком 4. Список, що розгортається, [3] генерується рядками [5-9]. Саме значення атрибута size="1" зумовлює те, що список відображає лише один елемент. Якщо цей атрибут відсутній, значенням за замовчуванням атрибута size є 1. Елементи списку були згенеровані тегами <f:selectItem> у рядках 6–8. Ці теги мають такий синтаксис:
<f:selectItem itemValue="valeur" itemLabel="texte"/>
Значення атрибута itemLabel — це те, що відображається у списку. Значення атрибута itemValue є значенням елемента. Саме це значення буде надіслано до контролера [Faces Servlet], якщо елемент буде вибрано у випадаючому списку.
Елемент, що відображається в [3], було визначено шляхом виклику методу getSelectOneListBox1() (рядок 5). Отриманий результат «2» (рядок 1 коду Java) призвів до того, що було відображено елемент із рядка 7 випадаючого списку, оскільки його атрибут itemValue має значення «2»,
- рядок 11 коду XHTML генерує [4]. Тут знову було використано метод getSelectOneListBox1 з [Form.java].
Потік HTML, згенерований сторінкою XHTML, має такий вигляд:
<tr>
<td class="col1"><span class="info">selectOneListBox (size=1)</span></td>
<td class="col2">choix unique : <select id="formulaire:selectOneListBox1" name="formulaire:selectOneListBox1" size="1">
<option value="1">un</option>
<option value="2" selected="selected">deux</option>
<option value="3">trois</option>
</select></td>
<td class="col3">2</td>
</tr>
- рядки 3 і 7: тег HTML <select ...>...</select>, згенерований тегом JSF <h:selectOneListBox>,
- рядки 4–6: теги HTML <option ...> ... </option>, згенеровані тегами JSF <f:selectItem>,
- рядок 5: те, що елемент із значенням value="2" вибрано зі списку, відображається наявністю атрибута selected="selected".
Тепер нижче виберемо [1] нове значення зі списку та підтвердимо форму за допомогою кнопки [Valider] [2]. У відповідь ми отримаємо сторінку [3]:
![]() |
Значення поля [1], надіслане у запиті, є таким:
Перевірка форми за допомогою [2] призвела до оновлення шаблону [Form.java] за допомогою запису [1]. Елемент HTML
<option value="3">trois</option>
був обраний. Браузер надіслав рядок «3» як значення компонента JSF, що сформував список, що розгортається:
<h:selectOneListbox id="selectOneListBox1" value="#{form.selectOneListBox1}" size="1">
Контролер JSF використає метод setSelectOneListBox1("3") для оновлення моделі випадаючого списку. Отже, після цього оновлення поле моделі [Form.java]
private String selectOneListBox1;
тепер містить значення «3».
Коли сторінка [index.xhtml] знову відображається після обробки, це значення зумовлює відображення [3,4], наведеного вище:
- вона визначає елемент випадаючого списку, який має відображатися — [3],
- значення поля selectOneListBox1 відображається у [4].
Розглянемо варіант тегу <h:selectOneListBox>:
<!-- рядок 5 -->
<h:outputText value="selectOneListBox (size=3)" styleClass="info"/>
<h:panelGroup>
<h:outputText value="#{msg['form.selectOneListBox2Prompt']}"/>
<h:selectOneListbox id="selectOneListBox2" value="#{form.selectOneListBox2}" size="3">
<f:selectItem itemValue="1" itemLabel="un"/>
<f:selectItem itemValue="2" itemLabel="deux"/>
<f:selectItem itemValue="3" itemLabel="trois"/>
<f:selectItem itemValue="4" itemLabel="quatre"/>
<f:selectItem itemValue="5" itemLabel="cinq"/>
</h:selectOneListbox>
</h:panelGroup>
<h:outputText value="#{form.selectOneListBox2}"/>
Шаблон у [Form.java] для тегу <h:selectOneListBox> у рядку 5 має такий вигляд:
private String selectOneListBox2="3";
Коли сторінка [index.xhtml] запитується вперше, отримується така сторінка:
![]() |
- рядок 2 коду XHTML генерує [1],
- текст [2] генерується рядком 4. Список із смугою прокрутки [3] генерується рядками [5-11]. Саме значення атрибута size= "3 " зумовлює те, що ми маємо список із повзунком, а не випадаючий список. Елементи списку були згенеровані тегами <f:selectItem> у рядках 6–8,
Елемент, вибраний у [3], було визначено шляхом виклику методу getSelectOneListBox2() (рядок 5). Отриманий результат «3» (рядок 1 коду Java) призвів до того, що було відображено елемент із рядка 8 списку, оскільки його атрибут itemValue має значення «3»,
- рядок 13 коду XHTML генерує [4]. Тут знову було використано метод getSelectOneListBox2 з [Form.java].
Потік HTML, згенерований сторінкою XHTML, має такий вигляд:
<tr>
<td class="col1"><span class="info">selectOneListBox (size=3)</span></td>
<td class="col2">choix unique : <select id="formulaire:selectOneListBox2" name="formulaire:selectOneListBox2" size="3">
<option value="1">un</option>
<option value="2">deux</option>
<option value="3" selected="selected">trois</option>
<option value="4">quatre</option>
<option value="5">cinq</option>
</select></td>
<td class="col3">3</td>
</tr>
- рядок 6: те, що елемент із значенням value="3" вибрано зі списку, відображається наявністю атрибута selected="selected".
Тепер нижче виберемо [1] нове значення зі списку та підтвердимо форму за допомогою кнопки [Valider] [2]. У відповідь ми отримаємо сторінку [3]:
![]() |
Значення, надіслане для поля [1], таке:
Перевірка форми за допомогою [2] призвела до оновлення шаблону [Form.java] за допомогою запису [1]. Елемент HTML
<option value="5">cinq</option>
був обраний. Браузер надіслав рядок «5» як значення компонента JSF, що сформував список, що розгортається:
<h:selectOneListbox id="selectOneListBox2" value="#{form.selectOneListBox2}" size="3">
Контролер JSF використає метод setSelectOneListBox2("5") для оновлення шаблону списку. Отже, після цього оновлення поле
private String selectOneListBox2;
тепер містить значення «5».
Коли сторінка [index.xhtml] знову відображається після обробки, це значення зумовлює відображення [3,4], наведеного вище:
- воно визначає елемент списку, який має бути вибрано — [3],
- значення поля selectOneListBox2 відображається як [4].
2.5.15. Тег <h:selectManyListBox>
Тег <h:selectmanyListBox> генерує тег <select multiple="multiple">...</select>, який дозволяє користувачеві вибрати кілька елементів зі списку.
Розглянемо такий код:
<!-- рядок 6 -->
<h:outputText value="selectManyListBox (size=3)" styleClass="info"/>
<h:panelGroup>
<h:outputText value="#{msg['form.selectManyListBoxPrompt']}"/>
<h:selectManyListbox id="selectManyListBox" value="#{form.selectManyListBox}" size="3">
<f:selectItem itemValue="1" itemLabel="un"/>
<f:selectItem itemValue="2" itemLabel="deux"/>
<f:selectItem itemValue="3" itemLabel="trois"/>
<f:selectItem itemValue="4" itemLabel="quatre"/>
<f:selectItem itemValue="5" itemLabel="cinq"/>
</h:selectManyListbox>
<p><input type="button" value="#{msg['form.buttonRazText']}" onclick="this.form['formulaire:selectManyListBox'].selectedIndex=-1;" /></p>
</h:panelGroup>
<h:outputText value="#{form.selectManyListBoxValue}"/>
та його шаблон у [Form.java]:
private String[] selectManyListBox=new String[]{"1","3"};
Коли сторінка [index.xhtml] запитується вперше, отримуємо таку сторінку:
![]() |
- рядок 2 коду XHTML генерує [1]
- Текст [2] генерується рядком 4. Список [3] генерується рядками [5-11]. Атрибут size="3" забезпечує, що список у певний момент відображає три з цих елементів. Елементи, вибрані зі списку, були визначені шляхом виклику методу getSelectManyListBox() (рядок 5) Java-моделі. Отриманий результат {"1","3"} (рядок 1 Java-коду) є масивом елементів типу String. Кожен із цих елементів слугує для вибору одного з елементів списку. У даному випадку будуть вибрані елементи з рядків 6 і 10, атрибут itemValue яких міститься в масиві {"1","3"}. Саме це і показує [3].
- рядок 14 коду XHTML генерує [4]. Тут використовується не метод getSelectManyListBox з Java-моделі списку, а наступний метод getSelectManyListBoxValue:
private String[] selectManyListBox=new String[]{"1","3"};
...
// гетери та сеттери
public String getSelectManyListBoxValue(){
return getValue(selectManyListBox);
}
private String getValue(String[] chaines){
String value="[";
for(String chaine : chaines){
value+=" "+chaine;
}
return value+"]";
}
Якби було викликано метод getSelectManyListBox, ми б отримали масив String. Щоб включити цей елемент у потік HTML, контролер мав би викликати його метод toString. Однак цей метод для масиву лише повертає його «хеш-код», а не список його елементів, як нам потрібно. Тому ми використовуємо наведений вище метод getSelectManyListBoxValue, щоб отримати рядок, що представляє вміст масиву,
- а 12-й рядок коду XHTML генерує кнопку [5]. При натисканні на цю кнопку виконується код JavaScript атрибута onclick. Він буде вбудований у сторінку HTML, яка буде згенерована кодом JSF. Щоб це зрозуміти, нам потрібно знати точну природу цієї сторінки.
Потік HTML, згенерований сторінкою XHTML, має такий вигляд:
<tr>
<td class="col1"><span class="info">selectManyListBox (size=3)</span></td>
<td class="col2">choix multiple : <select id="formulaire:selectManyListBox" name="formulaire:selectManyListBox" multiple="multiple" size="3">
<option value="1" selected="selected">un</option>
<option value="2">deux</option>
<option value="3" selected="selected">trois</option>
<option value="4">quatre</option>
<option value="5">cinq</option>
</select>
<p><input type="button" value="Raz" onclick="this.form['formulaire:selectManyListBox'].selectedIndex=-1;" /></p>
</td>
<td class="col3">[ 1 3]</td>
</tr>
- рядки 3 і 9: тег HTML <select multiple="multiple"...>...</select>, згенерований тегом JSF <h:selectManyListBox>. Саме наявність атрибута multiple вказує на те, що ми маємо справу зі списком із можливістю множинного вибору,
- а те, що моделлю списку є масив рядків {"1","3"}, зумовлює, що елементи списку в рядках 4 (value="1") та 6 (value="3") мають атрибут selected="selected",
- рядок 10: при натисканні на кнопку [Raz] виконується код JavaScript атрибута onclick. Сторінка представлена в браузері у вигляді дерева об’єктів, яке часто називають DOM (Document Object Model). Кожен об’єкт дерева доступний для коду JavaScript через його атрибут name. Список у рядку 3 коду HTML, наведеного вище, називається formulaire:selectManyListBox. Саму форму можна позначати по-різному. Тут вона позначається за допомогою нотації this.form, де this позначає кнопку [Raz], а this.form — форму, в якій знаходиться ця кнопка. Список «formulaire:selectManyListBox» знаходиться в цій самій формі. Отже, позначення this.form['formulaire:selectManyListBox'] вказує на розташування списку в дереві компонентів форми. Об’єкт, що представляє список, має атрибут selectedIndex, значенням якого є номер елемента, вибраного у списку. Цей номер починається з 0, що позначає перший елемент списку. Значення -1 означає, що в списку не вибрано жодного елемента. Код JavaScript, який присвоює атрибуту selectedIndex значення -1, призводить до скасування вибору всіх елементів списку, якщо такі були.
Тепер нижче виберемо [1] нові значення зі списку (щоб вибрати кілька елементів зі списку, утримуйте клавішу Ctrl під час клікання) і підтвердимо форму за допомогою кнопки [Valider] [2]. У відповідь ми отримаємо сторінку [3,4]:
![]() |
Значення відправленого поля [1] таке:
Перевірка форми за допомогою [2] призвела до оновлення шаблону [Form.java] за допомогою запису [1]. Елементи HTML
<option value="3">trois</option>
<option value="4">quatre</option>
<option value="5">cinq</option>
були обрані. Браузер надіслав три рядки «3», «4», «5» як значення компонента JSF, що сформував список, що розгортається:
<h:selectManyListbox id="selectManyListBox" value="#{form.selectManyListBox}" size="3">
Метод setSelectManyListBox шаблону буде використано для оновлення цього шаблону значеннями, надісланими браузером:
private String[] selectManyListBox;
....
public void setSelectManyListBox(String[] selectManyListBox) {
this.selectManyListBox = selectManyListBox;
}
У рядку 3 видно, що параметром методу є масив String. У даному випадку це буде масив {"3","4","5"}. Після цього оновлення поле
private String[] selectManyListBox;
тепер містить масив {"3","4","5"}.
Коли сторінка [index.xhtml] знову відображається після обробки, це значення зумовлює відображення [3,4], наведеного вище:
- воно визначає елементи списку, які мають бути виділені [3],
- значення поля selectManyListBox відображається як [4].
2.5.16. Тег <h:selectOneMenu>
Тег <h:selectOneMenu> ідентичний тегу <h:selectOneListBox size="1">. У наведеному прикладі виконується такий код JSF:
<!-- рядок 7 -->
<h:outputText value="selectOneMenu" styleClass="info"/>
<h:panelGroup>
<h:outputText value="#{msg['form.selectOneMenuPrompt']}"/>
<h:selectOneMenu id="selectOneMenu" value="#{form.selectOneMenu}">
<f:selectItem itemValue="1" itemLabel="un"/>
<f:selectItem itemValue="2" itemLabel="deux"/>
<f:selectItem itemValue="3" itemLabel="trois"/>
<f:selectItem itemValue="4" itemLabel="quatre"/>
<f:selectItem itemValue="5" itemLabel="cinq"/>
</h:selectOneMenu>
</h:panelGroup>
<h:outputText value="#{form.selectOneMenu}"/>
Шаблон тегу <h:selectOneMenu> у [Form.java] має такий вигляд:
private String selectOneMenu="1";
При першому запиті сторінки [index.xhtml] попередній код генерує такий вигляд:
![]() |
Приклад виконання може виглядати так:
![]() |
Значення, відправлене для поля [1], є таким:
2.5.17. Тег <h:selectManyMenu>
Тег <h:selectManyMenu> ідентичний тегу <h:selectManyListBox size="1">. Код JSF, що виконується у прикладі, має такий вигляд:
<!-- рядок 8 -->
<h:outputText value="selectManyMenu" styleClass="info"/>
<h:panelGroup>
<h:outputText value="#{msg['form.selectManyMenuPrompt']}" styleClass="prompt" />
<h:selectManyMenu id="selectManyMenu" value="#{form.selectManyMenu}" >
<f:selectItem itemValue="1" itemLabel="un"/>
<f:selectItem itemValue="2" itemLabel="deux"/>
<f:selectItem itemValue="3" itemLabel="trois"/>
<f:selectItem itemValue="4" itemLabel="quatre"/>
<f:selectItem itemValue="5" itemLabel="cinq"/>
</h:selectManyMenu>
<p><input type="button" value="#{msg['form.buttonRazText']}" onclick="this.form['formulaire:selectManyMenu'].selectedIndex=-1;" /></p>
</h:panelGroup>
<h:outputText value="#{form.selectManyMenuValue}" styleClass="prompt"/>
Шаблон тегу <h:selectManyMenu> у [Form.java] має такий вигляд:
private String[] selectManyMenu=new String[]{"1","2"};
На основі початкового запиту сторінки [index.xhtml] попередній код генерує таку сторінку:
![]() |
Список [1] містить тексти «один», …, «п’ять», причому виділено елементи «один» і «два». Згенерований код HTML має такий вигляд:
<tr>
<td class="col1"><span class="info">selectManyMenu</span></td>
<td class="col2"><span class="prompt">choix multiple : </span><select id="formulaire:selectManyMenu" name="formulaire:selectManyMenu" multiple="multiple" size="1">
<option value="1" selected="selected">un</option>
<option value="2" selected="selected">deux</option>
<option value="3">trois</option>
<option value="4">quatre</option>
<option value="5">cinq</option>
</select>
<p><input type="button" value="Raz" onclick="this.form['formulaire:selectManyMenu'].selectedIndex=-1;" /></p>
</td>
<td class="col3"><span class="prompt">[ 1 2]</span></td>
</tr>
Як видно вище, у рядках 4 та 5 виділено елементи «un» та «deux» (наявність атрибута selected).
Важко надати знімок екрана з прикладом виконання, оскільки неможливо показати вибрані елементи в меню. Пропонуємо читачеві самостійно провести тест (щоб вибрати кілька елементів у списку, утримуйте клавішу Ctrl під час клацання).
2.5.18. Тег <h:inputHidden>
Тег <h:inputHidden> не має візуального відображення. Він служить лише для вставки тегу <input type="hidden" value="..."/> у потік сторінки. Включені всередину тегу <h:form>, їхні значення є частиною даних, що надсилаються на сервер під час відправлення форми. Оскільки це поля форми, які користувач не бачить, їх називають прихованими полями. Перевага цих полів полягає в збереженні даних у пам’яті між різними циклами запит/відповідь одного й того самого клієнта:
- клієнт запитує форму F. Сервер надсилає її йому та розміщує інформацію I у прихованому полі C у вигляді <h:inputHidden id="C" value="I"/>,
- коли клієнт заповнив форму F і відправляє її на сервер, значення I поля C повертається на сервер. Тоді сервер може відновити інформацію I, яку він зберіг на сторінці. Таким чином створено пам’ять між двома циклами запит/відповідь,
- JSF сам використовує цю техніку. Інформація I, яку він зберігає у формі F, — це значення всіх її компонентів. Для цього він використовує таке приховане поле:
<input type="hidden" name="javax.faces.ViewState" id="javax.faces.ViewState" value="H4sIAAAAAAAAANV...8PswawAA" />
Приховане поле називається javax.faces.ViewState, а його значенням є рядок, який у закодованому вигляді представляє значення всіх компонентів сторінки, надісланої клієнту. Коли клієнт повертає сторінку після введення даних у форму, приховане поле javax.faces.ViewState надсилається разом із введеними значеннями. Саме це дозволяє контролеру JSF відтворити сторінку такою, якою вона була надіслана спочатку. Цей механізм було пояснено на сторінці 72.
Код JSF із прикладу виглядає так:
<!-- рядок 9 -->
<h:outputText value="inputHidden" styleClass="info"/>
<h:inputHidden id="inputHidden" value="#{form.inputHidden}"/>
<h:outputText value="#{form.inputHidden}"/>
Шаблон тегу <h:inputHidden> у [Form.java] має такий вигляд:
private String inputHidden="initial";
Це дає таке відображення під час першого запиту сторінки [index.xhtml]:
- рядок 2 генерує [1], рядок 4 — [2]. Рядок 3 не генерує жодного візуального елемента.
Згенерований код HTML має такий вигляд:
<tr>
<td class="col1"><span class="info">inputHidden</span></td>
<td class="col2"><input id="formulaire:inputHidden" type="hidden" name="formulaire:inputHidden" value="initial" /></td>
<td class="col3">initial</td>
</tr>
Під час відправлення форми з кодом POST «початкове» значення поля з іменем formulaire:inputHidden у рядку 3 буде надіслано разом з іншими значеннями форми. Поле
private String inputHidden;
буде оновлено цим значенням, яке воно вже мало спочатку. Це значення буде включено до нової сторінки, що повертається клієнту. Отже, ми завжди отримуємо знімок екрана, наведений вище.
Значення, відправлене для прихованого поля, таке:
2.5.19. Тег <h:selectBooleanCheckBox>
Тег <h:selectBooleanCheckBox> генерує тег HTML <input type="checkbox" ...>.
Розглянемо такий код JSF:
<!-- рядок 10 -->
<h:outputText value="selectBooleanCheckbox" styleClass="info"/>
<h:panelGroup>
<h:outputText value="#{msg['form.selectBooleanCheckboxPrompt']}" styleClass="prompt" />
<h:selectBooleanCheckbox id="selectBooleanCheckbox" value="#{form.selectBooleanCheckbox}"/>
</h:panelGroup>
<h:outputText value="#{form.selectBooleanCheckbox}"/>
Шаблон тегу <h:selectBooleanCheckbox> з 5-го рядка вище у [Form.java] має такий вигляд:
private boolean selectBooleanCheckbox=true;
Коли сторінка [index.xhtml] запитується вперше, отримана сторінка виглядає так:
![]() |
- рядок 2 коду XHTML генерує [1],
- текст [2] генерується рядком 4. Поле для позначення [3] генерується рядком [5]. Тут для встановлення або зняття прапорця було використано метод getSelectBooleanCheckbox з [Form.java]. Оскільки метод повернув логічне значення true (див. код Java), прапорець було встановлено,
- а рядок 7 коду XHTML генерує [4]. Знову використовується метод getSelectBooleanCheckbox з [Form.java] для генерації тексту [4].
Потік HTML, згенерований попереднім кодом JSF, має такий вигляд:
<tr>
<td class="col1"><span class="info">selectBooleanCheckbox</span></td>
<td class="col2"><span class="prompt">marié(e) : </span>
<input id="formulaire:selectBooleanCheckbox" type="checkbox" name="formulaire:selectBooleanCheckbox" checked="checked" /></td>
<td class="col3">true</td>
</tr>
У [4] ми бачимо тег HTML <input type="checkbox">, який було згенеровано. Значення true відповідної моделі призвело до того, що до тегу було додано атрибут checked="checked". Це означає, що поле позначено галочкою.
Тепер, нижче, знімемо галочку з поля [1], надішлемо форму [2] і подивимося на отриманий результат [3, 4]:
![]() |
Оскільки галочка знята, для поля [1] значення не вказано.
Перевірка форми за допомогою [2] призвела до оновлення шаблону [Form.java] за допомогою запису [1]. Поле selectBooleanCheckbox у [Form.java] отримало значення false. Повторне відображення [index.xhtml] показує, що поле selectBooleanCheckbox моделі було успішно оновлено до значень [3] та [4]. Цікаво зазначити, що саме завдяки прихованому полю javax.faces.ViewState JSF зміг визначити, що прапорець, який спочатку був встановлений, був знятий користувачем. Адже значення знятого прапорця не входить до набору значень, що надсилаються браузером. Завдяки дереву компонентів, збереженому в прихованому полі javax.faces.ViewState, JSF встановлює, що у формі був прапорець із назвою «selectBooleanCheckbox» і що його значення не входить до набору значень, надісланих клієнтським браузером. З цього можна зробити висновок, що у надісланій формі це поле було знято, що дозволяє присвоїти булеве значення false відповідній моделі Java:
private boolean selectBooleanCheckbox;
2.5.20. Тег <h:selectManyCheckBox>
Тег <h:selectManyCheckBox> генерує групу прапорців і, отже, кілька тегів HTML <input type="checkbox" ...>. Цей тег є аналогом тегу <h:selectManyListBox>, за винятком того, що елементи для вибору представлені у вигляді суміжних прапорців, а не у вигляді списку. Те, що було сказано про тег <h:selectManyListBox>, залишається актуальним і тут.
Розглянемо такий код JSF:
<!-- рядок 11 -->
<h:outputText value="selectManyCheckbox" styleClass="info"/>
<h:panelGroup>
<h:outputText value="#{msg['form.selectManyCheckboxPrompt']}" styleClass="prompt" />
<h:selectManyCheckbox id="selectManyCheckbox" value="#{form.selectManyCheckbox}">
<f:selectItem itemValue="1" itemLabel="rouge"/>
<f:selectItem itemValue="2" itemLabel="bleu"/>
<f:selectItem itemValue="3" itemLabel="blanc"/>
<f:selectItem itemValue="4" itemLabel="noir"/>
</h:selectManyCheckbox>
</h:panelGroup>
<h:outputText value="#{form.selectManyCheckboxValue}"/>
Шаблон тегу <h:selectManyCheckbox> у рядку 5 вище в [Form.java] має такий вигляд:
private String[] selectManyCheckbox=new String[]{"1","3"};
Коли сторінка [index.xhtml] запитується вперше, отримана сторінка виглядає так:
![]() |
- рядок 2 коду XHTML генерує [1],
- текст [2] генерується рядком 4. Поля для позначення [3] генеруються рядками 5–10. Для кожного з них:
- атрибут itemLabel визначає текст, що відображається поруч із прапорцем;
- атрибут itemvalue визначає значення, яке буде надіслано на сервер, якщо прапорець встановлено;
Шаблоном для цих чотирьох прапорців є таке поле Java:
private String[] selectManyCheckbox=new String[]{"1","3"};
Ця таблиця визначає:
- які прапорці мають бути встановлені під час відображення сторінки. Це здійснюється за допомогою їхнього значення, c.a.d, та їхнього поля itemValue. У наведеному вище прикладі поля, значення яких містяться в масиві {"1","3"}, будуть відмічені. Саме це видно на знімку екрана вище;
- коли сторінка відправляється, шаблон selectManyCheckbox отримує масив значень полів, які користувач позначив. Саме це ми розглянемо незабаром,
- рядок 12 коду XHTML генерує [4]. Саме наступний метод getSelectManyCheckboxValue згенерував [4]:
public String getSelectManyCheckboxValue(){
return getValue(getSelectManyCheckbox());
}
private String getValue(String[] chaines){
String value="[";
for(String chaine : chaines){
value+=" "+chaine;
}
return value+"]";
}
Потік HTML, згенерований попереднім кодом JSF, має такий вигляд:
<tr>
<td>
<input name="formulaire:selectManyCheckbox" id="formulaire:selectManyCheckbox:0" value="1" type="checkbox" checked="checked" /><label for="formulaire:selectManyCheckbox:0"> rouge</label></td>
<td>
<input name="formulaire:selectManyCheckbox" id="formulaire:selectManyCheckbox:1" value="2" type="checkbox" /><label for="formulaire:selectManyCheckbox:1"> bleu</label></td>
<td>
<input name="formulaire:selectManyCheckbox" id="formulaire:selectManyCheckbox:2" value="3" type="checkbox" checked="checked" /><label for="formulaire:selectManyCheckbox:2"> blanc</label></td>
<td>
<input name="formulaire:selectManyCheckbox" id="formulaire:selectManyCheckbox:3" value="4" type="checkbox" /><label for="formulaire:selectManyCheckbox:3"> noir</label></td>
</tr>
</table></td>
<td class="col3">[ 1 3]</td>
</tr>
Було згенеровано чотири теги HTML <input type="checkbox" ...>. Теги у рядках 3 та 7 мають атрибут checked="checked", завдяки чому вони відображаються як відмічені. Слід зауважити, що всі вони мають однаковий атрибут name="formulaire:selectManyCheckbox", тобто всі чотири поля HTML мають однакову назву. Якщо користувач встановить галочки у полях рядків 5 та 9, браузер надішле значення цих чотирьох полів у такому вигляді:
а шаблон для цих чотирьох полів
private String[] selectManyCheckbox=new String[]{"1","3"};
отримає масив {"2","4"}.
Перевіримо це нижче. У [1] ми вносимо зміну, у [2] — підтверджуємо форму. У [3] отриманий результат:
![]() |
Значення, відправлені для полів [1], такі:
2.5.21. Тег <h:selectOneRadio>
Тег <h:selectOneRadio> створює групу перемикачів, які виключають один одного.
Розглянемо такий код JSF:
<!-- рядок 12 -->
<h:outputText value="selectOneRadio" styleClass="info"/>
<h:panelGroup>
<h:outputText value="#{msg['form.selectOneRadioPrompt']}" />
<h:selectOneRadio id="selectOneRadio" value="#{form.selectOneRadio}">
<f:selectItem itemValue="1" itemLabel="voiture"/>
<f:selectItem itemValue="2" itemLabel="vélo"/>
<f:selectItem itemValue="3" itemLabel="scooter"/>
<f:selectItem itemValue="4" itemLabel="marche"/>
</h:selectOneRadio>
</h:panelGroup>
<h:outputText value="#{form.selectOneRadio}"/>
Шаблон тегу <h:selectOneRadio> у рядку 5 вище виглядає так у [Form.java]:
private String selectOneRadio="2";
Коли сторінка [index.xhtml] запитується вперше, відображається таке зображення:
![]() |
- рядок 2 коду XHTML генерує [1],
- Текст [2] генерується рядком 4. Перемикачі [3] генеруються рядками 5–10. Для кожного з них:
- атрибут itemLabel визначає текст, що відображається поруч із перемикачем;
- атрибут itemvalue визначає значення, яке буде надіслано на сервер, якщо перемикач буде встановлено;
Шаблоном для чотирьох перемикачів є таке поле Java:
private String selectOneRadio="2";
Ця модель визначає:
- коли сторінка відображається, єдину перемикальну кнопку, яка має бути позначена. Це здійснюється за допомогою їхнього значення, c.a.d, та їхнього поля itemValue. У наведеному вище прикладі буде обрано перемикач із значенням «2». Саме це видно на знімку екрана вище;
- під час відправлення сторінки шаблон selectOneRadio отримує значення перемикача, який було обрано. Саме це ми незабаром розглянемо,
- рядок 12 коду XHTML генерує [4].
Потік HTML, згенерований попереднім кодом JSF, має такий вигляд:
<tr>
<td class="col1"><span class="info">selectOneRadio</span></td>
<td class="col2">moyen de transport préféré : <table id="formulaire:selectOneRadio">
<tr>
<td>
<input type="radio" name="formulaire:selectOneRadio" id="formulaire:selectOneRadio:0" value="1" /><label for="formulaire:selectOneRadio:0"> voiture</label></td>
<td>
<input type="radio" checked="checked" name="formulaire:selectOneRadio" id="formulaire:selectOneRadio:1" value="2" /><label for="formulaire:selectOneRadio:1"> vélo</label></td>
<td>
<input type="radio" name="formulaire:selectOneRadio" id="formulaire:selectOneRadio:2" value="3" /><label for="formulaire:selectOneRadio:2"> scooter</label></td>
<td>
<input type="radio" name="formulaire:selectOneRadio" id="formulaire:selectOneRadio:3" value="4" /><label for="formulaire:selectOneRadio:3"> marche</label></td>
</tr>
Було згенеровано чотири теги HTML <input type="radio" ...>. Тег у рядку 8 має атрибут checked="checked", завдяки чому відповідна кнопка радіополя відображається як позначена. Слід зауважити, що всі теги мають однаковий атрибут name="formulaire:selectOneRadio", тобто всі чотири поля HTML мають однакову назву. Це є умовою для створення групи взаємовиключних перемикачів: коли один з них позначений, інші не позначені.
Нижче, у полі [1], ми відмічаємо один із перемикачів, у полі [2] — підтверджуємо форму, а в полі [3] — бачимо отриманий результат:
![]() |
Значення, відправлене для поля [1], таке:
2.6. Приклад mv-jsf2-04: динамічні списки
2.6.1. Додаток
Додаток такий самий, як і раніше:
![]() |
Єдині зміни стосуються способу формування елементів списків у полях [1] та [2]. Тут вони генеруються динамічно за допомогою коду Java, тоді як у попередній версії вони були «жорстко» вказані в коді сторінки JSF.
2.6.2. Проєкт NetBeans
Проєкт NetBeans для цього додатка має такий вигляд:
![]() |
Проєкт [mv-jsf2-04] ідентичний проєкту [mv-jsf2-03], за винятком таких відмінностей:
- у [1] на сторінці JSF елементи списків більше не будуть «жорстко» вказані в коді,
- у [2] шаблон сторінки JSF [1] буде змінено,
- на сторінці [3] буде змінено одне з повідомлень.
2.6.3. Сторінка [index.xhtml] та її шаблон [Form.java]
Сторінка JSF [index.xhtml] набуває такого вигляду:
<?xml version='1.0' encoding='UTF-8' ?>
<!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:f="http://java.sun.com/jsf/core">
<f:view locale="#{changeLocale.locale}">
<h:head>
<title>JSF</title>
<h:outputStylesheet library="css" name="styles.css"/>
</h:head>
<h:body style="background-image: url('${request.contextPath}/resources/images/standard.jpg');">
<h:form id="formulaire">
<!-- мови -->
<h:panelGrid columns="2">
<h:commandLink value="#{msg['form.langue1']}" action="#{changeLocale.setFrenchLocale}"/>
<h:commandLink value="#{msg['form.langue2']}" action="#{changeLocale.setEnglishLocale}"/>
</h:panelGrid>
<h1><h:outputText value="#{msg['form.titre']}"/></h1>
<h:panelGrid columnClasses="col1,col2,col3" columns="3" border="1">
...
<!-- рядок 4 -->
<h:outputText value="selectOneListBox (size=1)" styleClass="info"/>
<h:panelGroup>
<h:outputText value="#{msg['form.selectOneListBox1Prompt']}"/>
<h:selectOneListbox id="selectOneListBox1" value="#{form.selectOneListBox1}" size="1">
<f:selectItems value="#{form.selectOneListbox1Items}"/>
</h:selectOneListbox>
</h:panelGroup>
<h:outputText value="#{form.selectOneListBox1}"/>
<!-- рядок 5 -->
<h:outputText value="selectOneListBox (size=3)" styleClass="info"/>
<h:panelGroup>
<h:outputText value="#{msg['form.selectOneListBox2Prompt']}"/>
<h:selectOneListbox id="selectOneListBox2" value="#{form.selectOneListBox2}" size="3">
<f:selectItems value="#{form.selectOneListbox2Items}"/>
</h:selectOneListbox>
</h:panelGroup>
<h:outputText value="#{form.selectOneListBox2}"/>
<!-- рядок 6 -->
<h:outputText value="selectManyListBox (size=3)" styleClass="info"/>
<h:panelGroup>
<h:outputText value="#{msg['form.selectManyListBoxPrompt']}"/>
<h:selectManyListbox id="selectManyListBox" value="#{form.selectManyListBox}" size="3">
<f:selectItems value="#{form.selectManyListBoxItems}"/>
</h:selectManyListbox>
<p><input type="button" value="#{msg['form.buttonRazText']}" onclick="this.form['formulaire:selectManyListBox'].selectedIndex=-1;" /></p>
</h:panelGroup>
<h:outputText value="#{form.selectManyListBoxValue}"/>
<!-- рядок 7 -->
<h:outputText value="selectOneMenu" styleClass="info"/>
<h:panelGroup>
<h:outputText value="#{msg['form.selectOneMenuPrompt']}"/>
<h:selectOneMenu id="selectOneMenu" value="#{form.selectOneMenu}">
<f:selectItems value="#{form.selectOneMenuItems}"/>
</h:selectOneMenu>
</h:panelGroup>
<h:outputText value="#{form.selectOneMenu}"/>
<!-- рядок 8 -->
<h:outputText value="selectManyMenu" styleClass="info"/>
<h:panelGroup>
<h:outputText value="#{msg['form.selectManyMenuPrompt']}" styleClass="prompt" />
<h:selectManyMenu id="selectManyMenu" value="#{form.selectManyMenu}" >
<f:selectItems value="#{form.selectManyMenuItems}"/>
</h:selectManyMenu>
<p><input type="button" value="#{msg['form.buttonRazText']}" onclick="this.form['formulaire:selectManyMenu'].selectedIndex=-1;" /></p>
</h:panelGroup>
<h:outputText value="#{form.selectManyMenuValue}" styleClass="prompt"/>
...
<!-- рядок 11 -->
<h:outputText value="selectManyCheckbox" styleClass="info"/>
<h:panelGroup>
<h:outputText value="#{msg['form.selectManyCheckboxPrompt']}" styleClass="prompt" />
<h:selectManyCheckbox id="selectManyCheckbox" value="#{form.selectManyCheckbox}">
<f:selectItems value="#{form.selectManyCheckboxItems}"/>
</h:selectManyCheckbox>
</h:panelGroup>
<h:outputText value="#{form.selectManyCheckboxValue}"/>
<!-- рядок 12 -->
<h:outputText value="selectOneRadio" styleClass="info"/>
<h:panelGroup>
<h:outputText value="#{msg['form.selectOneRadioPrompt']}" />
<h:selectOneRadio id="selectOneRadio" value="#{form.selectOneRadio}">
<f:selectItems value="#{form.selectOneRadioItems}"/>
</h:selectOneRadio>
</h:panelGroup>
<h:outputText value="#{form.selectOneRadio}"/>
</h:panelGrid>
<p>
<h:commandButton type="submit" id="submit" value="#{msg['form.submitText']}"/>
</p>
</h:form>
</h:body>
</f:view>
</html>
Внесені зміни проілюстровано у рядках 26–28. Там, де раніше був код:
<h:selectOneListbox id="selectOneListBox1" value="#{form.selectOneListBox1}" size="1">
<f:selectItem itemValue="1" itemLabel="un"/>
<f:selectItem itemValue="2" itemLabel="deux"/>
<f:selectItem itemValue="3" itemLabel="trois"/>
</h:selectOneListbox>
тепер стоїть такий:
<h:selectOneListbox id="selectOneListBox1" value="#{form.selectOneListBox1}" size="1">
<f:selectItems value="#{form.selectOneListbox1Items}"/>
</h:selectOneListbox>
Три теги <f:selectItem> у рядках 2–4 були замінені на єдиний тег <f:selectItems> у рядку b. Цей тег має атрибут value, значенням якого є колекція елементів типу javax.faces.model.SelectItem. У наведеному вище прикладі значення атрибуту value буде отримано шляхом виклику наступного методу [form].getSelectOneListbox1Items:
public SelectItem[] getSelectOneListbox1Items() {
return getItems("A",3);
}
private SelectItem[] getItems(String label, int qte) {
SelectItem[] items=new SelectItem[qte];
for(int i=0;i<qte;i++){
items[i]=new SelectItem(i,label+i);
}
return items;
}
- у рядку 1 метод getSelectOneListbox1Items повертає масив елементів типу javax.faces.model.SelectItem, сформований приватним методом getItems із рядка 5. Слід зауважити, що метод getSelectOneListbox1Items не є методом getter приватного поля selectOneListBox1Items,
- а клас javax.faces.model.SelectItem має різні конструктори.

Ми використовуємо рядок 8 методу getItems, конструктор SelectItem(Object value, String label), який відповідає тегу JSF
<f:selectItem itemValue="value" labelValue="label"/>
- рядки 5–10: метод getItems(String label, int qte) створює масив із qte елементів типу SelectItem, де елемент i отримується за допомогою конструктора SelectItem(i, label+i).
Код JSF
<h:selectOneListbox id="selectOneListBox1" value="#{form.selectOneListBox1}" size="1">
<f:selectItems value="#{form.selectOneListbox1Items}"/>
</h:selectOneListbox>
стає функціонально еквівалентним наступному коду JSF:
<h:selectOneListbox id="selectOneListBox1" value="#{form.selectOneListBox1}" size="1">
<f:selectItem itemValue="0" itemLabel="A0"/>
<f:selectItem itemValue="1" itemLabel="A1"/>
<f:selectItem itemValue="2" itemLabel="A2"/>
</h:selectOneListbox>
Те саме робиться для всіх інших списків на сторінці JSF. Таким чином, у шаблоні [Form.java] містяться такі нові методи:
public SelectItem[] getSelectOneListbox1Items() {
return getItems("A",3);
}
public SelectItem[] getSelectOneListbox2Items() {
return getItems("B",4);
}
public SelectItem[] getSelectManyListBoxItems() {
return getItems("C",5);
}
public SelectItem[] getSelectOneMenuItems() {
return getItems("D",3);
}
public SelectItem[] getSelectManyMenuItems() {
return getItems("E",4);
}
public SelectItem[] getSelectManyCheckboxItems() {
return getItems("F",3);
}
public SelectItem[] getSelectOneRadioItems() {
return getItems("G",4);
}
private SelectItem[] getItems(String label, int qte) {
SelectItem[] items=new SelectItem[qte];
for(int i=0;i<qte;i++){
items[i]=new SelectItem(i,label+i);
}
return items;
}
2.6.4. Файл повідомлень
Змінено лише одне повідомлення:
[messages_fr.properties]
form.titre=Java Server Faces - remplissage dynamique des listes
[messages_en.properties]
form.titre=Java Server Faces - dynamic filling of lists of elements
2.6.5. Тестування
Просимо користувачів протестувати цю нову версію.
Найчастіше динамічні елементи форми є результатом бізнес-обробки або походять із бази даних:
![]() |
Розглянемо початковий запит на сторінку JSF [index.xhtml], який надсилає браузер GET:
- сторінка JSF запитується [1],
- контролер [Faces Servlet] запитує її відображення у [3]. Двигун JSF, який обробляє сторінку, звертається до її моделі [Form.java], наприклад, до методу getSelectOneListBox1Items. Цей метод цілком може повернути масив елементів типу SelectItem на основі інформації, записаної в базі даних. Для цього він звертається до шару [métier] [2b].
2.7. Приклад mv-jsf2-05: навігація — сесія — обробка винятків
2.7.1. Додаток
Додаток такий самий, як і раніше, за винятком того, що форма тепер має вигляд багатосторінкового майстра:
![]() |
- у [1], сторінка 1 форми — її також можна відкрити за посиланням 1 у [2]
- в [2] — група з 5 посилань.
- в [3], сторінка 2 форми, доступ до якої здійснюється за посиланням 2 з [2]
![]() |
![]() |
- у [4] — сторінка 3 форми, на яку веде посилання 3 з [2]
- у [5] — сторінка, отримана за посиланням «Викликати виняток» з [2]
![]() |
- у [6] — сторінка, на яку веде посилання 4 з [2]. На ній наведено підсумок даних, введених на сторінках 1–3.
2.7.2. Проєкт NetBeans
Проект NetBeans для цього додатка має такий вигляд:
![]() |
Проект [mv-jsf2-05] вводить дві новинки:
- у [1] сторінка JSF [index.xhtml] розділена на три сторінки [form1.xhtml, form2.xhtml, form3.xhtml], на які розподілено введені дані. Сторінка [form4.xhtml] є копією сторінки [index.xhtml] з попереднього проєкту. У [2] клас [Form.java] залишається без змін. Він слугуватиме шаблоном для чотирьох попередніх сторінок JSF,
- у [3] додано сторінку [exception.xhtml]: вона використовуватиметься у разі виникнення винятку в додатку.
2.7.3. Сторінки [form.xhtml] та їхній шаблон [Form.java]
2.7.3.1. Код сторінок XHTML
Сторінка JSF [form1.xhtml] має такий вигляд:
<?xml version='1.0' encoding='UTF-8' ?>
<!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:f="http://java.sun.com/jsf/core">
<f:view locale="#{changeLocale.locale}">
<h:head>
<title>JSF</title>
<h:outputStylesheet library="css" name="styles.css"/>
</h:head>
<h:body style="background-image: url('${request.contextPath}/resources/images/standard.jpg');">
<h:form id="formulaire">
<!-- посилання -->
<h:panelGrid columns="2">
<h:commandLink value="#{msg['form.langue1']}" action="#{changeLocale.setFrenchLocale}"/>
<h:commandLink value="#{msg['form.langue2']}" action="#{changeLocale.setEnglishLocale}"/>
</h:panelGrid>
<h1><h:outputText value="#{msg['form1.titre']}"/></h1>
<h:panelGrid columnClasses="col1,col2" columns="2" border="1">
<h:outputText value="#{msg['form.headerCol1']}" styleClass="entete"/>
<h:outputText value="#{msg['form.headerCol2']}" styleClass="entete"/>
<!-- рядок 1 -->
<h:outputText value="inputText" styleClass="info"/>
<h:panelGroup>
<h:outputText value="#{msg['form.loginPrompt']}"/>
<h:inputText id="inputText" value="#{form.inputText}"/>
</h:panelGroup>
<!-- рядок 2 -->
<h:outputText value="inputSecret" styleClass="info"/>
<h:panelGroup>
<h:outputText value="#{msg['form.passwdPrompt']}"/>
<h:inputSecret id="inputSecret" value="#{form.inputSecret}"/>
</h:panelGroup>
<!-- рядок 3 -->
<h:outputText value="inputTextArea" styleClass="info"/>
<h:panelGroup>
<h:outputText value="#{msg['form.descPrompt']}"/>
<h:inputTextarea id="inputTextArea" value="#{form.inputTextArea}" rows="4"/>
</h:panelGroup>
</h:panelGrid>
<!-- посилання -->
<h:panelGrid columns="6">
<h:commandLink value="1" action="form1"/>
<h:commandLink value="2" action="#{form.doAction2}"/>
<h:commandLink value="3" action="form3"/>
<h:commandLink value="4" action="#{form.doAction4}"/>
<h:commandLink value="#{msg['form.pagealeatoireLink']}" action="#{form.doAlea}"/>
<h:commandLink value="#{msg['form.exceptionLink']}" action="#{form.throwException}"/>
</h:panelGrid>
</h:form>
</h:body>
</f:view>
</html>
і відповідає такому відображенню:
![]() |
Зверніть увагу на наступні моменти:
- у рядку 16 таблиця, яка раніше мала три стовпці, тепер має лише два. Стовпець 3, у якому відображалися значення моделі, було видалено. Їх відображатиме [form4.xhtml],
- рядки 40–46: таблиця з шести посилань. Посилання в рядках 44 і 46 мають статичну навігацію: їхній атрибут action задано жорстко. Інші посилання мають динамічну навігацію: їхній атрибут action вказує на метод bean-форми, який відповідає за повернення ключа навігації. Методи, на які є посилання в [Form.java], такі:
// події
public String doAction2(){
return "form2";
}
public String doAction4(){
return "form4";
}
public String doAlea(){
// випадкове число від 1 до 3
int i=1+(int)(3*Math.random());
// повертаємо ключ навігації
return "form"+i;
}
public String throwException() throws java.lang.Exception{
throw new Exception("Exception test");
}
Наразі ми проігноруємо метод throwException у рядку 17. Ми повернемося до нього пізніше. Методи doAction2 та doAction4 просто повертають ключ навігації, не виконуючи жодної обробки. Отже, можна було б також написати:
<h:commandLink value="1" action="form1"/>
<h:commandLink value="2" action="form2"/>
<h:commandLink value="3" action="form3"/>
<h:commandLink value="4" action="form4"/>
<h:commandLink value="#{msg['form.pagealeatoireLink']}" action="#{form.doAlea}"/>
<h:commandLink value="#{msg['form.exceptionLink']}" action="#{form.throwException}"/>
Метод doAlea, у свою чергу, генерує випадковий ключ навігації, значення якого береться з множини {"form1", "form2", "form3"}.
Код сторінок [form2.xhtml, form3.xhtml, form3.xhtml] аналогічний коду сторінки [form1.xhtml].
2.7.3.2. Термін дії шаблону [Form.java] для сторінок [form*.xhtml]
Розглянемо таку послідовність дій:
![]() |
- на сторінці [1] заповнюємо сторінку 1 і переходимо на сторінку 3,
- на [2] заповнюється сторінка 3 і відбувається повернення на сторінку 1,
![]() |
- у [3] ми бачимо сторінку 1 такою, якою її було введено. Потім ми повертаємося на сторінку 3,
- у [4] сторінка 3 відображається такою, якою її було введено.
Механізм прихованого поля [javax.faces.ViewState] не є достатнім для пояснення цього явища.
Під час переходу від [1] до [2] відбувається кілька етапів:
- шаблон [Form.java] оновлюється з POST до [form1.jsp]. Зокрема, поле inputText отримує значення «інший текст»,
- навігаційний ключ «form3» викликає відображення [form3.xhtml]. ViewState, вбудований у [form3.xhtml], відображає стан лише компонентів [form3.xhtml], а не компонентів [form1.xhtml].
Під час переходу від [2] до [3]:
- модель [Form.java] оновлюється на основі POST з [form3.xhtml]. Якщо термін дії моделі [Form.java] закінчився, створюється абсолютно новий об’єкт [Form.java], який потім оновлюється на основі POST з [form3.xhtml]. У цьому випадку поле inputText шаблону повертається до значення за замовчуванням:
private String inputText="texte";
і зберігає його: адже у POST, створеному на основі [form3.xhtml], ніщо не оновлює поле inputText, яке є частиною шаблону [form1.xhtml], а не шаблону [form3.xhtml],
- навігаційний ключ «form1» викликає відображення [form1.xhtml]. Сторінка відображає свій шаблон. У нашому випадку поле введення login, пов’язане з шаблоном inputText, відображатиме texte, а не значення «інший текст», введене в [1]. Щоб поле inputText зберігало значення, введене в [1], термін дії шаблону [Form.java] повинен бути «сесія», а не «запит». У цьому випадку
- після завершення POST з [form1.xhtml] шаблон буде розміщений у сесії клієнта. Поле inputText матиме значення «інший текст»,
- під час виконання POST з [form3.xhtml] шаблон буде знайдено в цій сесії та оновлено за допомогою POST з [form3.xhtml]. Поле inputText не буде оновлено цим POST, але збереже значення «інший текст» , отримане в результаті виконання POST з [form1.xhtml] та [1].
Отже, оголошення біна [Form.java] має такий вигляд:
package forms;
import javax.enterprise.context.SessionScoped;
import javax.faces.bean.ManagedBean;
import javax.faces.model.SelectItem;
@ManagedBean
@SessionScoped
public class Form {
Рядок 8 надає біну область дії «сесія».
2.7.4. Обробка винятків
Повернемося до загальної архітектури додатка JSF:
![]() |
Що відбувається, коли обробник подій або модель отримує виняток із бізнес-шару, наприклад, несподіване роз'єднання з базою даних?
- обробники подій [2a] можуть перехоплювати будь-яке виключення, що надходить із рівня [métier], і повертати контролеру [Faces Servlet] ключ навігації до сторінки помилки, що відповідає цьому виключенню,
- Для шаблонів це рішення не підходить, оскільки під час їх виклику ([3,4]) система перебуває на етапі відображення конкретної сторінки (XHTML), а не на етапі її вибору. Як змінити сторінку, перебуваючи у фазі відображення однієї з них? Просте рішення, яке, однак, не завжди підходить, полягає в тому, щоб не обробляти виняток, який у такому разі буде передано до контейнера сервлетів, що виконує додаток. Цей контейнер можна налаштувати так, щоб він відображав певну сторінку, коли виняток передається до нього. Це рішення завжди можна застосувати, і ми розглянемо його зараз.
2.7.4.1. Налаштування веб-додатку для обробки винятків
Налаштування веб-додатку для обробки винятків здійснюється у файлі [web.xml]:
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
<context-param>
<param-name>javax.faces.STATE_SAVING_METHOD</param-name>
<param-value>client</param-value>
</context-param>
<context-param>
<param-name>javax.faces.PROJECT_STAGE</param-name>
<param-value>Development</param-value>
</context-param>
<context-param>
<param-name>javax.faces.FACELETS_SKIP_COMMENTS</param-name>
<param-value>true</param-value>
</context-param>
<servlet>
<servlet-name>Faces Servlet</servlet-name>
<servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>Faces Servlet</servlet-name>
<url-pattern>/faces/*</url-pattern>
</servlet-mapping>
<session-config>
<session-timeout>
30
</session-timeout>
</session-config>
<welcome-file-list>
<welcome-file>faces/form1.xhtml</welcome-file>
</welcome-file-list>
<error-page>
<error-code>500</error-code>
<location>/faces/exception.xhtml</location>
</error-page>
<error-page>
<exception-type>java.lang.Exception</exception-type>
<location>/faces/exception.xhtml</location>
</error-page>
</web-app>
У рядках 32–39 міститься визначення двох сторінок помилок. Можна використовувати стільки тегів <error-page>, скільки потрібно. Тег <location> вказує на сторінку, яку слід відобразити у разі помилки. Тип помилки, пов’язаний із цією сторінкою, можна визначити двома способами:
- за допомогою тегу <exception-type>, який визначає тип винятку в Java, що обробляється. Таким чином, тег <error-page> у рядках 36–39 вказує, що якщо контейнер сервлетів під час виконання додатка отримає виняток типу [java.lang.Exception] або його похідний (рядок 37), то він повинен відобразити сторінку [/faces/exception.xhtml] (рядок 38). Використовуючи тут найзагальніший тип винятку [java.lang.Exception], ми гарантуємо обробку всіх винятків,
- за допомогою тегу <error-code> (рядок 33), який визначає код помилки HTTP. Наприклад, якщо браузер запитує URL [http://machine:port/contexte/P], а сторінка P не існує в контексті додатка, додаток не бере участі у формуванні відповіді. Саме контейнер сервлетів генерує цю відповідь, надсилаючи стандартну сторінку помилки. Перший рядок потоку HTTP у цій відповіді містить код помилки 404, що вказує на те, що запитувана сторінка P не існує. Може виникнути потреба згенерувати відповідь, яка, наприклад, відповідатиме графічному стилю додатка або міститиме посилання для вирішення проблеми. У цьому випадку слід використовувати тег <error-page> з атрибутом <error-code>404</error-code>.
Вище наведено код помилки 500 HTTP, який повертається у разі «збою» додатка. Саме цей код повертається, якщо виняток передається до контейнера сервлетів. Отже, обидва теги <error-page> у рядках 28–35, ймовірно, є зайвими. Ми розмістили їх обидва, щоб проілюструвати два способи обробки помилки.
2.7.4.2. Імітація винятку
Виняток штучно генерується за посиланням [Lancer une exception]:
![]() |
![]() |
Клік на посилання [Lancer une exception] [1] призводить до відображення сторінки [2].
У коді сторінок [formx.xhtml] посилання [Lancer une exception] генерується наступним чином:
<!-- посилання -->
<h:panelGrid columns="6">
<h:commandLink value="1" action="form1"/>
...
<h:commandLink value="#{msg['form.exceptionLink']}" action="#{form.throwException}"/>
</h:panelGrid>
У рядку 5 видно, що при натисканні на посилання буде виконано метод [form].throwException. Він має такий вигляд:
public String throwException() throws java.lang.Exception{
throw new Exception("Exception test");
}
У ньому генерується виняток типу [java.lang.Exception]. Він передається до контейнера сервлетів, який потім відобразить сторінку [/faces/exception.xhtml].
2.7.4.3. Інформація, пов’язана з винятком
Коли виняток передається до контейнера сервлетів, той відображає відповідну сторінку помилки, передаючи їй інформацію про виняток. Ця інформація розміщується як нові атрибути запиту, що обробляється. Запит браузера та відповідь, яку він отримає, інкапсульовані в об’єкти Java типів [HttpServletRequest request] та [HttpServletResponse response]. Ці об’єкти доступні на всіх етапах обробки запиту браузера.
![]() |
Після отримання запиту HTTP від браузера контейнер сервлетів інкапсулює його в об’єкт Java [HttpServletRequest request] і створює об’єкт [HttpServletResponse response], який дозволить згенерувати відповідь. У цьому об’єкті, зокрема, міститься канал TCP-IP, який використовується для потоку HTTP відповіді. Усі рівні t1, t2, …, tn, які братимуть участь в обробці об’єкта request, мають доступ до цих двох об’єктів. Кожен із них може отримати доступ до елементів початкового запиту request та підготувати відповідь, доповнивши об’єкт response. Наприклад, рівень localisation може встановити localisation відповіді за допомогою методу response.setLocale(Locale l).
Різні рівні ti можуть обмінюватися інформацією через об’єкт request. Цей об’єкт має словник атрибутів, порожній під час створення, який може доповнюватися наступними рівнями обробки. Ці рівні можуть розміщувати в атрибутах об’єкта request інформацію, необхідну для наступного рівня обробки. Існує два методи для управління атрибутами об’єкта request:
- void setAttribute(String s, Object o), який дозволяє додати до атрибутів об’єкт o, ідентифікований рядком s,
- Object getAttribute(String s), що дозволяє отримати атрибут o, ідентифікований рядком s.
Коли виняток передається до контейнера сервлетів, останній додає до запиту, що обробляється, такі атрибути:
ключ | значення |
код помилки HTTP, який буде надіслано клієнту | |
тип винятку в Java разом із повідомленням про помилку. | |
URL, що викликався на момент виникнення винятку | |
сервлет, який обробляв запит у момент виникнення винятку |
Ми використаємо ці атрибути запиту на сторінці [exception.xhtml] для їхнього відображення.
2.7.4.4. Сторінка помилки [exception.xhtml]
Її вміст такий:
<?xml version='1.0' encoding='UTF-8' ?>
<!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:f="http://java.sun.com/jsf/core">
<f:view locale="#{changeLocale.locale}">
<h:head>
<title>JSF</title>
<h:outputStylesheet library="css" name="styles.css"/>
</h:head>
<h:body style="background-image: url('${request.contextPath}/resources/images/standard.jpg');">
<h:form id="formulaire">
<h3><h:outputText value="#{msg['exception.header']}"/></h3>
<h:panelGrid columnClasses="col1,col2" columns="2" border="1">
<h:outputText value="#{msg['exception.httpCode']}"/>
<h:outputText value="#{requestScope['javax.servlet.error.status_code']}"/>
<h:outputText value="#{msg['exception.message']}"/>
<h:outputText value="#{requestScope['javax.servlet.error.exception']}"/>
<h:outputText value="#{msg['exception.requestUri']}"/>
<h:outputText value="#{requestScope['javax.servlet.error.request_uri']}"/>
<h:outputText value="#{msg['exception.servletName']}"/>
<h:outputText value="#{requestScope['javax.servlet.error.servlet_name']}"/>
</h:panelGrid>
<!-- посилання -->
<h:panelGrid columns="6">
<h:commandLink value="1" action="form1"/>
<h:commandLink value="2" action="#{form.doAction2}"/>
<h:commandLink value="3" action="form3"/>
<h:commandLink value="4" action="#{form.doAction4}"/>
<h:commandLink value="#{msg['form.pagealeatoireLink']}" action="#{form.doAlea}"/>
</h:panelGrid>
</h:form>
</h:body>
</f:view>
</html>
2.7.4.4.1. Вирази на сторінці винятку
У ланцюжку обробки запиту клієнта сторінка XHTML зазвичай є останньою ланкою ланцюжка:
![]() |
Усі елементи ланцюга є класами Java, включаючи сторінку XHTML. Ця сторінка, фактично, перетворюється на сервлет контейнером сервлетів, c.a.d, у звичайний клас Java. Точніше кажучи, сторінка XHTML перетворюється на Java-код, який виконується в межах наступного методу:
public void _jspService(HttpServletRequest request, HttpServletResponse response)
throws java.io.IOException, ServletException {
JspFactory _jspxFactory = null;
PageContext pageContext = null;
HTTPSession session = null;
ServletContext application = null;
ServletConfig config = null;
JspWriter out = null;
Object page = this;
JspWriter _jspx_out = null;
PageContext _jspx_page_context = null;
...
...code de la page XHTML
Починаючи з 14-го рядка, міститься Java-код, що відображає сторінку XHTML. Цей код матиме певну кількість об’єктів, ініціалізованих методом _jspService, 1-й рядок вище:
- рядок 1: HttpServletRequest request: запит, що обробляється,
- рядок 1: HttpServletResponse response: відповідь, яка буде надіслана клієнту,
- рядок 7: ServletContext application: об’єкт, що представляє саму веб-програму. Як і об’єкт request, об’єкт application може мати атрибути. Вони є спільними для всіх запитів усіх клієнтів. Зазвичай це атрибути, доступні лише для читання,
- рядок 6: HTTPSession сесія: представляє сесію клієнта. Як і об’єкти request та application, об’єкт session може мати атрибути. Вони є спільними для всіх запитів одного й того самого клієнта,
- рядок 9: JspWriter out: потік запису до браузера клієнта. Цей об’єкт корисний для налагодження сторінки XHTML. Усе, що записується через out.println(text), відображатиметься у браузері клієнта.
Коли на сторінці JSF записується #{вираз}, вираз може бути ключем атрибута об’єктів request, session або application, зазначених вище. Відповідний атрибут послідовно шукається в цих трьох об’єктах. Таким чином, #{ключ} обчислюється наступним чином:
- request.getAttribute(ключ)
- session.getAttribute(ключ)
- application.getAttribute(ключ)
Як тільки отримується значення, відмінне від null, обчислення #{ключ} припиняється. Можна бути більш точним, вказавши контекст, у якому слід шукати атрибут:
- #{requestScope['clé']} — для пошуку атрибута в об’єкті request,
- #{sessionScope['clé']} — для пошуку атрибута в об’єкті session,
- #{applicationScope['clé']} — для пошуку атрибута в об’єкті application.
Саме це було зроблено на сторінці [exception.xhtml], стор. 116. Використовуються такі атрибути:
ключ | домен | значення |
запит | див. розділ 2.7.4.3. | |
те саме | те саме | |
те саме | те саме | |
те саме | те саме |
Різні повідомлення, необхідні для сторінки JSF [exception.xhtml], були додані до вже існуючих файлів повідомлень:
[messages_fr.properties]
exception.header=L'exception suivante s'est produite
exception.httpCode=Code HTTP de l'erreur
exception.message=Message de l'exception
exception.requestUri=URL demandée lors de l'erreur
exception.servletName=Nom de la servlet demandée lorsque l'erreur s'est produite
[messages_en.properties]
exception.header=The following error occurred
exception.httpCode=HTTP error code
exception.message=Exception message
exception.requestUri=URL requested when error occurred
exception.servletName=Servlet requested when error occurred
2.8. Приклад mv-jsf2-06: перевірка та перетворення введених даних
2.8.1. Додаток
Додаток містить форму для введення даних. Після її перевірки у відповідь повертається та сама форма, разом із можливими повідомленнями про помилки, якщо введені дані були визнані некоректними.
![]() |
![]() |
2.8.2. Проєкт NetBeans
Проект NetBeans для цього додатка виглядає наступним чином:
![]() |
Проєкт [mv-jsf2-06] знову базується на єдиній сторінці [index.html] [1] та її шаблоні [Form.java] [2]. Він продовжує використовувати повідомлення з [messages.properties], але виключно французькою мовою [3]. Опція зміни мови недоступна.
2.8.3. Середовище роботи програми
Тут ми наводимо зміст файлів, що налаштовують додаток, без особливих пояснень. Ці файли допоможуть краще зрозуміти наведене нижче.
[faces-config.xml]
<?xml version='1.0' encoding='UTF-8'?>
<!-- =========== FULL CONFIGURATION FILE ================================== -->
<faces-config version="2.0"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_2_0.xsd">
<application>
<resource-bundle>
<base-name>
messages
</base-name>
<var>msg</var>
</resource-bundle>
<message-bundle>messages</message-bundle>
</application>
</faces-config>
Рядок 17 є новим. Його пояснення буде наведено пізніше.
Файл повідомлень [messages_fr.properties]
form.titre=Jsf - validations et conversions
saisie1.prompt=1-Nombre entier de type int
saisie2.prompt=2-Nombre entier de type int
saisie3.prompt=3-Nombre entier de type int
data.required=Vous devez entrer une donn\u00e9e
integer.required=Vous devez entrer un nombre entier
saisie4.prompt=4-Nombre entier de type int dans l'intervalle [1,10]
saisie4.error=4-Vous devez entrer un nombre entier dans l'intervalle [1,10]
saisie5.prompt=5-Nombre r\u00e9el de type double
double.required=Vous devez entrer un nombre
saisie6.prompt=6-Nombre r\u00e9el>=0 de type double
saisie6.error=6-Vous devez entrer un nombre >=0
saisie7.prompt=7-Bool\u00e9en
saisie7.error=7-Vous devez entrer un bool\u00e9en
saisie8.prompt=8-Date au format jj/mm/aaaa
saisie8.error=8-Vous devez entrer une date valide au format jj/mm/aaaa
date.required=Vous devez entrer une date
saisie9.prompt=9-Cha\u00eene de 4 caract\u00e8res
saisie9.error=9-Vous devez entrer une cha\u00eene de 4 caract\u00e8res exactement
saisie9B.prompt=9B-Heure au format hh:mm
saisie9B.error=La cha\u00eene saisie ne respecte pas le format hh:mm
submit=Valider
cancel=Annuler
saisie.type=Type de la saisie
saisie.champ=Champ de saisie
saisie.erreur=Erreur de saisie
bean.valeur=Valeurs du mod\u00e8le du formulaire
saisie10.prompt=10-Nombre entier de type int <1 ou >7
saisie10.incorrecte=10-Saisie n\u00b0 10 incorrecte
saisie10.incorrecte_detail=10-Vous devez entrer un nombre entier <1 ou >7
saisies11et12.incorrectes=La propri\u00e9t\u00e9 saisie11+saisie12=10 n'est pas v\u00e9rifi\u00e9e
saisies11et12.incorrectes_detail=La propri\u00e9t\u00e9 saisie11+saisie12=10 n'est pas v\u00e9rifi\u00e9e
saisie11.prompt=11-Nombre entier de type int
saisie12.prompt=12-Nombre entier de type int
error.sign="!"
error.sign_detail="!"
Таблиця стилів [styles.css] має такий вигляд:
.info{
font-family: Arial,Helvetica,sans-serif;
font-size: 14px;
font-weight: bold
}
.col1{
background-color: #ccccff
}
.col2{
background-color: #ffcccc
}
.col3{
background-color: #ffcc66
}
.col4{
background-color: #ccffcc
}
.error{
color: #ff0000
}
.saisie{
background-color: #ffcccc;
border-color: #000000;
border-width: 5px;
color: #cc0033;
font-family: cursive;
font-size: 16px
}
.entete{
font-family: 'Times New Roman',Times,serif;
font-size: 14px;
font-weight: bold
}
2.8.4. Сторінка [index.xhtml] та її шаблон [Form.java]
Сторінка [index.xhtml] виглядає так:
<?xml version='1.0' encoding='UTF-8' ?>
<!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:f="http://java.sun.com/jsf/core">
<h:head>
<title>JSF</title>
<h:outputStylesheet library="css" name="styles.css"/>
</h:head>
<h:body style="background-image: url('${request.contextPath}/resources/images/standard.jpg');">
<h2><h:outputText value="#{msg['form.titre']}"/></h2>
<h:form id="formulaire">
<h:messages globalOnly="true" />
<h:panelGrid columns="4" columnClasses="col1,col2,col3,col4" border="1">
<!-- рядок 1 -->
<h:outputText value="#{msg['saisie.type']}" styleClass="entete"/>
<h:outputText value="#{msg['saisie.champ']}" styleClass="entete"/>
<h:outputText value="#{msg['saisie.erreur']}" styleClass="entete"/>
<h:outputText value="#{msg['bean.valeur']}" styleClass="entete"/>
<!-- рядок 2 -->
<h:outputText value="#{msg['saisie1.prompt']}"/>
<h:inputText id="saisie1" value="#{form.saisie1}" styleClass="saisie"/>
<h:message for="saisie1" styleClass="error"/>
<h:outputText value="#{form.saisie1}"/>
<!-- рядок 3 -->
<h:outputText value="#{msg['saisie2.prompt']}" />
<h:inputText id="saisie2" value="#{form.saisie2}" styleClass="saisie"/>
<h:message for="saisie2" showSummary="true" showDetail="false" styleClass="error"/>
<h:outputText value="#{form.saisie2}"/>
<!-- рядок 4 -->
<h:outputText value="#{msg['saisie3.prompt']}" />
<h:inputText id="saisie3" value="#{form.saisie3}" styleClass="saisie" required="true" requiredMessage="#{msg['data.required']}" converterMessage="#{msg['integer.required']}"/>
<h:message for="saisie3" styleClass="error"/>
<h:outputText value="#{form.saisie3}"/>
<!-- рядок 5 -->
<h:outputText value="#{msg['saisie4.prompt']}" />
<h:inputText id="saisie4" value="#{form.saisie4}" styleClass="saisie" required="true" requiredMessage="#{msg['data.required']}" converterMessage="#{msg['integer.required']}" validatorMessage="#{msg['saisie4.error']}">
<f:validateLongRange minimum="1" maximum="10" />
</h:inputText>
<h:message for="saisie4" styleClass="error"/>
<h:outputText value="#{form.saisie4}"/>
<!-- рядок 6 -->
...
<!-- рядок 7 -->
...
<!-- рядок 8 -->
...
<!-- рядок 9 -->
...
<!-- рядок 10 -->
...
<!-- рядок 11 -->
...
<!-- рядок 12 -->
...
<!-- рядок 13 -->
...
</h:panelGrid>
<!-- кнопки управління -->
<h:panelGrid columns="2">
<h:commandButton value="#{msg['submit']}" action="#{form.submit}"/>
<h:commandButton value="#{msg['cancel']}" immediate="true" action="#{form.cancel}"/>
</h:panelGrid>
</h:form>
</h:body>
</html>
Головною новиною є наявність тегів:
- для відображення повідомлень про помилки <h:messages> (рядок 14), <h:message> (рядки 24, 29, 34),
- які встановлюють обмеження щодо правильності введених даних <f:validateLongRange> (рядок 39), <f:validateDoubleRange>, <f:validateLength>, <f:validateRegex>,
- які визначають конвертер між введеними даними та їхньою моделлю, наприклад <f:convertDateTime>.
Шаблоном цієї сторінки є наступний клас [Form.java]:
package forms;
import com.corejsf.util.Messages;
import java.util.Date;
import javax.enterprise.context.RequestScoped;
import javax.faces.application.FacesMessage;
import javax.faces.bean.ManagedBean;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.validator.ValidatorException;
@ManagedBean
@RequestScoped
public class Form {
public Form() {
}
// введення
private Integer saisie1 = 0;
private Integer saisie2 = 0;
private Integer saisie3 = 0;
private Integer saisie4 = 0;
private Double saisie5 = 0.0;
private Double saisie6 = 0.0;
private Boolean saisie7 = true;
private Date saisie8 = new Date();
private String saisie9 = "";
private Integer saisie10 = 0;
private Integer saisie11 = 0;
private Integer saisie12 = 0;
private String errorSaisie11 = "";
private String errorSaisie12 = "";
// дії
public String submit() {
...
}
public String cancel() {
...
}
// валідатори
public void validateSaisie10(FacesContext context, UIComponent component, Object value) {
...
}
// методи getter та setter
...
}
Новиною тут є те, що поля шаблону більше не є виключно типу String, а мають різні типи.
2.8.5. Різні записи у формі
Тепер по черзі розглянемо різні поля форми.
2.8.5.1. Поля 1–4: введення цілого числа
На сторінці [index.xhtml] поле 1 представлено у такому вигляді:
<!-- рядок 2 -->
<h:outputText value="#{msg['saisie1.prompt']}"/>
<h:inputText id="saisie1" value="#{form.saisie1}" styleClass="saisie"/>
<h:message for="saisie1" styleClass="error"/>
<h:outputText value="#{form.saisie1}"/>
Шаблон form.saisie1 визначено в [Form.java] наступним чином:
private Integer saisie1 = 0;
У браузері на сторінці GET сторінка [index.xhtml], пов’язана зі своїм шаблоном [Form.java], візуально відображається так:
- рядок 2 дає [1],
- рядок 3 дає [2],
- рядок 4 дає [3],
- рядок 5 дає [4].
Припустимо, що введено та підтверджено таке:
![]() |
У результаті у формі, що повертається додатком, ми отримаємо такий результат:
![]() |
- у [1] — помилкове введення,
- у [2] — повідомлення про помилку, що на це вказує,
- у [3] видно, що значення поля Integer «введене1» моделі не змінилося.
Давайте розберемося, що сталося. Для цього повернемося до циклу обробки сторінки JSF:
![]() |
Ми розглядаємо цей цикл для компонента:
<h:inputText id="saisie1" value="#{form.saisie1}" styleClass="saisie"/>
та його шаблон:
private Integer saisie1 = 0;
- у [A] відновлюється сторінка [index.xhtml], надіслана під час GET браузера. У [A] сторінка виглядає так, як її отримав користувач. Компонент id="saisie1" повертається до початкового значення «0»,
- у [B] компоненти сторінки отримують значення, надіслані браузером. У [B] сторінка виглядає так, як її ввів і підтвердив користувач. Компонент id="saisie1" отримує значення «x»,
- у [C], якщо сторінка містить явні валідатори та конвертери, вони виконуються. Неявні конвертери також виконуються, якщо тип поля, пов’язаного з компонентом, не є типом String. Це саме той випадок, коли поле form.saisie1 має тип Integer. JSF спробує перетворити значення «x» компонента id="saisie1" у тип Integer. Це спричинить помилку, яка зупинить цикл обробки [A-F]. Ця помилка буде пов’язана з компонентом id="saisie1". Через [D2] потім відбувається прямий перехід до етапу формування відповіді. Повертається та сама сторінка [index.xhtml],
- фаза [D] відбувається лише в тому випадку, якщо всі компоненти сторінки пройшли фазу перетворення/валідації. Саме на цій фазі значення компонента id="saisie1" буде присвоєно його шаблону form.saisie1.
Якщо фаза [C] завершиться невдало, сторінка відобразиться знову, і буде повторно виконано такий код:
<!-- рядок 2 -->
<h:outputText value="#{msg['saisie1.prompt']}"/>
<h:inputText id="saisie1" value="#{form.saisie1}" styleClass="saisie"/>
<h:message for="saisie1" styleClass="error"/>
<h:outputText value="#{form.saisie1}"/>
![]() |
Повідомлення, що відображається в [2], походить із рядка 4 файлу [index.xhtml]. Тег <h:message for="idComposant"/> відображає повідомлення про помилку, пов’язане з компонентом, вказаним атрибутом for, якщо така помилка трапилася. Повідомлення, що відображається в [2], є стандартним і міститься у файлі [javax/faces/Messages.properties] архіву [jsf-api.jar]:
![]() |
У [2] видно, що файл повідомлень існує у кількох варіантах. Розглянемо вміст [Messages_fr.properties]:
Файл містить повідомлення, розділені на категорії:
- помилки в компоненті, рядок 3,
- помилки перетворення між компонентом та його моделлю, рядок 12
- помилки перевірки, якщо на сторінці присутні валідатори, рядок 23.
Помилка, що сталася у компоненті id="saisie1", є помилкою перетворення типу String у тип Integer. Відповідне повідомлення про помилку міститься у рядку 18 файлу повідомлень.
javax.faces.converter.IntegerConverter.INTEGER_detail={2} : «{0}» doit être un nombre compris entre -2147483648 et 2147483647. Exemple : {1}
Повідомлення про помилку, яке було відображено, наведено нижче:
![]() |
Як бачимо, у повідомленні:
- параметр {2} замінено на ідентифікатор компонента, для якого сталася помилка перетворення,
- параметр {0} замінено на значення, введене в [1] для цього компонента,
- параметр {1} замінено на число 9346.
Більшість повідомлень, пов’язаних із компонентами, мають дві версії: стислу (summary) та детальну (detail). Це стосується рядків 16–18:
Повідомлення з ключем _detail (рядок 2) є так званим детальним повідомленням. Інше — так зване стисле повідомлення. Тег <h:message> за замовчуванням відображає детальне повідомлення. Цю поведінку можна змінити за допомогою атрибутів showSummary та showDetail. Саме це зроблено для компонента з ідентифікатором saisie2:
<!-- рядок 3 -->
<h:outputText value="#{msg['saisie2.prompt']}" />
<h:inputText id="saisie2" value="#{form.saisie2}" styleClass="saisie"/>
<h:message for="saisie2" showSummary="true" showDetail="false" styleClass="error"/>
<h:outputText value="#{form.saisie2}"/>
У рядку 2 компонент saisie2 пов’язаний із наступним полем form.saisie2:
private Integer saisie2 = 0;
Отриманий результат такий:
![]() |
- у [1] — детальне повідомлення, у [2] — стисле повідомлення.
Тег <h:messages> відображає у вигляді списку всі узагальнені повідомлення про помилки всіх компонентів, а також повідомлення про помилки, що не пов’язані з жодним компонентом. І в цьому випадку атрибути можуть змінити цю поведінку за замовчуванням:
- showDetail: true / false — для виведення або приховування детальних повідомлень,
- showSummary: true / false — для вибору, чи відображати узагальнені повідомлення,
- globalOnly: true / false — для вибору, чи відображати лише повідомлення про помилки, не пов’язані з компонентами. Таке повідомлення може, наприклад, бути створене розробником.
Повідомлення про помилку, пов’язане з конвертацією, можна змінити різними способами. По-перше, можна вказати програмі використовувати інший файл повідомлень. Ця зміна здійснюється в [faces-config.xml]:
<faces-config ...">
<application>
<resource-bundle>
<base-name>
messages
</base-name>
<var>msg</var>
</resource-bundle>
<message-bundle>messages</message-bundle>
</application>
...
</faces-config>
Рядки 3–8 визначають файл повідомлень, але саме він не використовується тегами <h:message> та <h:messages>. Для його визначення слід використовувати тег <message-bundle> у рядку 9. Рядок 9 вказує тегам <h:message(s)>, що файл [messages.properties] має бути просканований перед файлом [javax.faces.Messages.properties]. Отже, якщо додати до файлу [messages_fr.properties] такі рядки:
# перетворення
javax.faces.converter.IntegerConverter.INTEGER=erreur
javax.faces.converter.IntegerConverter.INTEGER_detail=erreur d\u00e9taill\u00e9e
помилка, що повертається для компонентів saisie1 та saisie2, стане такою:

Інший спосіб змінити повідомлення про помилку конвертації — це використати атрибут converterMessage компонента, як показано нижче для компонента saisie3:
<!-- рядок 4 -->
<h:outputText value="#{msg['saisie3.prompt']}" />
<h:inputText id="saisie3" value="#{form.saisie3}" styleClass="saisie" required="true" requiredMessage="#{msg['data.required']}" converterMessage="#{msg['integer.required']}"/>
<h:message for="saisie3" styleClass="error"/>
<h:outputText value="#{form.saisie3}"/>
Компонент saisie3 пов’язаний із таким полем form.saisie3:
private Integer saisie3 = 0;
- у рядку 3 атрибут converterMessage явно визначає повідомлення, яке має відображатися у разі помилки конвертації,
- у рядку 3 атрибут required="true" вказує, що заповнення поля є обов’язковим. Поле не може залишатися порожнім. Поле вважається порожнім, якщо воно не містить жодного символу або містить послідовність пробілів. І тут у [javax.faces.Messages.properties] також передбачено повідомлення за замовчуванням:
Атрибут requiredMessage дозволяє замінити це повідомлення за замовчуванням. Якщо файл [messages.properties] містить такі повідомлення:
...
data.required=Vous devez entrer une donnée
integer.required=Vous devez entrer un nombre entier
можна отримати такий результат:
![]() |
або ось такий:
![]() |
Перевірка того, чи введене значення є цілим числом, не завжди є достатньою. Іноді потрібно перевірити, чи введене число належить до заданого інтервалу. У цьому випадку використовується валідатор. Прикладом цього є введення № 4. Його код у [index.xhtml] такий:
<!-- рядок 5 -->
<h:outputText value="#{msg['saisie4.prompt']}" />
<h:inputText id="saisie4" value="#{form.saisie4}" styleClass="saisie" required="true" requiredMessage="#{msg['data.required']}" converterMessage="#{msg['integer.required']}" validatorMessage="#{msg['saisie4.error']}">
<f:validateLongRange minimum="1" maximum="10" />
</h:inputText>
<h:message for="saisie4" styleClass="error"/>
<h:outputText value="#{form.saisie4}"/>
У рядку 3 компонент saisie4 пов’язаний із наступною моделлю form.saisie4:
private Integer saisie4 = 0;
У рядках 3–5 тег <h:inputText> має дочірній тег <f:validateLongRange>, який підтримує два необов’язкові атрибути: minimum та maximum. Цей тег, який також називають валідатором, дозволяє додати обмеження до значення, що вводиться: воно має бути не просто цілим числом, а цілим числом у проміжку [minimum, maximum], якщо присутні обидва атрибути minimum та maximum, більше або дорівнює minimum, якщо присутній лише атрибут minimum, менше або дорівнює maximum, якщо присутній лише атрибут maximum. Валідатор <f:validateLongRange> має стандартні повідомлення про помилки в [javax.faces.Messages.properties]:
Знову ж таки, ці повідомлення можна замінити на інші. Існує атрибут validatorMessage, який дозволяє визначити конкретне повідомлення для компонента. Так, із таким кодом JSF:
<h:inputText id="saisie4" value="#{form.saisie4}" styleClass="saisie" required="true" requiredMessage="#{msg['data.required']}" converterMessage="#{msg['integer.required']}" validatorMessage="#{msg['saisie4.error']}">
<f:validateLongRange minimum="1" maximum="10" />
</h:inputText>
та таким повідомленням у [messages.properties]:
saisie4.error=4-Vous devez entrer un nombre entier dans l'intervalle [1,10]
отримуємо такий результат:

2.8.5.2. Поля 5 і 6: введення дійсного числа
Введення дійсних чисел відбувається за правилами, подібними до правил введення цілих чисел. Код XHTML для полів 5 і 6 є таким:
<!-- рядок 6 -->
<h:outputText value="#{msg['saisie5.prompt']}" />
<h:inputText id="saisie5" value="#{form.saisie5}" styleClass="saisie" required="true" requiredMessage="#{msg['data.required']}" converterMessage="#{msg['double.required']}"/>
<h:message for="saisie5" styleClass="error"/>
<h:outputText value="#{form.saisie5}"/>
<!-- рядок 7 -->
<h:outputText value="#{msg['saisie6.prompt']}"/>
<h:inputText id="saisie6" value="#{form.saisie6}" styleClass="saisie" required="true" requiredMessage="#{msg['data.required']}" converterMessage="#{msg['double.required']}" validatorMessage="#{msg['saisie6.error']}">
<f:validateDoubleRange minimum="0.0"/>
</h:inputText>
<h:message for="saisie6" styleClass="error"/>
<h:outputText value="#{form.saisie6}"/>
Елементи шаблону [Form.java], пов’язані з компонентами saisie5 та saisie6:
private Double saisie5 = 0.0;
private Double saisie6 = 0.0;
Повідомлення про помилки, пов’язані з конвертерами та валідаторами компонентів saisie5 та saisie6, у [messages.properties]:
double.required=Vous devez entrer un nombre
saisie6.error=6-Vous devez entrer un nombre >=0
Ось приклад виконання:

2.8.5.3. Введення 7: введення булевого значення
Введення булевого значення зазвичай здійснюється за допомогою прапорця. Якщо це робиться за допомогою поля введення, рядок «true» перетворюється на булеве значення true, а будь-який інший рядок — на булеве значення false.
Код XHTML із прикладу:
<!-- рядок 8 -->
<h:outputText value="#{msg['saisie7.prompt']}"/>
<h:inputText id="saisie7" value="#{form.saisie7}" styleClass="saisie" required="true" requiredMessage="#{msg['data.required']}" converterMessage="#{msg['double.required']}"/>
<h:message for="saisie7" styleClass="error"/>
<h:outputText value="#{form.saisie7}"/>
Шаблон компонента saisie7:
private Boolean saisie7 = true;
Ось приклад введення даних та відповідь на нього:
![]() |
У [1] — введене значення. У результаті перетворення цей рядок «x» стає логічним значенням false. Це показує [2]. Значення [3] у шаблоні не змінилося. Воно змінюється лише тоді, коли всі перетворення та перевірки на сторінці пройшли успішно. У цьому прикладі цього не сталося.
2.8.5.4. Введення 8: введення дати
У цьому прикладі введення дати здійснюється за допомогою такого коду: XHTML:
<!-- рядок 9 -->
<h:outputText value="#{msg['saisie8.prompt']}"/>
<h:inputText id="saisie8" value="#{form.saisie8}" styleClass="saisie" required="true" requiredMessage="#{msg['date.required']}" converterMessage="#{msg['saisie8.error']}">
<f:convertDateTime pattern="dd/MM/yyyy"/>
</h:inputText>
<h:message for="saisie8" styleClass="error"/>
<h:outputText value="#{form.saisie8}">
<f:convertDateTime pattern="dd/MM/yyyy"/>
</h:outputText>
Компонент saisie8 у рядку 3 використовує конвертер java.lang.String <--> java.util.Date. Шаблон form.saisie8, пов’язаний із компонентом saisie8, має такий вигляд:
private Date saisie8 = new Date();
Компонент, визначений рядками 7–9, також використовує конвертер, але лише у напрямку java.util.Date --> java.lang.String.
Конвертер <f:convertDateTime> підтримує різні атрибути, зокрема атрибут pattern, який визначає формат символьного рядка, що має бути перетворений на дату, або формат, у якому має відображатися дата.
Під час першого запиту сторінки [index.xhtml] попередній рядок 8 відображається так:
Поля [1] та [2] відображають значення шаблону form.saisie8:
private Date saisie8 = new Date();
де saisie8 приймає значення поточної дати. У обох випадках для відображення дати використовується такий конвертер:
<f:convertDateTime pattern="dd/MM/yyyy"/>
де dd (day) позначає номер дня, MM (Month) — номер місяця, а yyyy (year) — рік. У [1] конвертер використовується для зворотного перетворення java.lang.String --> java.util.Date. Отже, щоб дата була дійсною, вона повинна відповідати формату «dd/MM/yyyy».
У [javax.faces.Messages.properties] передбачені стандартні повідомлення для недійсних дат:
які можна замінити на власні повідомлення. Так, у наведеному прикладі:
<h:inputText id="saisie8" value="#{form.saisie8}" styleClass="saisie" required="true" requiredMessage="#{msg['date.required']}" converterMessage="#{msg['saisie8.error']}">
<f:convertDateTime pattern="dd/MM/yyyy"/>
</h:inputText>
повідомленням, що відображається у разі помилки конвертації, буде наступне повідомлення з ключем saisie8.error:
saisie8.error=8-Vous devez entrer une date valide au format jj/mm/aaaa
Ось приклад:
![]()
2.8.5.5. Введення 9: введення рядка з обмеженою довжиною
Приклад 9 демонструє, як задати, щоб введений рядок містив кількість символів, що знаходиться в заданому діапазоні:
<!-- рядок 10 -->
<h:outputText value="#{msg['saisie9.prompt']}"/>
<h:inputText id="saisie9" value="#{form.saisie9}" styleClass="saisie" required="true" requiredMessage="#{msg['data.required']}" validatorMessage="#{msg['saisie9.error']}">
<f:validateLength minimum="4" maximum="4"/>
</h:inputText>
<h:message for="saisie9" styleClass="error"/>
<h:outputText value="#{form.saisie9}"/>
У рядку 4 валідатор <f:validateLength minimum="4" maximum="4"/> вимагає, щоб введений рядок містив рівно 4 символи. Можна використовувати лише один з атрибутів: minimum — для мінімальної кількості символів, maximum — для максимальної.
Шаблон form.saisie9 для компонента saisie9 у рядку 3 має такий вигляд:
private String saisie9 = "";
Для цього типу перевірки існують стандартні повідомлення про помилки:
які можна замінити, використовуючи атрибут validatorMessage, як у рядку 3 вище. Повідомлення для ключа saisie9.error має такий вигляд:
saisie9.error=9-Vous devez entrer une chaîne de 4 caractères exactement
Ось приклад виконання:
![]()
2.8.5.6. Введення 9B: введення рядка, що має відповідати шаблону
Приклад введення 9B демонструє, як обмежити кількість символів у введеному рядку певним діапазоном:
<!-- рядок 10B -->
<h:outputText value="#{msg['saisie9B.prompt']}"/>
<h:inputText id="saisie9B" value="#{form.saisie9B}" styleClass="saisie" required="true" requiredMessage="#{msg['data.required']}" validatorMessage="#{msg['saisie9B.error']}">
<f:validateRegex pattern="^\s*\d{2}:\d{2}\s*$"/>
</h:inputText>
<h:message for="saisie9B" styleClass="error"/>
<h:outputText value="#{form.saisie9B}"/>
У рядку 4 валідатор <f:validateRegex pattern="^\s*\d{2}:\d{2}\s*$"/> вимагає, щоб введений рядок відповідав шаблону регулярного виразу, а саме: послідовність із 0 або більше пробілів, 2 цифри, знак :, 2 цифри, послідовність з 0 або більше пробілів.
Шаблон form.saisie9B компонента saisie9B у рядку 3 має такий вигляд:
private String saisie9B;
Для цього типу перевірки існують стандартні повідомлення про помилки:
які можна замінити, використовуючи атрибут validatorMessage, як у рядку 3 вище. Повідомлення ключа saisie9.error має такий вигляд:
saisie9B.error=La cha\u00eene saisie ne respecte pas le format hh:mm
Ось приклад виконання:
![]()
2.8.5.7. Завдання 10: написати спеціальний метод перевірки
Підсумуємо: JSF дозволяє перевіряти серед введених значень правильність чисел (цілих, дійсних), дат, довжину рядків та відповідність введеного значення регулярному виразу. JSF дозволяє додавати до існуючих валідаторів та конвертерів власні валідатори та конвертери. Цей пункт тут не розглядається, але для більш детального вивчення можна ознайомитися з [ref2].
Тут ми розглянемо інший метод: перевірку введених даних за допомогою методу моделі форми. Ось приклад:
<!-- рядок 11 -->
<h:outputText value="#{msg['saisie10.prompt']}"/>
<h:inputText id="saisie10" value="#{form.saisie10}" styleClass="saisie" required="true" requiredMessage="#{msg['data.required']}" validator="#{form.validateSaisie10}"/>
<h:message for="saisie10" styleClass="error"/>
<h:outputText value="#{form.saisie10}"/>
Модель form.saisie10, пов’язана з компонентом saisie10 у рядку 3, виглядає так:
private Integer saisie10 = 0;
Потрібно, щоб введене число було <1 або >7. Це неможливо перевірити за допомогою базових валідаторів JSF. Тому ми пишемо власний метод валідації для компонента saisie10. Його вказуємо за допомогою атрибута validator компонента, що підлягає валідації:
<h:inputText id="saisie10" value="#{form.saisie10}" styleClass="saisie" required="true" requiredMessage="#{msg['data.required']}" validator="#{form.validateSaisie10}"/>
Компонент saisie10 перевіряється методом form.validateSaisie10. Він має такий вигляд:
public void validateSaisie10(FacesContext context, UIComponent component, Object value) {
int saisie = (Integer) value;
if (!(saisie < 1 || saisie > 7)) {
FacesMessage message = Messages.getMessage(null, "saisie10.incorrecte", null);
message.setSeverity(FacesMessage.SEVERITY_ERROR);
throw new ValidatorException(message);
}
}
Сигнатура методу перевірки обов’язково має відповідати сигнатурі в рядку 1:
- FacesContext context: контекст виконання сторінки — надає доступ до різної інформації, зокрема до об’єктів HttpServletRequest request та HttpServletResponse response,
- UIComponent component: компонент, який потрібно перевірити. Тег <h:inputText> представлений компонентом типу UIInput, похідним від UIComponent. У цьому випадку саме цей компонент UIInput приймається як другий параметр,
- Значення об’єкта: введене значення, яке потрібно перевірити, перетворене у тип його моделі. Тут важливо розуміти, що якщо перетворення String → тип моделі завершилося невдало, то метод перевірки не виконується. Коли виконується метод validateSaisie10, це означає, що перетворення String → Integer пройшло успішно. Тоді третій параметр має тип Integer.
- рядок 2: введене значення перетворюється на тип int,
- рядок 3: перевіряється, чи введене значення <1 або >7. Якщо так, перевірка завершується. Якщо ні, валідатор повинен повідомити про помилку, викликом винятку типу ValidatorException.
Клас ValidatorException має два конструктори:
![]() |
- конструктор [1] приймає як параметр повідомлення про помилку типу FacesMessage. Цей тип повідомлення відображається тегами <h:messages> та <h:message>,
- а конструктор [2] дозволяє додатково інкапсулювати причину типу Throwable або похідну від помилки.
Нам потрібно створити повідомлення типу FacesMessage. Цей клас має кілька конструкторів:
![]() |
Конструктор [1] визначає властивості об’єкта FacesMessage:
- FacesMessage.Severity severity: рівень серйозності, взятий із наступного переліку: SEVERITY_ERROR, SEVERITY_FATAL, SEVERITY_INFO, SEVERITY_WARN,
- String summary: стислий варіант повідомлення про помилку — відображається за допомогою тегів <h:message showSummary="true"> та <h:messages>,
- String detail: детальна версія повідомлення про помилку — відображається за допомогою тегів <h:message> та <h:messages showDetail="true">.
Можна використовувати будь-який з конструкторів, а відсутні параметри можна встановити пізніше за допомогою методів set.
Конструктор [1] не дозволяє вказати повідомлення, яке міститься у файлі інтернаціоналізованих повідомлень. Це, звісно, прикро. Девід Гірі та Кай Хорстманн у своїй книзі «Core JavaServer Faces» усувають цю прогалину за допомогою утилітарного класу com.corejsf.util.Messages. Саме цей клас використовується в 4-му рядку коду Java для створення повідомлення про помилку. Він містить лише статичні методи, серед яких метод getMessage, що використовується в 4-му рядку:
public static FacesMessage getMessage(String bundleName, String resourceId, Object[] params)
Метод getMessage приймає три параметри:
- String bundleName: ім’я файлу повідомлень без розширення .properties, але з іменем пакета. У даному випадку нашим першим параметром може бути messages, що вказує на файл [messages.properties]. Перш ніж використовувати файл, вказаний першим параметром, getMessage намагається використати файл повідомлень програми, якщо такий існує. Отже, якщо у файлі [faces-config.xml] оголошено файл повідомлень за допомогою тегу:
<application>
...
<message-bundle>messages</message-bundle>
</application>
то можна передати null як перший параметр методу getMessage. Саме це й було зроблено тут (див. [web.xm], стор. 120),
- String resourceId: ключ повідомлення, яке потрібно обробити, у файлі повідомлень. Ми бачили, що повідомлення може мати як скорочену, так і детальну версію. resourceId — це ідентифікатор скороченої версії. Детальна версія буде знайдена автоматично за ключем resourceId_detail. Таким чином, у [messages.properties] ми матимемо два повідомлення щодо помилки у записі № 10:
saisie10.incorrecte=10-Saisie n° 10 incorrecte
saisie10.incorrecte_detail=10-Vous devez entrer un nombre entier <1 ou >7
Повідомлення типу FacesMessage, створене методом Messages.getMessage, містить як стислу, так і детальну версії, якщо їх було знайдено. Обидві версії мають бути присутніми, інакше виникає виняток типу [NullPointerException],
- Object[] params: фактичні параметри повідомлення, якщо воно має формальні параметри {0}, {1}, ... Ці формальні параметри будуть замінені елементами масиву params.
Повернемося до коду методу перевірки компонента saisie10:
public void validateSaisie10(FacesContext context, UIComponent component, Object value) {
int saisie = (Integer) value;
if (!(saisie < 1 || saisie > 7)) {
FacesMessage message = Messages.getMessage(null, "saisie10.incorrecte", null);
message.setSeverity(FacesMessage.SEVERITY_ERROR);
throw new ValidatorException(message);
}
}
- у [4] повідомлення типу FacesMessage створюється за допомогою статичного методу Messages.getMessage,
- у [5] встановлюється рівень серйозності повідомлення,
- у [6] генерується виняток типу ValidatorException із попередньо сформованим повідомленням. Метод перевірки був викликаний наступним кодом XHTML:
<!-- рядок 11 -->
<h:outputText value="#{msg['saisie10.prompt']}"/>
<h:inputText id="saisie10" value="#{form.saisie10}" styleClass="saisie" required="true" requiredMessage="#{msg['data.required']}" validator="#{form.validateSaisie10}"/>
<h:message for="saisie10" styleClass="error"/>
<h:outputText value="#{form.saisie10}"/>
У рядку 3 метод перевірки виконується для компонента з ідентифікатором saisie10. Отже, повідомлення про помилку, згенероване методом validateSaisie10, пов’язується з цим компонентом і, відповідно, відображається у рядку 4 (атрибут for="saisie10"). За замовчуванням тег <h:message> відображає саме детальну версію.
Ось приклад виконання:
![]()
2.8.5.8. Поля 11 і 12: перевірка групи компонентів
До цього моменту розглянуті методи перевірки перевіряли лише один компонент. Як вчинити, якщо необхідна перевірка стосується кількох компонентів? Саме це ми й розглянемо зараз. У формі:

ми хочемо, щоб поля 11 і 12 містили два цілих числа, сума яких дорівнює 10.
Код JSF матиме такий вигляд:
<!-- рядок 12 -->
<h:outputText value="#{msg['saisie11.prompt']}"/>
<h:inputText id="saisie11" value="#{form.saisie11}" styleClass="saisie" required="true" requiredMessage="#{msg['data.required']}" converterMessage="#{msg['integer.required']}"/>
<h:panelGroup>
<h:message for="saisie11" styleClass="error"/>
<h:outputText value="#{form.errorSaisie11}" styleClass="error"/>
</h:panelGroup>
<h:outputText value="#{form.saisie11}"/>
<!-- рядок 13 -->
<h:outputText value="#{msg['saisie12.prompt']}"/>
<h:inputText id="saisie12" value="#{form.saisie12}" styleClass="saisie" required="true" requiredMessage="#{msg['data.required']}" converterMessage="#{msg['integer.required']}"/>
<h:panelGroup>
<h:message for="saisie12" styleClass="error"/>
<h:outputText value="#{form.errorSaisie12}" styleClass="error"/>
</h:panelGroup>
<h:outputText value="#{form.saisie12}"/>
а відповідний шаблон:
private Integer saisie11 = 0;
private Integer saisie12 = 0;
private String errorSaisie11 = "";
private String errorSaisie12 = "";
У рядку 3 коду JSF використовуються вже описані методи для перевірки того, що значення, введене для компонента saisie11, дійсно є цілим числом. Те саме стосується рядка 11 для компонента saisie12. Щоб перевірити, що saisie11 + saisie12 = 10, можна було б створити спеціальний валідатор. Це найкращий варіант. Знову ж таки, щоб його знайти, потрібно прочитати [ref2]. Тут ми дотримуємося іншого підходу.
Сторінка [index.xhtml] перевіряється за допомогою кнопки [Valider], код якої JSF має такий вигляд:
<!-- кнопки управління -->
<h:panelGrid columns="2">
<h:commandButton value="#{msg['submit']}" action="#{form.submit}"/>
...
</h:panelGrid>
де повідомлення msg['submit'] має такий вигляд:
submit=Valider
У рядку 3 видно, що для обробки кліка на кнопці [Valider] буде виконано метод form.submit. Він має такий вигляд:
// дії
public String submit() {
// останні підтвердження
validateForm();
// повертається та сама форма
return null;
}
// загальні підтвердження
private void validateForm() {
if ((saisie11 + saisie12) != 10) {
...
}
Важливо розуміти, що під час виконання методу submit:
- всі валідатори та конвертери форми вже виконані й пройшли успішно,
- поля моделі [Form.java] отримали значення, надіслані клієнтом.
Дійсно, повернімося до циклу обробки POST JSF:
![]() |
Метод submit є обробником подій. Він обробляє подію clic, пов’язану з кнопкою [Valider]. Як і всі обробники подій, він виконується на етапі [E], після того як усі валідатори та конвертери були виконані успішно ([C]) і модель була оновлена з урахуванням відправлених значень ([D]). Тому тут вже не йдеться про генерацію винятків типу [ValidatorException], як ми робили раніше. Ми просто надішлемо форму назад із повідомленнями про помилки:
![]() |
У [1] ми попередимо користувача, а в [2] та [3] поставимо позначку про помилку. У коді JSF повідомлення [1] буде отримано таким чином:
<h:form id="formulaire">
<h:messages globalOnly="true" />
<h:panelGrid columns="4" columnClasses="col1,col2,col3,col4" border="1">
<!-- рядок 1 -->
...
У рядку 2 тег <h:messages> за замовчуванням відображає стислу версію повідомлень про помилки для всіх неправильно введених даних компонентів форми, а також усі повідомлення про помилки, не пов’язані з компонентами. Атрибут globalOnly="true" обмежує відображення лише цими повідомленнями.
Повідомлення [2] та [3] відображаються за допомогою простих тегів <h:outputText>:
<!-- рядок 12 -->
<h:outputText value="#{msg['saisie11.prompt']}"/>
<h:inputText id="saisie11" value="#{form.saisie11}" styleClass="saisie" required="true" requiredMessage="#{msg['data.required']}" converterMessage="#{msg['integer.required']}"/>
<h:panelGroup>
<h:message for="saisie11" styleClass="error"/>
<h:outputText value="#{form.errorSaisie11}" styleClass="error"/>
</h:panelGroup>
<h:outputText value="#{form.saisie11}"/>
<!-- рядок 13 -->
...
<h:outputText value="#{form.errorSaisie12}" styleClass="error"/>
...
У рядках 4–7 компонент saisie11 може видавати два можливі повідомлення про помилку:
- те, що вказує на помилку конвертації або відсутність даних. Це повідомлення, згенероване самою програмою JSF, буде міститися в типі FacesMessage і відображатися за допомогою тегу <h:message> у рядку 5,
- те, яке ми згенеруємо, якщо введення11 + введення12 не дорівнює 10. Воно відображатиметься у рядку 6. Повідомлення про помилку міститиметься у шаблоні form.errorSaisie11.
Обидва повідомлення відповідають помилкам, які не можуть трапитися одночасно. Перевірка «введення11 + введення12 = 10» виконується в методі submit, який запускається лише тоді, коли у формі не залишилося жодної помилки. Коли він виконається, компонент saisie11 буде перевірено, а його шаблон form.saise11 отримає своє значення. Повідомлення в рядку 5 більше не зможе відображатися. І навпаки, якщо повідомлення в рядку 5 відображається, це означає, що у формі залишилася принаймні одна помилка, і метод submit не буде виконуватися. Повідомлення в рядку 6 не відображатиметься. Щоб обидва можливі повідомлення про помилки знаходилися в одному стовпці таблиці, їх об’єднано в тег <h:panelGroup> (рядки 4 та 7).
Метод submit виглядає наступним чином:
// дії
public String submit() {
// останні перевірки
validateForm();
// повторно надсилається та сама форма
return null;
}
// загальні перевірки
private void validateForm() {
if ((saisie11 + saisie12) != 10) {
// загальне повідомлення
FacesMessage message = Messages.getMessage(null, "saisies11et12.incorrectes", null);
message.setSeverity(FacesMessage.SEVERITY_ERROR);
FacesContext context = FacesContext.getCurrentInstance();
context.addMessage(null, message);
// повідомлення, пов'язані з полями
message = Messages.getMessage(null, "error.sign", null);
setErrorSaisie11(message.getSummary());
setErrorSaisie12(message.getSummary());
} else {
setErrorSaisie11("");
setErrorSaisie12("");
}
}
- рядок 4: метод submit викликає метод validateForm для виконання останніх перевірок,
- рядок 11: перевіряється, чи saisie11+saisie12=10,
- якщо це не так, у рядках 13–14 створюється повідомлення типу FacesMessage з ідентифікатором saisies11et12.incorrectes. Воно має такий вигляд:
saisies11et12.incorrectes=La propriété saisie11+saisie12=10 n'est pas vérifiée
- створене таким чином повідомлення додається (рядки 15–16) до списку повідомлень про помилки додатка. Це повідомлення не пов’язане з конкретним компонентом. Це загальне повідомлення додатка. Воно відображатиметься за допомогою тегу <h:messages globalOnly="true"/>, наведеного вище,
- рядок 18: створюється нове повідомлення типу FacesMessage з ідентифікатором error.sign. Воно має такий вигляд:
error.sign="!"
Ми вже зазначали, що статичний метод [Messages.getMessage] формує повідомлення типу FacesMessage із скороченою та детальною версіями, якщо вони існують. У даному випадку існує лише скорочена версія повідомлення error.sign. Скорочену версію повідомлення m отримуємо за допомогою функції m.getSummary(). У рядках 19 і 20 скорочена версія повідомлення error.sign розміщується в полях errorSaisie11 та errorSaisie12 шаблону. Вони будуть відображатися за допомогою таких тегів JSF:
<h:outputText value="#{form.saisie11}"/>
...
<h:outputText value="#{form.saisie12}"/>
- рядки 22–23: якщо властивість saisie11+saisie12=10 перевірено, то обидва поля errorSaisie11 та errorSaisie12 шаблону очищаються, щоб видалити можливе попереднє повідомлення про помилку. Слід пам’ятати, що модель зберігається між запитами у сесії клієнта.
Ось приклад виконання:
![]() |
У стовпці [1] можна помітити, що шаблон отримав відправлені значення, що свідчить про успішне виконання всіх операцій перевірки та перетворення між відправленими значеннями та шаблоном. Таким чином, вдалося виконати обробник події form.submit, який обробляє натискання кнопки [Valider]. Саме він згенерував повідомлення, що відображаються в [2] та [3]. Видно, що модель було оновлено, хоча форма була відхилена та повернута клієнту. У такому випадку бажано, щоб шаблон не оновлювався. Адже якщо уявити, що користувач скасує оновлення за допомогою кнопки [Annuler] [4], повернутися до початкового шаблону буде неможливо, якщо його не збережено.
2.8.5.9. POST — форма без перевірки введених даних
Розглянемо наведену вище форму та припустимо, що користувач, не розуміючи своїх помилок, хоче припинити заповнення форми. Тоді він скористається кнопкою [Annuler], згенерованою за допомогою такого коду JSF:
<!-- кнопки управління -->
<h:panelGrid columns="2">
<h:commandButton value="#{msg['submit']}" action="#{form.submit}"/>
<h:commandButton value="#{msg['cancel']}" immediate="true" action="#{form.cancel}"/>
</h:panelGrid>
У рядку 4 повідомлення msg['cancel'] має такий вигляд:
cancel=Annuler
Метод form.cancel, пов’язаний із кнопкою [Annuler], буде виконано лише в тому випадку, якщо форма є валідною. Саме це ми продемонстрували на прикладі методу form.submit, пов’язаного із кнопкою [Valider]. Якщо користувач бажає скасувати введення даних у форму, перевіряти правильність його введених даних, звісно, немає сенсу. Цей результат досягається за допомогою атрибута immediate="true", який вказує JSF виконати метод form.cancel, оминаючи етапи перевірки та перетворення. Повернемося до циклу обробки POST JSF:
![]() |
Події компонентів дії <h:commandButton> та <h:commandLink>, що мають атрибут immediate="true", обробляються на етапі [C], після чого цикл JSF безпосередньо переходить до етапу [E], на якому відбувається формування відповіді.
Метод form.cancel виглядає наступним чином:
public String cancel() {
saisie1 = 0;
saisie2 = 0;
saisie3 = 0;
saisie4 = 0;
saisie5 = 0.0;
saisie6 = 0.0;
saisie7 = true;
saisie8 = new Date();
saisie9 = "";
saisie10 = 0;
return null;
}
Якщо натиснути кнопку [Annuler] у попередній формі, у відповідь отримаємо таку сторінку:
![]() |
- ми знову отримуємо форму, оскільки обробник події form.cancel повертає ключ навігації null. Отже, повертається сторінка [index.xhtml],
- шаблон [Form.java] було змінено методом form.cancel. Це відображається у стовпці [2], який показує цей шаблон,
- а стовпець [3] відображає значення, записані для компонентів.
Повернемося до коду JSF компонента saisie1 [4];
<!-- рядок 1 -->
<h:outputText value="#{msg['saisie1.prompt']}"/>
<h:inputText id="saisie1" value="#{form.saisie1}" styleClass="saisie"/>
<h:message for="saisie1" styleClass="error"/>
<h:outputText value="#{form.saisie1}"/>
У рядку 4 значення компонента saisie1 пов’язане з шаблоном form.saisie1. Це спричиняє кілька наслідків:
- під час виконання GET з [index.xhtml] компонент saisie1 відображатиме значення шаблону form.saisie1,
- під час виконання POST з [index.xhtml], значення, введене для компонента saisie1, присвоюється шаблону form.saisie1 лише в тому випадку, якщо всі перевірки та перетворення форми пройшли успішно. Незалежно від того, чи була модель оновлена за допомогою відправлених значень, якщо форма повертається після завершення POST, компоненти відображають саме відправлене значення, а не значення моделі, до якої вони прив’язані. Це показано на знімку екрана вище, де стовпці [2] та [3] мають різні значення.
2.9. Приклад mv-jsf2-07: події, пов’язані зі зміною стану компонентів JSF
2.9.1. Додаток
У додатку наведено приклад POST, реалізований без використання кнопки чи посилання. Форма має такий вигляд:
![]() |
Вміст списку combo2 [2] пов’язаний із елементом, вибраним у combo1 [1]. При зміні вибору в [1] виконується POST форми, під час якого вміст combo2 змінюється, щоб відобразити елемент, вибраний у [1], після чого форма повертається. Під час виконання цього POST перевірка не проводиться.
2.9.2. Проєкт NetBeans
Проект NetBeans для цього додатка має такий вигляд:
![]() |
Маємо єдину форму [index.xhtml] із відповідною шаблоном [Form.java].
2.9.3. Середовище додатка
Файл повідомлень [messages_fr.properties]:
app.titre=intro-07
app.titre2=JSF - Listeners
combo1.prompt=combo1
combo2.prompt=combo2
saisie1.prompt=Nombre entier de type int
submit=Valider
raz=Raz
data.required=Donnée requise
integer.required=Entrez un nombre entier
saisie.type=Type de la saisie
saisie.champ=Champ de saisie
saisie.erreur=Erreur de saisie
bean.valeur=Valeurs du modèle du formulaire
Таблиця стилів [styles.css]:
.info{
font-family: Arial,Helvetica,sans-serif;
font-size: 14px;
font-weight: bold
}
.col1{
background-color: #ccccff
}
.col2{
background-color: #ffcccc
}
.col3{
background-color: #ffcc66
}
.col4{
background-color: #ccffcc
}
.error{
color: #ff0000
}
.saisie{
background-color: #ffcccc;
border-color: #000000;
border-width: 5px;
color: #cc0033;
font-family: cursive;
font-size: 16px
}
.combo{
color: green;
}
.entete{
font-family: 'Times New Roman',Times,serif;
font-size: 14px;
font-weight: bold
}
2.9.4. Форма [index.xhtml]
Форма [index.xhtml] має такий вигляд:
<?xml version='1.0' encoding='UTF-8' ?>
<!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:f="http://java.sun.com/jsf/core">
<h:head>
<title>JSF</title>
<h:outputStylesheet library="css" name="styles.css"/>
...
</h:head>
<h:body style="background-image: url('${request.contextPath}/resources/images/standard.jpg');">
<h2><h:outputText value="#{msg['app.titre2']}"/></h2>
<h:form id="formulaire">
<h:messages globalOnly="true"/>
<h:panelGrid columns="4" border="1" columnClasses="col1,col2,col3,col4">
<!-- заголовки -->
<h:outputText value="#{msg['saisie.type']}" styleClass="entete"/>
<h:outputText value="#{msg['saisie.champ']}" styleClass="entete"/>
<h:outputText value="#{msg['saisie.erreur']}" styleClass="entete"/>
<h:outputText value="#{msg['bean.valeur']}" styleClass="entete"/>
<!-- рядок 1 -->
<h:outputText value="#{msg['combo1.prompt']}"/>
<h:selectOneMenu id="combo1" value="#{form.combo1}" immediate="true" onchange="submit();" valueChangeListener="#{form.combo1ChangeListener}" styleClass="combo">
<f:selectItems value="#{form.combo1Items}"/>
</h:selectOneMenu>
<h:panelGroup></h:panelGroup>
<h:outputText value="#{form.combo1}"/>
<!-- рядок 2 -->
<h:outputText value="#{msg['combo2.prompt']}"/>
<h:selectOneMenu id="combo2" value="#{form.combo2}" styleClass="combo">
<f:selectItems value="#{form.combo2Items}"/>
</h:selectOneMenu>
<h:panelGroup></h:panelGroup>
<h:outputText value="#{form.combo2}"/>
<!-- рядок 3 -->
<h:outputText value="#{msg['saisie1.prompt']}"/>
<h:inputText id="saisie1" value="#{form.saisie1}" required="true" requiredMessage="#{msg['data.required']}" styleClass="saisie" converterMessage="#{msg['integer.required']}"/>
<h:message for="saisie1" styleClass="error"/>
<h:outputText value="#{form.saisie1}"/>
</h:panelGrid>
<!-- кнопки управління -->
<h:panelGrid columns="2" border="0">
<h:commandButton value="#{msg['submit']}"/>
...
</h:panelGrid>
</h:form>
</h:body>
</html>
Нововведення полягає в коді списку combo1, рядки 24–26. З’являються нові атрибути:
- onchange: атрибут HTML — визначає функцію або код JavaScript, який має виконуватися, коли змінюється елемент, вибраний у combo1. Тут код JavaScript submit() відправляє форму на сервер,
- valueChangeListener: атрибут JSF — визначає назву методу, який має виконуватися на стороні сервера, коли змінюється елемент, вибраний у combo1. Загалом виконується два методи: один на стороні клієнта, інший — на стороні сервера,
- immediate=true: атрибут JSF — визначає момент, коли має бути виконаний обробник події на стороні сервера: після того, як форма буде відтворена відповідно до введених користувачем даних, але до перевірки правильності введених даних. Тут ми хочемо заповнити список combo2 відповідно до елемента, вибраного зі списку combo1, навіть якщо в іншій частині форми можуть бути помилкові дані. Ось приклад:
![]() |
- у [1] — перше значення,
- у [2] — переносимо вибраний елемент із combo1 з A до B.
Отриманий результат такий:
![]() |
Відбувся POST. Вміст combo2 та [2] було адаптовано доелемент, вибраний у combo1 та [1], хоча введення [3] було неправильним. Саме атрибут immediate=true спричинив те, що метод form.combo1ChangeListener було виконано до перевірок правильності. Без цього атрибута метод не був би виконаний, оскільки цикл обробки зупинився б на перевірках валідності через помилку в [3].
У [messages.properties] повідомлення, пов’язані з формою, такі:
app.titre=intro-07
app.titre2=JSF - Listeners
combo1.prompt=combo1
combo2.prompt=combo2
saisie1.prompt=Nombre entier de type int
submit=Valider
raz=Raz
data.required=Donnée requise
integer.required=Entrez un nombre entier
saisie.type=Type de la saisie
saisie.champ=Champ de saisie
saisie.erreur=Erreur de saisie
bean.valeur=Valeurs du modèle du formulaire
Термін дії [Form.java] встановлено як request:
package forms;
...
@ManagedBean
@RequestScoped
public class Form {
У рядку 6 область дії bean встановлюється як request.
2.9.5. Шаблон [Form.java]
Модель [Form.java] має такий вигляд:
package forms;
import java.util.logging.Logger;
import javax.enterprise.context.RequestScoped;
import javax.faces.bean.ManagedBean;
import javax.faces.context.FacesContext;
import javax.faces.event.ValueChangeEvent;
import javax.faces.model.SelectItem;
@ManagedBean
@RequestScoped
public class Form {
public Form() {
}
// поля форми
private String combo1="A";
private String combo2="A1";
private Integer saisie1=0;
// робочі поля
final private String[] combo1Labels={"A","B","C"};
private String combo1Label="A";
private static final Logger logger=Logger.getLogger("forms.Form");
// методи
public SelectItem[] getCombo1Items(){
// ініціалізація combo1
SelectItem[] combo1Items=new SelectItem[combo1Labels.length];
for(int i=0;i<combo1Labels.length;i++){
combo1Items[i]=new SelectItem(combo1Labels[i],combo1Labels[i]);
}
return combo1Items;
}
public SelectItem[] getCombo2Items(){
// ініціалізація combo2 залежно від combo1
SelectItem[] combo2Items=new SelectItem[5];
for(int i=1;i<=combo2Items.length;i++){
combo2Items[i-1]=new SelectItem(combo1Label+i,combo1Label+i);
}
return combo2Items;
}
// прислуховувачі
public void combo1ChangeListener(ValueChangeEvent event){
// відстеження
logger.info("combo1ChangeListener");
// отримуємо значення, відправлене з combo1
combo1Label=(String)event.getNewValue();
// повертаємо відповідь, оскільки хочемо обійти перевірки
FacesContext.getCurrentInstance().renderResponse();
}
public String raz(){
// продовження
logger.info("raz");
// очищення форми
combo1Label="A";
combo1="A";
combo2="A1";
saisie1=0;
return null;
}
// гетери — сеттери
...
}
Зв’яжемо форму [index.xhtml] з її шаблоном [Form.java]:
Список combo1 генерується за допомогою такого коду JSF:
<h:selectOneMenu id="combo1" value="#{form.combo1}" immediate="true" onchange="submit();" valueChangeListener="#{form.combo1ChangeListener}" styleClass="combo">
<f:selectItems value="#{form.combo1Items}"/>
</h:selectOneMenu>
Вона отримує свої елементи за допомогою методу getCombo1Items своєї шаблону (рядок 2). Цей метод визначено в рядках 28–35 коду Java. Він генерує список із трьох елементів {"A", "B", "C"}.
Список combo2 генерується за допомогою наступного коду JSF:
<h:selectOneMenu id="combo2" value="#{form.combo2}" styleClass="combo">
<f:selectItems value="#{form.combo2Items}"/>
</h:selectOneMenu>
Вона отримує свої елементи за допомогою методу getCombo2Items своєї моделі (рядок 2). Цей метод визначено в рядках 37–44 коду Java. Він генерує список із п’яти елементів {"X1", "X2", "X3", "X4", "X5"}, де X — це елемент combo1Label із рядка 16. Отже, під час початкового формування форми список combo2 містить елементи {"A1","A2","A3", "A4", "A5"}.
Коли користувач змінить елемент, вибраний у списку combo1,
- подія onchange="submit();" буде оброблена клієнтським браузером. Таким чином, форма буде відправлена на сервер,
- на стороні сервера JSF виявить, що компонент combo1 змінив значення. Буде виконано метод combo1ChangeListener у рядках 47–54. Метод типу ValueChangeListener отримує як параметр об’єкт типу javax.faces.event.ValueChangeEvent. Цей об’єкт дозволяє отримати старе та нове значення компонента, значення якого змінилося, за допомогою таких методів:

Тут компонентом є список combo1 типу UISelectOne. Його значення має тип String.
- рядок 51 шаблону Java: нове значення combo1 зберігається в combo1Label, яке використовується для генерації елементів списку combo2,
- рядок 53: повертається відповідь. Тут слід пам’ятати, що обробник combo1ChangeListener виконується з атрибутом immediate="true". Отже, він виконується після етапу, на якому дерево компонентів сторінки було оновлено з урахуванням відправлених значень, і перед процесом перевірки цих значень. Однак ми хочемо уникнути цього процесу перевірки, оскільки список combo2 має бути оновлений, навіть якщо в формі залишилися помилкові записи. Тому ми вимагаємо, щоб відповідь була надіслана негайно, без проходження етапу перевірки введених даних.
- Форма буде надіслана у тому вигляді, в якому її було заповнено. Однак елементи списків combo1 та combo2 не є занесеними значеннями. Вони будуть згенеровані заново шляхом виклику методів getCombo1Items та getCombo2Items. Останній метод використовуватиме нове значення combo1Label, встановлене методом combo1ChangeListener, і елементи списку combo2 зміняться.
2.9.6. Кнопка [Raz]
За допомогою кнопки [Raz] ми хочемо повернути форму до початкового стану, як показано нижче:
![]() |
![]() |
![]() |
У [1] — форма до натискання кнопки [Raz], у [2] — результат натискання кнопки POST.
Хоча функціонально це просто, реалізація цього випадку використання виявляється досить складною. Можна спробувати різні рішення, зокрема те, що використовувалося для кнопки [Annuler] у попередньому прикладі:
<h:commandButton value="#{msg['raz']}" immediate="true" action="#{form.raz}"/>
де метод form.raz виглядає так:
public String raz(){
// очищення форми
combo1Label="A";
combo1="A";
combo2="A1";
saisie1=0;
return null;
}
Результат, отриманий за допомогою кнопки [Raz] у попередньому прикладі, виглядає так:
![]() |
У стовпці [1] показано, що метод form.raz було виконано. Однак у стовпці [1] продовжують відображатися відправлені значення:
- для combo1 опублікованим значенням було «B». Отже, цей елемент виділено у списку,
- для combo2 занесеним значенням було «B5». Внаслідок виконання form.raz елементи {"B1", ..., «B5»} з combo2 були змінені на {«A1», ..., «A5»}. Елемент «B5» більше не існує, тому його неможливо вибрати. У такому випадку відображається перший елемент списку,
- для saisie1 значення, введене користувачем, становило 10.
Це нормальна поведінка при використанні атрибута immediate="true". Щоб отримати інший результат, потрібно відправити ті значення, які ми хочемо бачити в новій формі, навіть якщо користувач ввів інші значення. Це можна реалізувати за допомогою невеликого коду JavaScript на стороні клієнта. Форма виглядає наступним чином:
<script language="javascript">
function raz(){
document.forms['formulaire'].elements['formulaire:combo1'].value="A";
document.forms['formulaire'].elements['formulaire:combo2'].value="A1";
document.forms['formulaire'].elements['formulaire:saisie1'].value=0;
//document.forms['formulaire'].submit();
}
</script>
...
<h:commandButton value="#{msg['raz']}" onclick='raz()' immediate="true" action="#{form.raz}"/>
- у рядку 10 атрибут onclick='raz()' вказує на виконання функції JavaScript raz, коли користувач натискає на кнопку [Raz],
- рядок 3: елементу HTML з іменем «formulaire:combo1» присвоюється значення «A». Різні елементи рядка 3 такі:
- document — сторінка, що відображається браузером,
- document.forms: сукупність форм у документі,
- document.forms['formulaire']: форма з атрибутом name="formulaire",
- documents.forms['formulaire'].elements: сукупність елементів форми, що мають атрибут name="formulaire",
- document.forms['formulaire'].elements['formulaire:combo1']: елемент форми з атрибутом name="formulaire:combo1"
- document.forms['formulaire'].elements['formulaire:combo1'].value: значення, яке буде відправлено елементом форми з атрибутом name="formulaire:combo1".
Щоб дізнатися атрибути name різних елементів сторінки, що відображається браузером, можна переглянути її вихідний код (нижче наведено приклад із IE7):
![]() |
<form id="formulaire" name="formulaire" ...>
...
<select id="formulaire:combo1" name="formulaire:combo1" ...>
З огляду на це, можна зрозуміти, що в коді JavaScript функції raz:
- рядок 3 зумовлює, що значення, яке передається для компонента combo1, буде рядком A,
- рядок 4 призводить до того, що значення, яке передається для компонента combo2, буде рядком A1,
- рядок 5 призводить до того, що значення, яке буде передано для компонента saisie1, буде рядком 0.
Після цього відбудеться обробка POST з форми, пов’язаного з будь-якою кнопкою типу <h:commandButton> (рядок 10). Буде виконано метод form.raz, і форма буде повернута у тому вигляді, в якому вона була відправлена. У результаті отримаємо таке:
![]() |
Цей результат приховує багато речей. Значення «A», «A1», «0» компонентів combo1, combo2, saisie1 надсилаються на сервер. Припустимо, що попереднє значення combo1 дорівнювало «B». Отже, відбулася зміна значення компонента combo1, і метод form.combo1ChangeListener також має бути виконаний. У нас є два обробники подій з атрибутом immediate="true". Чи будуть вони обидва виконані? Якщо так, то в якому порядку? Чи лише один? Якщо так, то який саме?
Щоб дізнатися більше, ми створюємо журнали в додатку:
package forms;
import java.util.logging.Logger;
...
public class Form {
...
// поля форми
private String combo1="A";
private String combo2="A1";
private Integer saisie1=0;
// робочі поля
final private String[] combo1Labels={"A","B","C"};
private String combo1Label="A";
private static final Logger logger=Logger.getLogger("forms.Form");
// обробник
public void combo1ChangeListener(ValueChangeEvent event){
// відстеження
logger.info("combo1ChangeListener");
// отримуємо значення, відправлене з combo1
combo1Label=(String)event.getNewValue();
// повертаємо відповідь, оскільки хочемо обійти перевірки
FacesContext.getCurrentInstance().renderResponse();
}
public String raz(){
// продовження
logger.info("raz");
// очищення форми
combo1Label="A";
combo1="A";
combo2="A1";
saisie1=0;
return null;
}
...
}
- рядок 16: створюється генератор журналів. Параметр getLogger дозволяє розрізняти джерела записів у журналах. Тут генератор журналів називається forms.Form,
- рядок 21: реєструється виклик методу combo1ChangeListener,
- рядок 30: реєструється виклик методу raz.
Які записи у журналі генеруються натисканням кнопки [Raz] або зміною значення combo1? Розглянемо різні випадки:
- використовуємо кнопку [Raz], тоді як елемент, вибраний у combo1, — це «A». Отже, «A» є останнім значенням компонента combo1. Ми бачили, що кнопка [Raz] виконувала функцію JavaScript, яка відправляла значення «A» для компонента combo1. Отже, значення компонента combo1 не змінюється. Журнали показують, що виконується лише метод form.raz:
- використовується кнопка [Raz], хоча елемент, вибраний у combo1, не є «A». Отже, компонент combo1 змінює своє значення: його останнє значення не було «A», і кнопка [Raz] передає йому значення «A». У логах тоді видно, що виконуються два методи. У такому порядку: combo1ChangeListener, raz:
![]() |
- ми змінюємо значення combo1, не використовуючи кнопку [Raz]. Журнали показують, що виконується лише метод combo1ChangeListener:
![]() |
2.10. Приклад mv-jsf2-08: тег <h:dataTable>
2.10.1. Додаток
Додаток відображає список осіб із можливістю їх видалення:
![]() |
- у [1] — список осіб,
- у [2] — посилання, за якими їх можна видалити.
2.10.2. Проєкт NetBeans
Проект NetBeans для цього додатка виглядає так:
![]() |
У нас є єдина форма [index.xhtml] із відповідною шаблоном [Form.java].
2.10.3. Середовище додатка
Файл конфігурації [faces-config.xml]:
<?xml version='1.0' encoding='UTF-8'?>
<!-- =========== FULL CONFIGURATION FILE ================================== -->
<faces-config version="2.0"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_2_0.xsd">
<application>
<resource-bundle>
<base-name>
messages
</base-name>
<var>msg</var>
</resource-bundle>
<message-bundle>messages</message-bundle>
</application>
</faces-config>
Файл повідомлень [messages_fr.properties]:
app.titre=intro-08
app.titre2=JSF - DataTable
submit=Valider
personnes.headers.id=Id
personnes.headers.nom=Nom
personnes.headers.prenom=Pr\u00e9nom
Таблиця стилів [styles.css]:
.headers {
text-align: center;
font-style: italic;
color: Snow;
background: Teal;
}
.id {
height: 25px;
text-align: center;
background: MediumTurquoise;
}
.nom {
text-align: left;
background: PowderBlue;
}
.prenom {
width: 6em;
text-align: left;
color: Black;
background: MediumTurquoise;
}
2.10.4. Форма [index.xhtml] та її шаблон [Form.java]
Нагадаємо, що до сторінки [index.xhtml] прив’язано такий вигляд:
![]() |
Форма [index.xhtml] має такий вигляд:
<?xml version='1.0' encoding='UTF-8' ?>
<!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:f="http://java.sun.com/jsf/core">
<h:head>
<title>JSF</title>
<h:outputStylesheet library="css" name="styles.css"/>
</h:head>
<h:body style="background-image: url('${request.contextPath}/resources/images/standard.jpg');">
<h2><h:outputText value="#{msg['app.titre2']}"/></h2>
<h:form id="formulaire">
<h:dataTable value="#{form.personnes}" var="personne" headerClass="headers" columnClasses="id,nom,prenom">
........................
</h:dataTable>
</h:form>
</h:body>
</html>
У рядку 14 тег <h:dataTable> використовує поле #{form.personnes} як джерело даних. Воно має такий вигляд:
private List<Personne> personnes;
Клас [Personne] має такий вигляд:
package forms;
public class Personne {
// дані
private int id;
private String nom;
private String prénom;
// виробники
public Personne(){
}
public Personne(int id, String nom, String prénom){
this.id=id;
this.nom=nom;
this.prénom=prénom;
}
// toString
public String toString(){
return String.format("Personne[%d,%s,%s]", id,nom,prénom);
}
// геттери та сеттери
...
}
Повернемося до вмісту тегу <h:dataTable>:
<h:dataTable value="#{form.personnes}" var="personne" headerClass="headers" columnClasses="id,nom,prenom">
...
</h:dataTable>
- атрибут var="personne" визначає ім'я змінної, що представляє поточну особу всередині тегу <h:datatable>,
- атрибут headerClass="headers" визначає стиль заголовків стовпців таблиці,
- атрибут columnClasses="...." визначає стиль кожного стовпця таблиці.
Розглянемо один із стовпців таблиці та подивимося, як він побудований:
![]() |
Код XHTML стовпця Id має такий вигляд:
<h:dataTable value="#{form.personnes}" var="personne" headerClass="headers" columnClasses="id,nom,prenom">
<h:column>
<f:facet name="header">
<h:outputText value="#{msg['personnes.headers.id']}"/>
</f:facet>
<h:outputText value="#{personne.id}"/>
</h:column>
...
</h:dataTable>
lignes 3-5 : la balise <f:facet name="header"> définit le titre de la colonne,
ligne 4 : le titre de la colonne est pris dans le fichier des messages,
ligne 6 : personne fait référence à l'attribut var de la balise <h:dataTable ...> (ligne 1). On écrit donc l'id de la personne courante.
<h:dataTable value="#{form.personnes}" var="personne" headerClass="headers" columnClasses="id,nom,prenom">
<h:column>
<f:facet name="header">
<h:outputText value="#{msg['personnes.headers.id']}"/>
</f:facet>
<h:outputText value="#{personne.id}"/>
</h:column>
<h:column>
<f:facet name="header">
<h:outputText value="#{msg['personnes.headers.nom']}"/>
</f:facet>
<h:outputText value="#{personne.nom}"/>
</h:column>
<h:column>
<f:facet name="header">
<h:outputText value="#{msg['personnes.headers.prenom']}"/>
</f:facet>
<h:outputText value="#{personne.prénom}"/>
</h:column>
...
</h:dataTable>
- рядки 3–7: стовпець id таблиці,
- рядки 8–13: стовпець «прізвище» таблиці,
- рядки 14–19: стовпець «ім’я» таблиці.
Тепер розглянемо стовпець посилань [Retirer]:
![]() |
Цей стовпець генерується за допомогою такого коду:
<h:dataTable value="#{form.personnes}" var="personne" headerClass="headers" columnClasses="id,nom,prenom">
...
<h:column>
<h:commandLink value="Retirer" action="#{form.retirerPersonne}">
<f:setPropertyActionListener target="#{form.personneId}" value="#{personne.id}"/>
</h:commandLink>
</h:column>
</h:dataTable>
Посилання [Retirer] генерується рядками 4–6. При натисканні на посилання буде виконано метод [Form].retirerPersonne. Настав час розглянути клас [Form.java]:
package forms;
import java.util.ArrayList;
import java.util.List;
import javax.enterprise.context.RequestScoped;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.SessionScoped;
@ManagedBean
@SessionScoped
public class Form {
// модель
private List<Personne> personnes;
private int personneId;
// конструктор
public Form() {
// ініціалізація списку осіб
personnes = new ArrayList<Personne>();
personnes.add(new Personne(1, "dupont", "jacques"));
personnes.add(new Personne(2, "durand", "élise"));
personnes.add(new Personne(3, "martin", "jacqueline"));
}
public String retirerPersonne() {
// пошук вибраної особи
int i = 0;
for (Personne personne : personnes) {
// поточна особа = вибрана особа?
if (personne.getId() == personneId) {
// поточна особа видаляється зі списку
personnes.remove(i);
// завершено
break;
} else {
// наступна особа
i++;
}
}
// тестуємо на тій самій сторінці
return null;
}
// гетери та сеттери
...
}
- рядки 18–24: конструктор ініціалізує список осіб із рядка 14,
- рядок 10: оскільки цей список має зберігатися протягом усього циклу запитів, область дії біна — сесія.
Під час виконання методу [retirerPersonne] у рядку 26 поле в рядку 15 було ініціалізовано за ідентифікатором особи, на посилання якої було натиснуто [Retirer]:
<h:commandLink value="Retirer" action="#{form.retirerPersonne}">
<f:setPropertyActionListener target="#{form.personneId}" value="#{personne.id}"/>
</h:commandLink>
Тег <f:setPropertyActionListener> дозволяє передавати інформацію до моделі. Тут значення атрибута value копіюється в поле моделі, ідентифіковане атрибутом target. Таким чином, ідентифікатор поточної особи, яку потрібно видалити зі списку осіб, копіюється в поле [Form].personneId за допомогою геттера цього поля. Це відбувається перед виконанням методу, на який посилається атрибут action у рядку 1.
У рядках 26–43 метод [supprimerPersonne] видаляє особу, для якої id дорівнює personneId.
2.11. Приклад mv-jsf2-09: верстка додатка JSF
2.11.1. Додаток
Цей приклад демонструє, як оформити сторінку додатка JSF з двома вікнами:
![]() |
Додаток має два види:
- у [1] — сторінка 1,
- у [2] — сторінка 2.
Можна переходити між цими двома сторінками. Тут ми хочемо показати, що сторінки 1 і 2 мають спільне оформлення, як це видно на знімках екрана вище.
2.11.2. Проєкт NetBeans
Проект NetBeans для цього додатка виглядає так:
![]() |
Додаток містить лише сторінки XHTML. Відповідного шаблону Java немає.
2.11.3. Сторінка [layout.xhtml]
Сторінка [layout.xhtml] визначає формат сторінок додатка:
<?xml version='1.0' encoding='UTF-8' ?>
<!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:f="http://java.sun.com/jsf/core"
xmlns:ui="http://java.sun.com/jsf/facelets">
<h:head>
<title>JSF</title>
<h:outputStylesheet library="css" name="styles.css"/>
</h:head>
<h:body style="background-image: url('${request.contextPath}/resources/images/standard.jpg');">
<h:form id="formulaire">
<table style="width: 400px">
<tr>
<td colspan="2" bgcolor="#ccccff">
<ui:include src="entete.xhtml"/>
</td>
</tr>
<tr style="height: 200px">
<td bgcolor="#ffcccc">
<ui:include src="menu.xhtml"/>
</td>
<td>
<ui:insert name="contenu" >
<h2>Contenu</h2>
</ui:insert>
</td>
</tr>
<tr bgcolor="#ffcc66">
<td colspan="2">
<ui:include src="basdepage.xhtml"/>
</td>
</tr>
</table>
</h:form>
</h:body>
</html>
У рядку 7 з’являється новий простір імен ui. Цей простір імен містить теги, що дозволяють форматувати сторінки додатка. Теги цього простору використовуються в рядках 17, 22, 25, 32.
Сторінка [layout.xhtml] відображає інформацію у таблиці HTML (рядок 14). Цю сторінку можна відкрити у браузері:
![]() |
- у [1], запитується URL.
Поле [2] було згенеровано за допомогою наступного коду XHTML:
<h:body style="background-image: url('${request.contextPath}/resources/images/standard.jpg');">
<h:form id="formulaire">
<table style="width: 400px">
<tr>
<td colspan="2" bgcolor="#ccccff">
<ui:include src="entete.xhtml"/>
</td>
</tr>
...
</table>
</h:form>
</h:body>
Тег <ui:include> у рядку 6 дозволяє включити на сторінку зовнішній код XHTML. Файл [entete.xhtml] має такий вигляд:
<?xml version='1.0' encoding='UTF-8' ?>
<!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">
<body>
<h2>entête</h2>
</body>
</html>
У файл [layout.xhtml] буде вставлено весь код із рядків 3–8. Таким чином, теги <html> та <body> будуть вставлені в тег <td>. Це не спричиняє помилок. Отже, сторінки, вставлені за допомогою <ui:include>, є повними сторінками XHTML. З візуальної точки зору, вплив матиме лише рядок 6. Теги <html> та <body> присутні з синтаксичних міркувань.
Область [3] була згенерована за допомогою такого коду XHTML:
<h:form id="formulaire">
<table style="width: 400px">
<tr style="height: 200px">
<td bgcolor="#ffcccc">
<ui:include src="menu.xhtml"/>
</td>
...
</tr>
...
</table>
</h:form>
Тег <ui:include> у рядку 5 включає такий файл [menu.xhtml]:
<?xml version='1.0' encoding='UTF-8' ?>
<!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">
<body>
<h2>menu</h2>
</body>
</html>
Область [4] була згенерована за допомогою наступного коду XHTML:
<h:form id="formulaire">
<table style="width: 400px">
...
<tr bgcolor="#ffcc66">
<td colspan="2">
<ui:include src="basdepage.xhtml"/>
</td>
</tr>
</table>
</h:form>
Тег <ui:include> у рядку 6 включає наступний файл [basdepage.xhtml]:
<?xml version='1.0' encoding='UTF-8' ?>
<!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">
<body>
<h2>bas de page</h2>
</body>
</html>
Область [5] була згенерована за допомогою наступного коду XHTML:
<h:form id="formulaire">
...
<td>
<ui:insert name="contenu" >
<h2>Contenu</h2>
</ui:insert>
</td>
...
</table>
</h:form>
Тег <ui:insert> у рядку 5 визначає зону під назвою «вміст». Це зона, яка може містити змінний вміст. Ми розглянемо, як саме. Коли ми завантажили сторінку [layout.xhtml], для зони з назвою «contenu» не було визначено жодного вмісту. У цьому випадку використовується вміст тегу <ui:insert> у рядках 4–6. Отже, відображається рядок 5.
2.11.4. Сторінка [page1.xhtml]
Сторінка [layout.xhtml] не призначена для перегляду. Вона слугує шаблоном для сторінок [page1.xhtml] та [page2.xhtml]. Це називається шаблоном сторінок. Сторінка [page1.xhtml] виглядає так:
<?xml version='1.0' encoding='UTF-8' ?>
<!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:f="http://java.sun.com/jsf/core"
xmlns:ui="http://java.sun.com/jsf/facelets">
<ui:composition template="layout.xhtml">
<ui:define name="contenu">
<h2>page 1</h2>
<h:commandLink value="page 2" action="page2"/>
</ui:define>
</ui:composition>
</html>
- у рядку 6 використовується простір імен ui,
- у рядку 7 за допомогою тегу <ui:composition> вказується, що сторінка пов’язана з шаблоном [layout.xhtml],
- у рядку 8 завдяки цьому зв’язку кожен тег <ui:define> буде пов’язаний із тегом <ui:insert> використовуваного шаблону, у даному випадку [layout.xhtml]. Зв’язок здійснюється за допомогою атрибута name обох тегів. Вони мають бути однаковими.
Відображається сторінка [layout.xhtml], де вміст кожного тегу <ui:insert> замінюється вмістом тегу <ui:define> із запитуваної сторінки. У цьому випадку все відбувається так, ніби відображається така сторінка:
<?xml version='1.0' encoding='UTF-8' ?>
<!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:f="http://java.sun.com/jsf/core"
xmlns:ui="http://java.sun.com/jsf/facelets">
<h:head>
<title>JSF</title>
<h:outputStylesheet library="css" name="styles.css"/>
</h:head>
<h:body style="background-image: url('${request.contextPath}/resources/images/standard.jpg');">
<h:form id="formulaire">
<table style="width: 400px">
<tr>
<td colspan="2" bgcolor="#ccccff">
<ui:include src="entete.xhtml"/>
</td>
</tr>
<tr style="height: 200px">
<td bgcolor="#ffcccc">
<ui:include src="menu.xhtml"/>
</td>
<td>
<h2>page 1</h2>
<h:commandLink value="page 2" action="page2"/>
</td>
</tr>
<tr bgcolor="#ffcc66">
<td colspan="2">
<ui:include src="basdepage.xhtml"/>
</td>
</tr>
</table>
</h:form>
</h:body>
</html>
Рядки 25–26 сторінки [page1.xhtml] були вставлені замість тегу <ui:insert> зі сторінки [layout.xml].
Сторінка [page2.xhtml] аналогічна [page1.xhtml]:
<?xml version='1.0' encoding='UTF-8' ?>
<!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:f="http://java.sun.com/jsf/core"
xmlns:ui="http://java.sun.com/jsf/facelets">
<ui:composition template="layout.xhtml">
<ui:define name="contenu">
<h2>page 2</h2>
<h:commandLink value="page 1" action="page1"/>
</ui:define>
</ui:composition>
</html>
2.12. Conclusion
Щойно проведений аналіз JSF 2 далеко не вичерпний. Але його достатньо для розуміння прикладів, що наводяться далі. Для більш глибокого вивчення рекомендуємо ознайомитися з [ref2].
2.13. Тестування за допомогою Eclipse
Покажемо, як проводити тестування проектів Maven за допомогою SpringSource Tool Suite:
![]() |
- у [1] імпортується проект Maven [2], який вибирається за допомогою кнопки [3]. Тут ми беремо проект Maven [mv-jsf2-09] для Eclipse
- у [4] імпортований проект було правильно розпізнано як проект Maven [5],
![]() |
- у [6], імпортований проект у провіднику проектів,
- у [7], його запускають на сервері [8] Tomcat [9],
![]() |
- на [10] запущено Tomcat 7,
- у [11] головна сторінка проєкту [mv-jsf2-09] [11] відображається у вбудованому браузері Eclipse.

























































































































































