JSF是基于组件的框架,这意味着它提供了实现自己的组件所需的基础结构。 JSF 2提供了一种简单的方法来实现带有复合的自定义组件。
在之前的文章中,我已经向您展示了几种复合组件的实现(请参阅“ 模板和复合组件 ”,“ Ajax组件 ”和“ 事后Ajax复合组件 ”)。 在本文中,我将通过介绍使用JSF 2实现复合组件的五种最佳实践来总结该主题以及JSF 2 fu系列:
为了说明这些最佳实践,我将讨论它们如何应用于简单的复合组件的实现。
可编辑的输入复合组件
本文的示例组件是一个可编辑的输入复合组件。 图1所示的应用程序使用两个可编辑的输入,一个用于名字,一个用于姓氏:
图1.可编辑的文本组件
![三个屏幕截图从上到下显示了名字的编辑顺序](https://i-blog.csdnimg.cn/blog_migrate/45ee88b0cc639dddd06aa169d9e3c107.png)
从上到下, 图1中的三个屏幕截图显示了名字的编辑顺序:
- 顶部的屏幕快照显示了应用程序的初始外观,并在“ 名:”和“名:”标签的右侧有“ 编辑...”按钮。
- 中间的屏幕截图显示了用户单击“ 名字:”旁边的“ 编辑...”按钮后,应用程序的外观,并将
Roger
输入到文本输入区域。 完成按钮出现在文本输入区域的右侧。 - 底部的屏幕截图显示了用户单击“ 完成”按钮后应用程序的外观。 现在, 名字:Roger显示,并在其右侧带有“ 编辑...”按钮。
接下来,我将讨论如何使用可编辑输入组件,然后向您展示其实现方式。 之后,我将就组件的实现讨论五个最佳实践中的每一个。
使用组件
您可以像使用任何JSF复合组件一样使用可编辑输入组件:声明适当的名称空间,并使用JSF为复合组件生成的标签。 清单1用图1所示页面的标记说明了这两个步骤:
清单1.使用<util:inputEditable>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:util="http://java.sun.com/jsf/composite/util">
<h:head>
<title>Implementing custom components</title>
</h:head>
<h:body>
<h:form>
<h:panelGrid columns="2">
First name:
<util:inputEditable id="firstName"
value="#{user.firstName}"/>
Last name:
<util:inputEditable id="lastName"
value="#{user.lastName}"/>
</h:panelGrid>
</h:form>
</h:body>
</html>
为了完整起见,清单2显示了清单1中引用的user
bean的实现:
清单2. User
Bean
package com.corejsf;
import java.io.Serializable;
import javax.inject.Named;
import javax.enterprise.context.SessionScoped;
@Named("user")
@SessionScoped
public class UserBean implements Serializable {
private String firstName;
private String lastName;
public String getFirstName() { return firstName; }
public void setFirstName(String newValue) { firstName = newValue; }
public String getLastName() { return lastName; }
public void setLastName(String newValue) { lastName = newValue; }
}
既然您已经了解了如何使用可编辑输入组件,那么我将向您展示如何实现它。
组件的实现
可编辑输入组件是在resources / util目录中的inputEditable.js,inputEditable.properties和inputEditable.xhtml文件中实现的,如图2的文件系统层次结构所示:
图2.组件的文件
清单3显示了inputEditable.xhtml:
清单3. inputEditable
组件的标记(inputEditable.xhtml)
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:composite="http://java.sun.com/jsf/composite">
<composite:interface>
<composite:attribute name="text"/>
<composite:editableValueHolder name="text" targets="editableText" />
<composite:actionSource name="editButton" targets="editButton" />
<composite:actionSource name="doneButton" targets="doneButton" />
<composite:clientBehavior name="edit" event="action" targets="editButton"/>
<composite:clientBehavior name="done" event="action" targets="doneButton"/>
<composite:facet name="textMessage"/>
</composite:interface>
<composite:implementation>
<h:outputScript library="javascript" name="prototype.js" target="head"/>
<h:outputScript library="javascript" name="scriptaculous.js" target="head"/>
<h:outputScript library="javascript" name="effects.js" target="head"/>
<h:outputScript library="util" name="inputEditable.js" target="head"/>
<div id="#{cc.clientId}">
<h:outputText id="text" value="#{cc.attrs.text}"/>
<h:commandButton id="editButton" type="button"
value="#{cc.resourceBundleMap.editButtonText}"
onclick="this.startEditing()"/>
<h:inputText id="editableText" value="#{cc.attrs.text}" style="display: none"/>
<h:commandButton id="doneButton"
value="#{cc.resourceBundleMap.doneButtonText}" style="display: none">
<f:ajax render="text textMessage" execute="editableText"
onevent="ajaxExecuting"/>
</h:commandButton>
<h:panelGroup id="textMessage">
<composite:renderFacet name="textMessage"/>
</h:panelGroup>
</div>
<script> com.clarity.init('#{cc.clientId}'); </script>
</composite:implementation>
</html>
标记创建四个组件,最初只可见其中两个(文本和编辑按钮)。 当用户单击edit按钮时,应用程序将调用com.clarity.startEditing()
JavaScript函数,该函数在清单4中实现:
清单4. inputEditable
组件JavaScript(inputEditable.js)
package com.corejsf;
var com = {};
if (!com.clarity) {
com.clarity = {
init: function (ccid) {
var mydiv = document.getElementById(ccid);
mydiv.editButton = $(mydiv.id + ':editButton');
mydiv.text = $(mydiv.id + ':text');
mydiv.editableText = $(mydiv.id + ':editableText');
mydiv.doneButton = $(mydiv.id + ':doneButton');
mydiv.doneButton.offsetLeft = mydiv.editButton.offsetLeft;
mydiv.editButton.startEditing = function() {
mydiv.text.fade( { duration: 0.25 } );
mydiv.editButton.fade( { duration: 0.25 } );
window.setTimeout( function() {
mydiv.editableText.appear( { duration: 0.25 } );
mydiv.doneButton.appear( { duration: 0.25 } );
window.setTimeout( function() {
mydiv.editableText.focus();
}, 300);
}, 300);
};
},
toggleDisplay: function(element) {
element.style.display = element.style.display == "none" ? "" : "none";
},
ajaxExecuting: function(data) {
var mydiv = $(data.source.parentNode);
if (data.status == 'complete') {
toggleDisplay(mydiv.editableText);
toggleDisplay(mydiv.doneButton);
toggleDisplay(mydiv.text);
toggleDisplay(mydiv.editButton);
}
}
}
}
该startEditing()
函数使用fade()
和appear()
从Scriptaculous的框架方法(参见相关主题 )。 它还使用几个计时器来确保淡入淡出和出现的顺序正确。 请注意, startEditing()
函数最终还将焦点集中在文本输入上。
清单5显示了inputEditable.properties:
清单5. inputEditable
组件的属性文件(inputEditable.properties)
editButtonText=edit...
doneButtonText=done
接下来,我将从五个最佳实践的角度讨论可编辑输入的实现。
将组件包装在DIV中
当JSF创建复合组件时,它会创建框架所指的命名容器 ,其中包含复合组件内的所有组件。 但是,命名容器不会生成标记。 相反,JSF为组合内部的每个组件生成标记。 结果,页面标记无法通过其组件ID整体引用该组件,因为默认情况下,没有使用该ID的组件。
通过在实现时将组件包装在DIV
,可以使页面作者能够引用复合组件。 例如,假设您希望页面标记在Ajax调用中引用可编辑的输入组件。 在下面的标记中,我添加了一个处理名字输入的Ajax按钮。 单击该按钮可对服务器进行Ajax调用,并在服务器上处理名字的输入。 当Ajax调用返回时,JSF呈现输入:
<h:form>
<h:panelGrid columns="2">
First name:
<util:inputEditable id="firstName"
value="#{user.firstName}"/>
Last name:
<util:inputEditable id="lastName"
value="#{user.lastName}"/>
<h:commandButton value="Update first name">
<f:ajax execute="firstName" render="firstName">
</h:commandButton>
</h:panelGrid>
</h:form>
前面的标记中的Ajax按钮有效,因为在清单3中,我将组件包装在DIV
:
<div id="#{cc.clientId}">
...
</div>
用于DIV
的标识符是组合本身的客户标识符。 因此,页面作者可以像我刚才讨论的Ajax按钮一样,整体上引用复合组件。
整合JavaScript和Ajax
在本文的静态屏幕快照中看不到的一件事是用户单击“ edit ...”按钮时可编辑输入组件执行的淡入动画。 渐隐最终是由Scriptaculous框架完成的。 在清单3中 ,我使用<h:outputScript>
标签输出Scriptaculous所需JavaScript,在清单4中,我使用框架的fade()
和appear()
方法来获得所需的动画:
mydiv.editButton.startEditing = function() {
mydiv.text.fade( { duration: 0.25 } );
mydiv.editButton.fade( { duration: 0.25 } );
window.setTimeout( function() {
mydiv.editableText.appear( { duration: 0.25 } );
mydiv.doneButton.appear( { duration: 0.25 } );
window.setTimeout( function() {
mydiv.editableText.focus();
}, 300);
}, 300);
};
前面JavaScript中的嵌套计时器可确保动画中的所有内容均按提示进行。 例如,我推迟将焦点放在输入文本上,直到确定输入已经出现在屏幕上为止。 否则,如果我在输入出现之前调用focus()
,则该呼叫将不会停留。
将第三方JavaScript框架(例如Scriptaculous或JQuery)与JSF一起使用很简单。 您在页面中输出适当JavaScript,然后在JavaScript代码中使用该框架。
当用户单击完成按钮时,可编辑输入组件还使用Ajax来调用服务器:
<h:commandButton id="doneButton"
value="#{cc.resourceBundleMap.doneButtonText}" style="display: none">
<f:ajax render="text textMessage" execute="editableText"
onevent="ajaxExecuting"/>
</h:commandButton>
在前面的标记中,当用户单击完成按钮时,我使用JSF的<f:ajax>
标记进行Ajax调用。 该Ajax调用执行服务器上的文本输入,并在Ajax调用返回时更新文本和文本消息。
使用JavaScript闭包
在实现复合组件时,必须在一个页面中考虑多个组件。 当组件的所有实例共享相同JavaScript时,您必须小心操作仅与用户当前交互的组件。
您可以通过多种方式在页面中支持多个组件。 Oracle工程师Jim Driscoll在有关类似可编辑输入组件的博客条目中讨论的一种方法是维护组件ID的命名空间(请参阅参考资料 )。 另一种方法是使用JavaScript闭包:
com.clarity = {
init: function (ccid) {
var mydiv = document.getElementById(ccid);
mydiv.editButton = $(mydiv.id + ':editButton');
mydiv.text = $(mydiv.id + ':text');
mydiv.editableText = $(mydiv.id + ':editableText');
mydiv.doneButton = $(mydiv.id + ':doneButton');
mydiv.doneButton.offsetLeft = mydiv.editButton.offsetLeft;
mydiv.editButton.startEditing = function() {
mydiv.text.fade( { duration: 0.25 } );
mydiv.editButton.fade( { duration: 0.25 } );
window.setTimeout( function() {
mydiv.editableText.appear( { duration: 0.25 } );
mydiv.doneButton.appear( { duration: 0.25 } );
window.setTimeout( function() {
mydiv.editableText.focus();
}, 300);
}, 300);
};
},
这是清单4中完整显示的可编辑输入组件JavaScript。 如清单3底部所示 ,将为每个可编辑的输入组件调用init(
)函数。 给定组件所在DIV
的客户端标识符,我将获得对该特定DIV
的引用。 而且由于JavaScript是一种动态语言,可让您在运行时向对象添加属性和方法,因此我将稍后在回调函数中对所有所需元素的引用添加到DIV
本身。
我还将startEditing()
方法添加到组件的“编辑”按钮。 当用户单击edit ...时 ,我将调用该方法:
<h:commandButton id="editButton" type="button"
value="#{cc.resourceBundleMap.editButtonText}"
onclick="this.startEditing()"/>
调用startEditing()
函数时, mydiv
变量将保留调用init()
方法时具有的原始值。 这是关于JavaScript闭包(在某种程度上是Java内部类)的很棒的事情。 在init()
和startEditing()
之间经过了多长时间都没有关系,并且在两次init()
被调用过多次也没关系—调用startEditing()
时,其mydiv
值为它是针对特定组件调用init()
方法时拥有的副本。
因为JavaScript闭包保留了周围函数变量的值,所以可以确保每个startEditing()
函数都为其组件访问适当的DIV
。
让页面作者自定义
在组件定义中通常只有一两行XML,您可以让页面作者自定义您的组件。 定制复合组件的三种主要方法是:
- 添加验证器,转换器和侦听器
- 刻面
- 阿贾克斯
验证器,转换器和侦听器
只要公开那些内部组件,就可以让页面作者将验证器,转换器和侦听器附加到复合组件内部的组件。 例如,图3显示了添加到可编辑输入组件的验证:
图3.验证名字字段
图3显示一条错误消息,指出该字段至少需要10个字符。 页面作者已将验证器添加到名字可编辑输入组件的文本中,如下所示:
<util:inputEditable id="firstname" text="#{user.firstName}">
<f:validateLength minimum="10" for="text"/>
</util:inputEditable>
注意<f:validateLength>
标记的for
属性。 这告诉JSF验证器是针对可编辑输入组件中的文本的。 JSF知道该文本,因为我在组件的实现中公开了它:
<composite:interface>
...
<composite:editableValueHolder name="text" targets="editableText" />
...
</composite:interface>
<composite:implementation>
...
<h:inputText id="editableText" value="#{cc.attrs.text}" style="display: none"/>
...
</composite:implementation>
刻面
在图3中 ,错误消息显示在页面底部。 那是因为我正在使用Development项目阶段,而JSF会在底部自动添加验证错误。 您无法确定错误与哪个可编辑输入相关联,因此最好将错误消息放在有问题的组件旁边,如图4所示:
图4.使用构面
页面作者可以通过向组件添加构面来实现此目的:
<util:inputEditable id="firstname" text="#{user.firstName}">
<f:validateLength minimum="10" for="text"/>
<f:facet name="textMessage">
<h:message for="editableText" style="color: red"/>
</f:facet>
</util:inputEditable>
该方面受组件支持:
<composite:interface>
<composite:facet name="textMessage"/>
</composite:interface>
<div id="#{cc.clientId}">
<h:panelGroup id="textMessage">
<composite:renderFacet name="textMessage"/>
</h:panelGroup>
...
</div>
阿贾克斯
正如我在“ 事实Ajax复合组件 ”中所讨论的那样,您可以使用<composite:clientBehavior>
标记让页面作者将Ajax功能添加到您的复合组件中。 图5显示了一个对话框,该对话框监视当用户单击edit ...按钮时发生的Ajax调用:
图5.监视Ajax请求
页面作者只需将<f:ajax>
标记添加到组件,并指定一个Ajax函数,其onevent
属性是JavaScript函数(显示对话框的函数),以随着Ajax调用的进行进行调用:
<util:inputEditable id="firstname" text="#{user.firstName}">
<f:validateLength minimum="10" for="text"/>
<f:facet name="textMessage">
<h:message for="editableText" style="color: red"/>
</f:facet>
<f:ajax event="edit" onevent="monitorAjax"/>
</util:inputEditable>
页面作者可以向组件添加<f:ajax>
标记,因为我已经在组件的实现中公开了该客户端行为:
<composite:interface>
<composite:clientBehavior name="edit" event="action" targets="editButton"/>
</composite:interface>
国际化
可编辑的输入组件在其两个按钮上显示一些文本( edit ...和done )。 为了使该组件可用于多种语言环境,必须对该文本进行国际化和本地化。
要本地化组件的文本,只需在与组件相同的目录中添加属性文件,然后通过以下表达式访问属性文件中的键: #{cc.resourceBundleMap. KEY }
#{cc.resourceBundleMap. KEY }
,其中KEY
是属性文件中的键。 从清单3和清单5中可以看到,这就是我本地化可编辑输入组件的按钮的文本的方式。
结论
JSF 1使实现组件变得困难,因此大多数JSF开发人员选择不这样做。 使用JSF 2,自定义组件不再是自定义组件框架开发人员的专有领域。 在本文中,我向您展示了一些实现复合组件的最佳实践。 只需做一些工作,就可以使页面作者轻松扩展您的组件。
翻译自: https://www.ibm.com/developerworks/java/library/j-jsf2fu0111/index.html