跳转到主要内容

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件.

当您初次登录到 developerWorks 时,将会为您创建一份概要信息。您在 developerWorks 概要信息中选择公开的信息将公开显示给其他人,但您可以随时修改这些信息的显示状态。您的姓名(除非选择隐藏)和昵称将和您在 developerWorks 发布的内容一同显示。

所有提交的信息确保安全。

  • 关闭 [x]

当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。

昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件.

所有提交的信息确保安全。

  • 关闭 [x]

JSF 2 简介: 复合组件最佳实践

实现可扩展的自定义组件

David Geary, 总裁, Clarity Training, Inc.
David Geary 是一名作家、演讲家和顾问,也是 Clarity Training, Inc. 的总裁,他指导开发人员使用 JSF 和 Google Web Toolkit (GWT) 实现 Web 应用程序。他是 JSTL 1.0 和 JSF 1.0/2.0 专家组的成员,与人合作编写了 Sun 的 Web Developer 认证考试的内容,并且为多个开源项目作出贡献,包括 Apache Struts 和 Apache Shale。David 的 Graphic Java Swing 一直是关于 Java 的畅销书籍,而 Core JSF(与 Cay Horstman 合著)是关于 JSF 的畅销书。他还是 GWT Solutions 一书的作者。David 经常在各大会议和用户组发表演讲。他从 2003 年开始就一直是 NFJS tour 的定期演讲人,并且在 Java University 教授课程,三次当选为 JavaOne 之星。

简介: 在本期 JSF 2 简介 中,您将学习 5 个实现 Java™Server Faces 复合组件的最佳实践。遵照这些准则,您将使页面创建者易于扩展您的自定义组件。

查看本系列更多内容

发布日期: 2011 年 5 月 17 日
级别: 中级 原创语言: 英文
访问情况 : 4402 次浏览
评论: 


关于本系列

JSF 2 简介 系列是继 David Geary 同名的 三篇文章简介 推出的,像一个功夫大师一样帮助您发展和提升您的 JSF 2 框架技能。本系列深入研究框架及其周围的生态系统,并从外部视角展示一些 Java EE 技术,比如 Contexts and Dependency Injection 如何与 JSF 集成。

JSF 是一个基于组件的框架,这意味着它可以提供您所需要的基础架构来实现您自己的组件。JSF 2 提供一种简单的方法来实现自定义的复合组件。

在前面的文章中,我已经向您展示了几个复合组件的实现(见“模板及复合组件”,“Ajax 组件” 和 “后来添加的 Ajax 复合组件”)。在本文中,我将通过展示 5 个使用 JSF 2 实现复合组件的最佳实践来结束该主题 — 以及 JSF 2 fu 系列:

  1. 将您的组件封装在一个 DIV 中。
  2. 合并 JavaScript 和 Ajax。
  3. 使用 JavaScript 闭包在一个页面中支持多个组件。
  4. 允许页面创建者定制您的组件。
  5. 国际化您的组件。

为了更好地理解这些最佳实践,我将讨论如何应用一个简单复合组件的实现。

可编辑输入复合组件

文本的示例组件是一个可编辑的输入复合组件。 所示的应用程序使用两个可编辑输入复合组件,一个用于输入名字,另一个输入姓氏:


图 1. 可编辑文本组件
从上到下显示名字编辑序列的 3 个屏幕截图

从上到下, 中的这 3 个屏幕截图显示了名字的编辑序列:

  • 最上面的截图显示应用程序的初始状态,edit... 按钮位于 First name:Last name: 标签的右边。
  • 中间截图显示了当用户单击了 First name: 后边的 edit... 按钮,并在文本输入区域输入 Roger 之后应用程序的状态。一个 done 按钮出现在文本输入区右边。
  • 最下面的屏幕截图显示了当用户单击了 done 按钮之后应用程序的状态。现在出现了 First name: Roger,在其右边有一个 edit... 按钮。

接下来,我们将讨论如何使用这个可编辑输入组件,并向您显示如何实现。之后,我将就组件的实现逐个讨论这 5 个最佳实践。

使用组件

使用可编辑输入组件和使用其他任何 JFS 组件一样:声明适当的名称空间,然后对复合组件使用 JFS 生成的标记。 说明了 中所示的页面的两个步骤以及标签:


清单 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>

为了完整起见, 显示了 引用的 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. 组件的文件
 inputEditable 组件的文件系统层次结构和文件夹的屏幕截图

显示的是 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>

该标记创建了 4 个组件,最初只有两个 — 文本和编辑按钮 — 是可见的。当用户单击 edit 按钮时,应用程序调用 com.clarity.startEditing() JavaScript 函数,其实现如 所示:


清单 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() 函数使用源自 Scriptaculous 框架的 fade()appear() 方法(见 参考资料)。它也使用了一对计时器来确保以正确的次序淡入淡出。注意 startEditing() 函数最终也给了文本输入焦点。

显示了 inputEditable.properties:


清单 5 inputEditable 组件的属性文件(inputEditable.properties)
				
editButtonText=edit...
doneButtonText=done

接下来,我将从 5 个最佳实践的角度来讨论可编辑输入的实现。


将组件封装在一个 DIV 中

JFS 在创建一个复合组件时,它创建了一个框架称之为命名容器 的组件,这包括复合组件中的所有组件。这个命名容器不能生成标记;相反,JFS 为复合组件中的每一个组件生成标记。结果是页面标记不能通过其组件 ID 将组件作为一个整体引用,因为默认情况下没有带有此 ID 的组件。

您可以为页面创建者提供引用复合组件的能力,在您执行该组件时将其封装在一个 DIV 中。比如,假设您想要页面标记来引用一个可编辑的输入组件作为一个 Ajax 调用的一部分。在下列标记中,我将添加一个 Ajax 按钮来处理名字的输入。单击按钮使 Ajax 对服务器进行调用,其中名字输入已被处理;Ajax 调用返回时,JFS 呈现输入内容:

<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 按钮正常工作,因为在 中我将组件封装在一个 DIV 中:

<div id="#{cc.clientId}">

   ...

</div>

用于 DIV 的标识符是复合组件自身的客户端标识符。因此,页面创建者可以将复合组件作为一个整体引用,和我之前讨论的 Ajax 按钮一样。


合并 JavaScript 和 Ajax

在本文的静态截图中,您看不到当用户单击 edit... 按钮时可编辑输入组件执行的淡出动画。淡出是由 Scriptaculous 框架最后完成的。在 中,我使用 <h:outputScript> 标记输出 Scriptaculous 所需的 JavaScript,在 中,我使用框架的 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(),调用将不起作用。

通过 JFS 使用第三方 JavaScript 框架(比如 Scriptaculous 或 jQuery)很容易。您可以在页面输出适当的 JavaScript,然后在您的 JavaScript 中使用该框架。

用户单击 done 按钮时,可编辑输入组件也可使用 Ajax 对服务器进行调用:

<h:commandButton id="doneButton" 
   value="#{cc.resourceBundleMap.doneButtonText}" style="display: none">
          
   <f:ajax render="text textMessage" execute="editableText" 
      onevent="ajaxExecuting"/>
            
</h:commandButton>

在上述标记中,当用户单击 done 按钮时,我使用 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); 
         };
      },

这是用于可编辑输入组件的 JavaScript, 中显示一个整体。init() 函数被每个可编辑输入组件所调用,正如您在 的底部所看到的。给定组件封装的 DIV 客户端标识符,我得到该特定 DIV 的一个引用。因为 JavaScript 是一个动态语言,允许您在运行时向对象添加属性和方法,我向 DIV 中添加了对回调函数中所需的所有元素的引用。

我也向组件的 edit 按钮添加了一个 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 个主要方法是:

  • 添加验证器、转换器和监听器
  • Facets
  • Ajax

验证器、转换器和监听器

在您公布这些内部组件的时候,可以允许页面创建者将验证器、转换器和监听器添加到您的复合组件中的组件中。例如, 显示了添加到一个可编辑输入组件的验证器:


图 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>    

Facets

在 中,错误消息显示在页面的底部,这是因为我正在使用 Development 项目阶段,JSF 自动在底部添加验证错误。您不能告知这个错误是和哪个可编辑输入有关系的,因此您最好将错误消息放在违法组件旁边,如 所示:


图 4 . 使用 facets

页面创建者可以通过向组件添加一个 facet 来实现:

<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>

此 facet 是组件所支持的:

<composite:interface>
   <composite:facet name="textMessage"/>
</composite:interface>
   
<div id="#{cc.clientId}">
   <h:panelGroup id="textMessage">
      <composite:renderFacet name="textMessage"/>
   </h:panelGroup>
   ...
</div>

Ajax

正如我在 “后来添加的 Ajax 复合组件” 中所介绍的,您可以使用 <composite:clientBehavior> 标记来让页面创建者添加 Ajax 功能到您的复合组件。 显示了一个对话框,在用户单击 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},其中 KEY 是 属性文件中的密钥。正如您在 和 中所看到的,至此我已经为可编辑输入组件按钮的文本进行了本地化。


结束语

JSF 1 使实现组件很困难,因此多数 JFS 开发人员放弃了使用它。有了 JFS 2 之后,自定义组件不再是自定义组件架构开发人员的专利。在本文中,我向您介绍了一些实现复合组件的最佳实践。只需要一点点改动,您就可以使您的组件对页面创建者来说是易于扩展的。


参考资料

学习

获得产品和技术

  • JSF:下载 JSF 2.0。

讨论

  • 加入 developerWorks 中文社区。查看开发人员推动的博客、论坛、组和维基,并与其他 developerWorks 用户交流。

关于作者

David Geary 是一名作家、演讲家和顾问,也是 Clarity Training, Inc. 的总裁,他指导开发人员使用 JSF 和 Google Web Toolkit (GWT) 实现 Web 应用程序。他是 JSTL 1.0 和 JSF 1.0/2.0 专家组的成员,与人合作编写了 Sun 的 Web Developer 认证考试的内容,并且为多个开源项目作出贡献,包括 Apache Struts 和 Apache Shale。David 的 Graphic Java Swing 一直是关于 Java 的畅销书籍,而 Core JSF(与 Cay Horstman 合著)是关于 JSF 的畅销书。他还是 GWT Solutions 一书的作者。David 经常在各大会议和用户组发表演讲。他从 2003 年开始就一直是 NFJS tour 的定期演讲人,并且在 Java University 教授课程,三次当选为 JavaOne 之星。

关于报告滥用的帮助

报告滥用

谢谢! 此内容已经标识给管理员注意。


关于报告滥用的帮助

报告滥用

报告滥用提交失败。 请稍后重试。


developerWorks:登录


需要一个 IBM ID?
忘记 IBM ID?


忘记密码?
更改您的密码

单击提交则表示您同意developerWorks 的条款和条件。 使用条款

 


当您初次登录到 developerWorks 时,将会为您创建一份概要信息。您在 developerWorks 概要信息中选择公开的信息将公开显示给其他人,但您可以随时修改这些信息的显示状态。您的姓名(除非选择隐藏)和昵称将和您在 developerWorks 发布的内容一同显示。

请选择您的昵称:

当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。

昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。

(长度在 3 至 31 个字符之间)


单击提交则表示您同意developerWorks 的条款和条件。 使用条款.

 


为本文评分

评论

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Java technology, Web development
ArticleID=659416
ArticleTitle=JSF 2 简介: 复合组件最佳实践
publish-date=05172011
author1-email=david.mark.geary_cnnew1@gmail.com
author1-email-cc=

标签

Help
使用 搜索 文本框在 My developerWorks 中查找包含该标签的所有内容。

使用 滑动条 调节标签的数量。

热门标签 显示了特定专区最受欢迎的标签(例如 Java technology,Linux,WebSphere)。

我的标签 显示了特定专区您标记的标签(例如 Java technology,Linux,WebSphere)。

使用搜索文本框在 My developerWorks 中查找包含该标签的所有内容。热门标签 显示了特定专区最受欢迎的标签(例如 Java technology,Linux,WebSphere)。我的标签 显示了特定专区您标记的标签(例如 Java technology,Linux,WebSphere)。