如果计费部门要开发一个用于创建发票的新对话框,则可能如下所示:
如您所见,上面的屏幕在上部引用了一个客户。 单击短名称文本字段后面的“ ..”按钮将打开以下对话框,允许用户选择客户:
按“选择”后,客户数据将显示在发票表格中。
也可以通过简单地输入客户编号或在发票屏幕上的文本字段中输入简称来选择客户。 如果输入唯一的短名称,则根本不会出现选择对话框。 而是直接显示客户数据。 只有不明确的简称会导致打开客户选择屏幕。
客户功能将由属于客户管理团队的开发人员提供。 一种典型的方法是由客户管理开发团队提供一些服务,而计费部门的开发人员创建用户界面并调用这些服务。
但是,这种方法涉及到这两个不同部门之间的耦合要强于实际需要。 发票只需要一个唯一的ID即可引用客户数据。 创建发票对话框的开发人员实际上并不想知道如何查询客户数据或在后台使用哪些服务来获取该信息。
客户管理开发人员应提供UI的完整部分,以显示客户ID并处理客户的选择:
使用JSF 2,使用复合组件很容易实现。 客户管理部门和计费部门之间的逻辑接口包括三个部分:
- 复合组件(XHTML)
- 复合组件的支持bean
- 侦听器界面,用于处理选择结果
提供者(客户管理部门)
复合组件:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN""http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html xmlns="http://www.w3.org/1999/xhtml"xmlns:ui="http://java.sun.com/jsf/facelets"xmlns:h="http://java.sun.com/jsf/html"xmlns:f="http://java.sun.com/jsf/core"xmlns:composite="http://java.sun.com/jsf/composite"xmlns:ice="http://www.icesoft.com/icefaces/component"xmlns:ace="http://www.icefaces.org/icefaces/components"xmlns:icecore="http://www.icefaces.org/icefaces/core"><ui:composition><composite:interface name="customerSelectionPanel" displayName="Customer Selection Panel" shortDescription="Select a customer using it's number or short name"><composite:attribute name="model" type="org.fuin.examples.soui.view.CustomerSelectionBean" required="true" /> </composite:interface><composite:implementation><ui:param name="model" value="#{cc.attrs.model}"/><ice:form id="customerSelectionForm"><icecore:singleSubmit submitOnBlur="true" /><h:panelGroup id="table" layout="block"><table><tr><td><h:outputLabel for="customerNumber"value="#{messages.customerNumber}" /></td><td><h:inputText id="customerNumber"value="#{model.id}" required="false" /></td><td> </td><td><h:outputLabel for="customerShortName"value="#{messages.customerShortName}" /></td><td><h:inputText id="customerShortName"value="#{model.shortName}" required="false" /></td><td><h:commandButton action="#{model.select}"value="#{messages.select}" /></td></tr><tr><td><h:outputLabel for="customerName"value="#{messages.customerName}" /></td><td colspan="5"><h:inputText id="customerName"value="#{model.name}" readonly="true" /></td></tr></table></h:panelGroup></ice:form></composite:implementation></ui:composition></html>
复合组件的后备bean:
package org.fuin.examples.soui.view;import java.io.Serializable;import javax.enterprise.context.Dependent;
import javax.inject.Inject;
import javax.inject.Named;import org.apache.commons.lang.ObjectUtils;
import org.fuin.examples.soui.model.Customer;
import org.fuin.examples.soui.services.CustomerService;
import org.fuin.examples.soui.services.CustomerShortNameNotUniqueException;
import org.fuin.examples.soui.services.UnknownCustomerException;@Named
@Dependent
public class CustomerSelectionBean implements Serializable {private static final long serialVersionUID = 1L;private Long id;private String shortName;private String name;private CustomerSelectionListener listener;@Injectprivate CustomerService service;public CustomerSelectionBean() {super();listener = new DefaultCustomerSelectionListener();}public Long getId() {return id;}public void setId(final Long id) {if (ObjectUtils.equals(this.id, id)) {return;}if (id == null) {clear();} else {clear();this.id = id;try {final Customer customer = service.findById(this.id);changed(customer);} catch (final UnknownCustomerException ex) {FacesUtils.addErrorMessage(ex.getMessage());}}}public String getShortName() {return shortName;}public void setShortName(final String shortNameX) {final String shortName = (shortNameX == "") ? null : shortNameX;if (ObjectUtils.equals(this.shortName, shortName)) {return;}if (shortName == null) {clear();} else {if (this.id != null) {clear();}this.shortName = shortName;try {final Customer customer = service.findByShortName(this.shortName);changed(customer);} catch (final CustomerShortNameNotUniqueException ex) {select();} catch (final UnknownCustomerException ex) {FacesUtils.addErrorMessage(ex.getMessage());}}}public String getName() {return name;}public CustomerSelectionListener getConnector() {return listener;}public void select() {// TODO Implement...}public void clear() {changed(null);}private void changed(final Customer customer) {if (customer == null) {this.id = null;this.shortName = null;this.name = null;listener.customerChanged(null, null);} else {this.id = customer.getId();this.shortName = customer.getShortName();this.name = customer.getName();listener.customerChanged(this.id, this.name);}}public void setListener(final CustomerSelectionListener listener) {if (listener == null) {this.listener = new DefaultCustomerSelectionListener();} else {this.listener = listener;}}public void setCustomerId(final Long id) throws UnknownCustomerException {clear();if (id != null) {clear();this.id = id;changed(service.findById(this.id));}}private static final class DefaultCustomerSelectionListener implementsCustomerSelectionListener {@Overridepublic final void customerChanged(final Long id, final String name) {// Do nothing...}}}
用于处理结果的侦听器接口:
package org.fuin.examples.soui.view;/*** Gets informed if customer selection changed.*/
public interface CustomerSelectionListener {/*** Customer selection changed.** @param id New unique customer identifier - May be NULL.* @param name New customer name - May be NULL.*/public void customerChanged(Long id, String name);}
用户(计费部门)
发票Bean只是通过注入来使用客户选择Bean,并使用侦听器接口连接到它:
package org.fuin.examples.soui.view;import java.io.Serializable;import javax.annotation.PostConstruct;
import javax.enterprise.context.SessionScoped;
import javax.enterprise.inject.New;
import javax.inject.Inject;
import javax.inject.Named;@Named("invoiceBean")
@SessionScoped
public class InvoiceBean implements Serializable {private static final long serialVersionUID = 1L;@Inject @Newprivate CustomerSelectionBean customerSelectionBean;private Long customerId;private String customerName;@PostConstructpublic void init() {customerSelectionBean.setListener(new CustomerSelectionListener() {@Overridepublic final void customerChanged(final Long id, final String name) {customerId = id;customerName = name;}});}public CustomerSelectionBean getCustomerSelectionBean() {return customerSelectionBean;}public String getCustomerName() {return customerName;}}
最后,在发票XHTML中,使用了复合组件并将其链接到注入的支持bean:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN""http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html xmlns="http://www.w3.org/1999/xhtml"xmlns:ui="http://java.sun.com/jsf/facelets"xmlns:h="http://java.sun.com/jsf/html"xmlns:f="http://java.sun.com/jsf/core"xmlns:fuin="http://fuin.org/examples/soui/facelets"xmlns:customer="http://java.sun.com/jsf/composite/customer"><ui:composition template="/WEB-INF/templates/template.xhtml"><ui:param name="title" value="#{messages.invoiceTitle}" /><ui:define name="header"></ui:define><ui:define name="content"><customer:selection-panel model="#{invoiceBean.customerSelectionBean}" /></ui:define><ui:define name="footer"></ui:define></ui:composition></html>
摘要
总之,用户界面中引用其他部门数据的部分应由提供数据的部门负责。 然后,可以很容易地对提供的代码进行任何更改,而无需对使用代码进行任何更改。 此方法的另一个重要好处是可以统一应用程序的用户界面。 显示相同数据的控件和面板始终看起来相同。 每个部门还可以为其提供的用户界面组件创建一个存储库,从而使设计新对话框的过程像将正确的组件放在一起一样容易。
参考: A Java Developer's Life博客上的JCG合作伙伴 Michael Schnell提供的面向服务的UI 。
翻译自: https://www.javacodegeeks.com/2012/09/service-oriented-ui-with-jsf.html