Controller
Создайте в директории WEB-INF/dyn файл MyNotificationBean.java с содержимым:
import java.io.Serializable; import javax.annotation.PostConstruct; import javax.enterprise.context.RequestScoped; import javax.enterprise.context.SessionScoped; import javax.faces.view.ViewScoped; import javax.inject.Inject; import javax.inject.Named; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import ru.bitel.mybgbilling.kernel.common.AbstractBean; import ru.bitel.mybgbilling.kernel.common.inject.BGInject; import ru.bitel.mybgbilling.kernel.common.inject.BGInjection; import ru.bitel.mybgbilling.kernel.contract.NotificationBean; import ru.bitel.bgbilling.common.BGException; import ru.bitel.bgbilling.kernel.contract.api.common.bean.ContractNotification; import ru.bitel.bgbilling.kernel.contract.api.common.service.ContractNotificationService; @Named @ViewScoped // 1. @BGInjection // 2. public class MyNotificationBean extends AbstractBean implements Serializable { private static final Logger logger = LoggerFactory.getLogger( MyNotificationBean.class ); @BGInject // 2. private ContractNotificationService contractNotificationService; private List<ContractNotification> contractNotificationList; @Override protected void init() // 3. throws BGException { logger.info( "init" ); populate(); } public void populate() // 4. { logger.info( "populate" ); contractNotificationList = contractNotificationService.contractNotificationList( getContractId() ); } public List<ContractNotification> getContractNotificationList() // 5. { return contractNotificationList; } public void markRead( int id ) // 6. throws BGException { contractNotificationService.contractNotificationMarkRead( getContractId(), id ); populate(); } public void deleteContractNotification( int id ) throws BGException { contractNotificationService.contractNotificationDelete( getContractId(), id ); populate(); } }
- Данный контроллер будет существовать, пока открыта страница, которая его использует, т.к. у него указана аннотация @ViewScoped (javax.faces.view.ViewScoped). Когда абонент перейдет на другой пункт меню и обратно или нажмет F5 - будет создан новый объект данного контроллера. Если указать вместо @ViewScoped аннотацию @SessionScoped (javax.enterprise.context.SessionScoped), тогда контроллер будет существовать в течении жизни HTTP-сессии. Если же указать @RequestScoped (javax.enterprise.context.RequestScoped) - то только на протяжении обработки HTTP-запроса. Создание контроллера происходит при первом его использовании, например, в файле xhtml.
- Аннотация @BGInject позволяет использовать веб-сервисы биллинга (но дополнительно требуется аннотация @BGInjection на классе).
- Обычно для инициализации контроллера создают метод init с аннотацией @PostConstruct, однако в данном случае используем внутренний класс AbstractBean, который уже задействовал этот механизм, поэтому необходимо переопределить его метод init.
- Метод populate заполняет контроллер данными, он вызывается при инициализации из метода init, но также может быть вызван и из xhtml.
- Метод getContractNotificationList используется для получения списка объектов для последующего отображения в xhtml #{myNotificationBean.contractNotificationList}.
- Метод markRead вызывает веб-сервис биллинга, а затем вызывает populate для обновления данных.
View
Создайте в директории WEB-INF/content папку, например, provider и в ней файл myNotifications.xhtml:
<?xml version="1.0" encoding="UTF-8"?> <ui:composition xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://java.sun.com/jsf/html" xmlns:jsf="http://xmlns.jcp.org/jsf" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:f="http://java.sun.com/jsf/core" xmlns:c="http://java.sun.com/jsp/jstl/core" xmlns:fn="http://java.sun.com/jsp/jstl/functions" xmlns:b="http://java.sun.com/jsf/composite/composite" xmlns:namespace="http://java.sun.com/jsf/composite/namespace" xmlns:bts="http://java.sun.com/jsf/composite/bts" xmlns:a="http://xmlns.jcp.org/jsf/passthrough" template="/WEB-INF/template/page-wrapper.xhtml"> <ui:define name="page-content-header"> <!-- 1. --> <h1>Заголовок</h1> </ui:define> <ui:define name="page-content-data"> <!-- 2. --> <div class="panel panel-default"> <div class="panel-body"> <em jsf:rendered="#{empty myNotificationBean.contractNotificationList}">Оповещений нет</em> <!-- 3. --> <ui:repeat value="#{myNotificationBean.contractNotificationList}" var="notification" varStatus="status"> <!-- 4. --> <div style="status .last?'margin-bottom: 5px ':''"> <form jsf:id="notificationReadForm" role="form" class="form-inline"> <!-- 5. --> <a onclick="if($('#collapse#{notification.id}').hasClass('in')) return false; else {$('#collapsea#{notification.id}').removeClass('fa-envelope').addClass('fa-envelope-o')} " data-toggle="collapse" href="#collapse#{notification.id}" aria-expanded="true" aria-controls="collapseOne" class="noupdate"> <i id="collapsea#{notification.id}" class="fa #{notification.read?'fa-envelope-o':'fa-envelope'} fa-fw"></i> <h:outputText value=" " /> <h:outputText value="#{notification.dateTime}"> <!-- 6. --> <f:convertDateTime type="both" dateStyle="medium" /> </h:outputText> #{notification.title} <f:ajax listener="#{myNotificationBean.markRead( notification.id )}" event="click" /> <!-- 6. --> </a> </form> <div class="collapse" id="collapse#{notification.id}"> <div class="well well-sm"> <h:outputText value="#{notification.message}" escape="false" /> <!-- 6. --> <br /> <br /> <form jsf:id="notificationDeleteForm" role="form" class="form-inline"> <!-- 5. --> <button class="btn btn-primary confirm" update-target="page-content-data">#{msg['action.delete']} <f:ajax listener="#{myNotificationBean.deleteContractNotification( notification.id )}" event="click" render=":page-content-data" /> <!-- 8. --> <!-- 9. --> </button> </form> </div> </div> </div> </ui:repeat> </div> </div> </ui:define> </ui:composition>
Внутри блока <ui:define name="page-content-header"> находится заголовок текущей страницы, например, "Новости" или "Уведомления".
- Внутри блока <ui:define name="page-content-data"> находится сама страница (её содержимое).
- Атрибут jsf:rendered указывает, нужно ли отображать данный элемент.
Тэг ui:repeat осуществляет обработку списка #{myNotificationBean.contractNotificationList}, создавая новую переменную #{notification}, которая доступна внутри этого тэга-цикла.
- Для того, чтобы работал вызов методов контроллера, а также тэг f:ajax, форма должна быть JSF-компонентом. Чтобы форма стала JSF-компонентом, достаточно указать атрибут jsf:id.
Обычно для отображения переменной достаточна запись вида #{notification.title}, однако, если необходимо форматирование, например, даты, то нужно использовать тэг h:outputText. Если содержимое переменной не нужно эскейпировать перед выводом (например, если внутри переменной-строки есть запись вида "<br/>" и ее нужно вывести как перевод строки, а не как строку "<br/>"), то также используется тэг h:outputText с атрибутом escape="false".
- Тэг f:ajax с атрибутом event="click" внутри ссылки указывает, что на событие клика нужно вызвать метод #{myNotificationBean.markRead( notification.id )}. Обновление страницы здесь не происходит (только js на то же события клика меняет класс элемента для смены иконки).
- Тэг f:ajax с атрибутом event="click" внутри кнопки указывает, что на событие нажатия кнопки нужно вызвать метод #{myNotificationBean.deleteContractNotification( notification.id ).
Тэг f:ajax с атрибутом render=":page-content-data" указывает, что после выполнения запроса нужно обновить область c id page-content-data (в данном случае это область внутри блока <ui:define name="page-content-data">).
Меню
Для того, чтобы страница отобразилась, на нее должна указывать специальная ссылка. Чтобы добавить ссылку в главное меню, нужно указать в WEB-INF/mybgbilling-menu.groovy:
menu( page: "provider/myNotifications", subPage: "", icon: "fa-envelope-o", title: "menu.notifications", show: !isCustomer() ),
Или можно указать ссылку прямо на странице:
<form jsf:id="linkForm" role="form" class="form-inline"> <a jsf:action="#{navigationBean.setPage('provider/myNotifications', 0)}" update-target="page-wrapper"> <i class="fa fa-rub fa-fw"></i> Ссылка <f:ajax render=":page-wrapper" /> </a> </form>
В виде кнопки:
<form jsf:id="linkForm" role="form" class="form-inline"> <button class="btn btn-primary confirm" update-target="page-wrapper">Ссылка <f:ajax listener="#{navigationBean.setPage( 'provider/myNotifications', 0 )}" event="click" render=":page-wrapper" /> </button> </form>