JSF自定义组件

实现自定义组件

除了提供一组标准的组件、一个在HTML中呈现这些组件的呈现工具,以及一组相应的JSP标签之外,JavaServer Faces也让您可以创建自定义组件。下面的应用程序包含了一个JSF自定义组件的两个实例。该自定义组件用于显示两幅图像。当您单击那个组件时,它会切 换当前显示的图像。

点击放大

图5a. 两个自定义组件显示了它们的原始图像。

点击放大

图5b. 在用户单击左边组件后应用程序发生的变化

点击放大

图5c. 在用户单击右边组件后应用程序发生的变化

图5a展示了应用程序启动后出现的画面。两个自定义组件显示了它们的原始图像。图5b展示了在用户单击左边组件后出现的画面。图5c展示在用户随后单击右边组件后出现的画面。如果您再次单击组件,组件将显示它们的原始图像。

这些自定义组件可能看起来微不足道,而且毫无用处;尽管前一种说法是正确的(故意让这些组件微不足道,从而可以说明JSF组件实现),但后 一种说法未必正确--这个组件可用作包含可点击图像的任何组件的一部分。例如,这个自定义组件可用在树形控制中,以展开和收缩树中显示的节点。

清单9列出了图5a展示的JSP页面。

清单9. /index.jsp

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
   <head>
       <title>Creating Custom Components with JavaServer Faces</title>
   </head>

   <body>
       <%@ taglib uri="http://java.sun.com/j2ee/html_basic/" prefix="faces" %>
      <%@ taglib uri="/WEB-INF/tlds/example.tld" prefix="sabreware" %>

       <faces:usefaces>
         <sabreware:toggleGraphic id='bananaKiwi'
                     imageOne='/graphics/banana.jpg'
                     imageTwo='/graphics/kiwi.jpg'/>

         <sabreware:toggleGraphic id='pineappleStrawberry'
                     imageOne='/graphics/pineapple.jpg'
                     imageTwo='/graphics/strawberry.jpg'/>
       </faces:usefaces>
   </body>
</html>

前述的JSP页面是简单的--它使用一个自定义标签来创建自定义组件的两个实例。出于完整性考虑,清单10中列出了与<sabreware:toggleGraphic>标签有关的标签库描述符(TLD)。

清单10. /WEB-INF/tlds/example.tld

<?xml version="1.0" encoding="ISO-8859-1" ?>
<!DOCTYPE taglib
   PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.2//EN"
   "http://java.sun.com/dtd/web-jsptaglibrary_1_2.dtd">

<taglib>
   <tlib-version>1.0</tlib-version>
   <jsp-version>1.2</jsp-version>
   <short-name>JSF Example</short-name>
   <display-name>A Simple JSF Example Tag</display-name>
   <description>This library contains one simple custom JSF tag</description>

   <tag>
       <name>toggleGraphic</name>
       <tag-class>com.sabreware.tags.ToggleGraphicTag</tag-class>
       <body-content>JSP</body-content>
       <description>A simple tag for a custom JSF component</description>
       <attribute>
         <name>imageOne</name>
         <required>true</required>
         <rtexprvalue>false</rtexprvalue>
       </attribute>
       <attribute>
         <name>imageTwo</name>
         <required>true</required>
         <rtexprvalue>false</rtexprvalue>
       </attribute>
       <attribute>
         <name>id</name>
         <required>true</required>
         <rtexprvalue>false</rtexprvalue>
       </attribute>
   </tag>
</taglib>

<sabreware:toggleGraphic>标签有3个属性:一个标识符和组件的两个图像。清单11展示了标签处理程序。

清单11. /WEB-INF/classes/com/sabreware/tags/ToggleGraphicTag.java

package com.sabreware.tags;

import javax.faces.webapp.FacesTag;
import javax.faces.component.UIComponent;
import javax.faces.component.UIGraphic;
import javax.faces.context.FacesContext;
import javax.faces.event.FacesEvent;
import javax.faces.event.RequestEventHandler;

import com.sabreware.components.UIToggleGraphic;

public class ToggleGraphicTag extends FacesTag {
   private String imageOne, imageTwo;

   public void setImageOne(String imageOne) {
       this.imageOne = imageOne;  
   }
   public void setImageTwo(String imageTwo) {
       this.imageTwo = imageTwo;  
   }
   public void overrideProperties(UIComponent component) {
       super.overrideProperties(component);
       component.setAttribute("imageOne", imageOne);
       component.setAttribute("imageTwo", imageTwo);
   }
   public UIComponent createComponent() {
       UIToggleGraphic comp = new UIToggleGraphic();
       comp.setURL(imageOne);
       return comp;
   }
   public String getRendererType() {
       return null;
   }
}

前述的JSP自定义标签扩展了javax.faces.webapp目录中的FacesTag类。所有表示JSF组件的自定义标签要么扩展了FacesTag,要么在标签需要操纵其主体内容时扩展了FacesBodyTag。

除了将组件的图像存储为类成员变量之外,前述的JSP自定义标签也将这些图像作为该标签的组件属性存储在 overrideProperties()中。createComponent()方法是抽象的,因此必须由所有的FacesTag扩展实现。该方法创建 了一个组件并返回了到那个组件的引用。在前述的标签中,那个方法也通过调用该组件的setURL()方法来设置组件的原始图像。最后,FacesTag中 同样为抽象的getRendererType()方法返回了一个字符串常量,代表该组件的呈现程序的标识符。在这种情形中,组件不具有呈现程序。因此, getRendererType()方法返回null。

清单12展示了该自定义组件。

清单12. /WEB-INF/classes/com/sabreware/components/UIToggleGraphic.java

package com.sabreware.components;

import javax.servlet.http.HttpServletRequest;
import javax.faces.component.UIGraphic;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import javax.faces.event.CommandEvent;
import javax.faces.event.FacesEvent;

public class UIToggleGraphic extends UIGraphic {
   // This component supports one command: click.
   private static String clickCommand = "click";

   // This method indicates whether this component renders itself
   // or delegates rendering to a renderer.
   public boolean getRendersSelf() {
       return true;
   }

   // This method, which is called during the Render Response phase,
   // generates the markup that represents the component.
   public void encodeEnd(FacesContext context) throws java.io.IOException {
       ResponseWriter writer = context.getResponseWriter();
       HttpServletRequest request = (HttpServletRequest)
                             context.getServletRequest();

       // Represent this component as an HTML anchor element with an
       // image. When the image is clicked, the current page is reloaded
       // with a request parameter named component whose value is this
       // component's ID.
       writer.write("<a href='?component=" + getComponentId() + "'>");
       writer.write("<img border='0' src='");
       writer.write(request.getContextPath() + (String)getURL() + "'/>");
       writer.write("</a>");
   }

   // This method, which is called during the Apply Request Values phase,
   // decodes request parameters.
   public void decode(FacesContext context) throws java.io.IOException {
       HttpServletRequest request = (HttpServletRequest)
                             context.getServletRequest();
       // If there's a request parameter named component whose value
       // matches this component's ID...
       if(getComponentId().equals(request.getParameter("component"))) {
         // ...enqueue a command event on the FacesContext event queue
         // for this component. The processEvent method, listed below,
         // processes that event.
         context.addRequestEvent(this, new CommandEvent(this, clickCommand));
       }
   }

   // This method, which is called during the Handle Request Events phase,
   // processes command events that were added to the FacesContext by
   // the decode method.
   public boolean processEvent(FacesContext context, FacesEvent event) {
       if(event instanceof CommandEvent) {
         CommandEvent cmdEvent = (CommandEvent)event;

         // If the event's command name equals "click"...
         if(clickCommand.equals(cmdEvent.getCommandName())) {
             // ...toggle the component's image.
            toggleImage();
         }
         return false; // Go directly to render phase.
       }
       return true; // Process request normally.
   }

   // This method returns a string representing the component's type.
   public String getComponentType() {
       return "com.sabreware.components.UIToggleGraphic";
   }

   // This private method toggles the component's image.
   private void toggleImage() {
       String     imageOne = (String)getAttribute("imageOne");
       String     imageTwo = (String)getAttribute("imageTwo");
       String currentImage = (String)getAttribute("image");

       if(imageTwo.equals(currentImage))
         setAttribute("image", imageOne);
       else                
         setAttribute("image", imageTwo);

       // The setURL() method is defined in the superclass (UIGraphic).
       setURL((String)getAttribute("image"));
   }
}

前述的自定义组件扩展了UIGraphic类--一个用于显示不能由用户操纵的图像的JSF标准组件。UIToggleGraphic类重载了getRendersSelf,以返回true。这表明组件自己处理呈现和事件处理。

呈现是在encodeEnd()方法中发生的,这个方法生成了组件的标记--一个HTML anchor元素。当单击那个锚点时,就会重新加载当前页,并创建一个名为component的请求参数,其值为该组件的ID。 encodeEnd()方法是在JSF生命周期的Render Response阶段由JSF实现调用的。

decode()方法是在Apply Request Values阶段由JSF实现调用的。该方法会查找名为component的参数,如果请求参数值匹配组件的ID,decode()方法就把一个请求事件添加到JSF的上下文中。

事件处理是在 processEvent()方法中发生的,该方法是在Handle Request Events阶段由JSF实现调用的。这个方法检查是否单击了事件的名称(由 decode()方法生成的事件)。如果是, processEvent()方法就调用组件的 toggleImage()方法,以切换组件显示的图像。随后在组件呈现时,它就会显示新选中的图像。

前述的例子展示了如何可以使用JavaServer Faces实现自定义组件。但在清单12中,组件是自己处理呈现和事件处理的,因此它没有达到最大的灵活性。例如,组件不能安装其他的呈现程序,因此也不 能生成除HTML之外的标记。尽管还要多做一点工作,但把组件与呈现和事件处理分开将大大增加这些组件的灵活性(正如下一节所讨论的那样)。

分离呈现和事件处理

本节描述了前一节所讨论的,即如何把自定义组件与呈现和事件处理分离。把组件与它们的呈现和事件处理分离将增加重用性和灵活性,让您可以将其他的呈现程序与一个组件关联起来,以使用其他标记语言来生成这个组件的表示。

在清单13中,我已经重写了清单12中的组件。

清单13. /WEB-INF/classes/com/sabreware/components/UIToggleGraphic.java

package com.sabreware.components;

import javax.faces.component.UIGraphic;
import com.sabreware.eventHandlers.ToggleGraphicEventHandler;

public class UIToggleGraphic extends UIGraphic {
   // This component supports one command: click
   private static String clickCommand = "click";

   public UIToggleGraphic() {
      addRequestEventHandler(new ToggleGraphicEventHandler());
   }

   public boolean getRendersSelf() {
       return false;
   }

   public String getComponentType() {
       return "com.sabreware.components.UIToggleGraphic";
   }

   public String getRendererType() {
       return "ToggleGraphicHTMLRenderer";
   }

   public String getClickCommandName() {
       return clickCommand;
   }

   // This private method toggles the component's image.
   public void toggleImage() {
       String     imageOne = (String)getAttribute("imageOne");
       String     imageTwo = (String)getAttribute("imageTwo");
       String currentImage = (String)getAttribute("image");

       if(imageTwo.equals(currentImage))
         setAttribute("image", imageOne);
       else                
         setAttribute("image", imageTwo);

       // The setURL method is defined in the superclass (UIGraphic)
       setURL((String)getAttribute("image"));
   }
}

前述的组件将呈现和事件处理委托给其他对象。它通过重写getRendersSelf()方法以返回false来指明了委托。

组件的构造器创建了一个事件处理程序,并将这个事件处理程序添加到该组件的事件处理程序清单中。该组件也重写了 getRendererType()方法,以返回该组件呈现程序的标识符。

该组件的呈现代码被封装在一个呈现程序中,如清单14中所示。

清单14. /WEB-INF/classes/com/sabreware/renderers/ToggleGraphicHTMLRenderer.java

package com.sabreware.renderers;

import java.util.Iterator;
import java.util.Vector;
import javax.servlet.http.HttpServletRequest;
import javax.faces.component.AttributeDescriptor;
import javax.faces.component.UIComponent;
import javax.faces.component.UIGraphic;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import javax.faces.event.CommandEvent;
import javax.faces.event.FacesEvent;
import javax.faces.render.Renderer;
import com.sabreware.components.UIToggleGraphic;

public class ToggleGraphicHTMLRenderer extends Renderer {
   // This vector's iterator is returned from the getAttributeNames methods.
   private Vector emptyVector = new Vector();

   // This method, which is called during the Apply Request Values phase,
   // decodes request parameters.
   public void decode(FacesContext context, UIComponent component)
                                       throws java.io.IOException {
       HttpServletRequest request = (HttpServletRequest)
                             context.getServletRequest();
       // If there's a request parameter named component whose value
       // matches this component's ID...
       if(component.getComponentId().equals(
                               request.getParameter("component"))) {
         // ...enqueue a command event on the FacesContext event queue
         // for this component. The processEvent method, listed below,
         // processes that event.
         context.addRequestEvent(component,
             new CommandEvent(component, ((UIToggleGraphic)component).
                                           getClickCommandName()));
       }
   }

   // This method, which is called during the Render Response phase,
   // generates the markup that represents the component.
   public void encodeEnd(FacesContext context, UIComponent component)
                                       throws java.io.IOException {
       UIToggleGraphic toggleGraphic = (UIToggleGraphic)component;
       ResponseWriter writer = context.getResponseWriter();
       HttpServletRequest request = (HttpServletRequest)
                             context.getServletRequest();

       // Represent this component as an HTML anchor element with an
       // image. When the image is clicked, the current page is reloaded
       // with a request parameter named component whose value is this
       // component's ID.
       writer.write("/<a href='?component=" + component.getComponentId() + "'>");
       writer.write("/<img border='0' src='");
       writer.write(request.getContextPath() + (String)toggleGraphic.getURL() + "'/>");
       writer.write("/</a>");
   }
   public void encodeBegin(FacesContext context, UIComponent component)
                                       throws java.io.IOException {
   }
   public void encodeChildren(FacesContext context, UIComponent component)
                                       throws java.io.IOException {
   }
   public AttributeDescriptor getAttributeDescriptor(String componentType,
                                     String name) {
       return null;
   }
   public AttributeDescriptor getAttributeDescriptor(UIComponent component,
                                     String name) {
       return null;
   }
   public Iterator getAttributeNames(String componentType) {
       return emptyVector.iterator();
   }
   public Iterator getAttributeNames(UIComponent component) {
       return emptyVector.iterator();
   }
   public boolean supportsComponentType(String componentType) {
       return "com.sabreware.components.UIToggleGraphic".equals(componentType);
   }
   public boolean supportsComponentType(UIComponent component) {
       return supportsComponentType(component.getComponentType());
   }
}

清单12的 原始组件一样,前述的呈现程序实现了decode()和encodeEnd()方法。后者生成了组件的HTML标记。现在,最后的8个方法必须由所有的呈 现程序实现(不管它们是相关的或不相关的),因为这些方法是由Renderer接口定义的,所有的呈现程序都必须实现这些方法。有望JSF 1.0会提供一种抽象类来实现 Renderer接口,并提供这些方法合理的默认实现。

清单15列出了组件的事件处理程序。

清单15. /WEB-INF/classes/com/sabreware/eventHandlers/ToggleGraphicEventHandler.java

package com.sabreware.eventHandlers;

import javax.faces.event.RequestEventHandler;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.event.CommandEvent;
import javax.faces.event.FacesEvent;
import com.sabreware.components.UIToggleGraphic;

public class ToggleGraphicEventHandler extends RequestEventHandler {
   public boolean processEvent(FacesContext context, UIComponent component,
                                           FacesEvent event) {
       if(event instanceof CommandEvent) {
         CommandEvent cmdEvent = (CommandEvent)event;
         UIToggleGraphic toggleGraphic = (UIToggleGraphic)component;

         // If the event's command name equals "click"...
         if(toggleGraphic.getClickCommandName().
                       equals(cmdEvent.getCommandName())) {
             // ...toggle the component's image
             toggleGraphic.toggleImage();
             return false; // go directly to render phase
         }
       }
       return true; // process request normally
   }
}

清单12中的组件一样,前述的事件处理程序实现了processEvent()方法,以处理组件的请求事件。如果这个方法返回true,JSF生命周期就正常继续。否则,JSF生命周期就直接进入Render Response阶段。

Web应用程序的革命

JavaServer Faces是一个Web应用程序框架,它定义了一个请求生命周期和一种丰富的组件层次结构。请求生命周期将开发人员从他们的Web应用程序的代码编写中解 放出来,使得这些应用程序更加易于实现。组件层次结构让开发人员可以实现呈现不同标记类型的自定义组件。另外,开发人员可以为JSF内置组件实现呈现程 序,因此这些组件也可以生成其他的标记类型。JavaServer Faces有望对基于Java的Web应用程序的开发的产生关键影响。

关于作者

David Geary是Core JSTL Mastering the JSP Standard Tag Library (Prentice Hall,2002; ISBN: 0131001531)、 Advanced JavaServer Pages (Prentice Hall,2001; ISBN:0130307041)和Graphic Java系列(Sun Microsystems Press)的作者。18年来,David一直使用多种面向对象语言开发面向对象的软件。从GOF Design Patterns一书于1994年出版以来,David就是一位设计模式的积极倡议者,并在Smalltalk、C++和Java中使用了一些已实现的设 计模式。1997年,David开始成为一个专职的作者和业余的演讲者和顾问。David是定义JSP标准标签库专家组的一名成员,也是Apache Struts JSP框架的贡献者。他主持编写JavaWorld的 Java Design Patterns专栏。

免责声明

本文讨论的代码是针对EA2 JSF参考实现编写的。如前所述,规范和参考实现处在不固定状态,因此,本文中的代码在不远的将来可能会过时;然而,这些代码可作为EA2参考实现的广 告,它们分别在Tomcat 4.0.6和Resin 2.1.6上测试通过。此外,您可以一直阅读JSF规范,直到它成熟为止,但要真正掌握这些概念,还必须反复琢磨这些代码。

参考资料

 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
JSF是一种用于构建Java Web 应用程序的标准框架(是Java Community Process 规定的JSR-127标准)。JSF(Java Server Faces)技术为开发基于网络用户界面的Java开发者提供了标准的编程接口API以及标签库。就像Struts框架一样,JSF定义了一套JSF标签 JSF的全称   1、Joint Strike Fighter (JSF)   2、Java Server Faces (JSF) Java Server Faces (JSF)   JSF是一种用于构建Java Web 应用程序的标准框架(是Java Community Process 规定的JSR-127标准)。它提供了一种以组件为中心的用户界面(UI)构建方法,从而简化了Java服务器端应用程序的开发。由于由Java Community Process (JCP) 推动,属于Java EE 5中的技术规范,而受到了厂商的广泛支持。   JSF(Java Server Faces)技术为开发基于网络用户界面的Java开发者提供了标准的编程接口API以及标签库。就像Struts框架一样,JSF定义了一套JSF标签,能够生成与JavaBean属性绑定在一起的HTML表单元素。从应用开发者的角度看,两种框架十分相似,但是JSF可能会得到更多的支持,因为JSF是Java的标准。在未来的发展中,有可能所有的J2EE应用服务器都需要支持JSF。 Java Server Faces技术好处   引入了基于组件和事件驱动的开发模式,使开发人员可以使用类似于处理传统界面的方式来开发Web应用程序。提供了行为与表达的清晰分离。 不用特别的脚本语言或者标记语言来连接UI组件和Web层。JSF技术API被直接分层在Servlet API的顶端。 技术为管理组件状态提供一个丰富的体系机构、处理组件数据、确认用户输入和操作事件。 Java Server Faces应用程序   典型的JSF应用程序包含下列部分:   一组JSP页面   一组后台bean(为在一个页面上的UI组件定义的属性和函数的JavaBean组件)   应用程序配置资源文件(定义页面导航规则、配置bean和其它的自定义对象,如自定义组件)   部署描述文件( web.xml )   一组由应用程序开发者创建的自定义对象(有可能)   一些可能包含自定义组件、约束、转换器或者监听器的对象   为在页面中表现自定义对象的一组自定义tag   包含JSP页面的JSF应用程序也使用由为了表现UI组件和在页面上的其他对象的JSF技术而定义的标准的tag库。 Java Server Faces技术的重要开发框架 sun-ri、myfaces、icefaces、richfaces、seam

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值