内容


JSF 2 简介

后来添加的 Ajax 复合组件

让页面作者将 Ajax 功能添加到复合组件中

Comments

系列内容:

此内容是该系列 # 部分中的第 # 部分: JSF 2 简介

敬请期待该系列的后续内容。

此内容是该系列的一部分:JSF 2 简介

敬请期待该系列的后续内容。

JSF 2 简介 系列的 上一期 中,我讨论了使用内置的 Ajax 实现自动完成复合组件。页面作者可以在一个 facelet 中使用该组件,该组件负责处理所有 Ajax 细节。尽管内置的 Ajax 已经存在,但仍然可以在开发人员实现复合组件之后(可能是很长一段时间以后),方便地让页面作者将 Ajax 添加到该组件中。本文将展示复合组件如何容纳后来添加的 Ajax。

在 “JSF 2 简介,第 3 部分:事件处理、JavaScript 和 Ajax” 中已经讨论过,JSF 2 的 <f:ajax> 标记允许页面作者将后来的 Ajax 添加到 JSF 2 的内置组件中。例如,使用 <f:ajax>,您可以轻松将一个提交按钮转变为一个 Ajax 按钮:

<h:commandButton value="Click me">
  <f:ajax>
</h:commandButton>

但是, JSF 2 的 <f:ajax> 标记不会处理复合组件(确实如此),因为复合组件只是组件容器。

例如,在 “JSF 2 简介,第 2 部分:模板及复合组件” 中,我介绍了一个简单的图标复合组件,它包含一个链接,由一幅图像表示。当用户单击该图标时,该链接提交一个表单,后者触发一个与图标链接相关联的服务器端动作侦听器。使用图标很简单:

<util:icon image="...">
  <f:actionListener for="link" type="...">
</util:icon>

因为您可以使用 <f:ajax> 标记将提交按钮转变为 Ajax 按钮,您可能认为您能够对图标执行相同操作:

<util:icon image="...">
  <f:ajax>
  <f:actionListener for="link" type="...">
</util:icon>

上面的代码片段不会生效,因为我向图标组件附加了 <f:ajax> 标记,我真正想要做的是将它附加到图标内部的链接上。

在本例中,我需要的是一种允许我将 Ajax 行为附加到图标内部的链接的机制,或者更一般地,允许我将 Ajax 行为附加到复合组件内部的组件。这种机制(在 Mojarra 和 Apache MyFaces 中实现过,而在 JSF 2.0 中完全没有记录)是本文讨论的重点。(注意:在编写本文时已添加了 MyFaces 支持。)在了解该机制的工作原理之前,我将创建一个新的图标复合组件供使用。

可重用的图标组件

想象一下您拥有一项全世界最酷的工作。也许您正在实现新一代魔兽世界图形引擎。但遗憾的是,这只是个梦想。今天,您要实现的是如图 1 所示的字体选择程序:

图 1. 选择一种字体
字体选择程序
字体选择程序

您的上司问您实现此选择程序将需要多长时间。他们用户能够单击 2 字符预览窗口中的 + 和 - 图标来更改字体大小。当然,他们希望使用 Ajax,以便预览窗口和图标旁边显示的大小会自动更新,而无需对页面的其余部分进行操作。

您的上司希望得到的是一个简单的字体选择程序组件,但您知道这并不简单。您将实现一个通用的图标组件,这个组件可在运行时使用一个图像和一个操作进行配置,而且能够完全 Ajax 化,然后您将在字体预览窗口使用您的图标组件。通过这种方式,您将得到一个在将来迟早会用上的图标组件。

现在,我将向您展示如何使用不超过 25 行 XML 代码实现该图标组件。

字体选择程序示例

字体选择程序由 4 个文件组成:

  • 图 1 中显示的页面,在 index.xhtml 中定义。
  • 图标组件,位于 /resources/util/icon.xhtml 下。
  • 一个侦听器(com.clarity.FontSelectionListener.java)。
  • 一个 bean(com.clarity.FontSettings)。

图 2 展示了目录结构:

图 2. 字体选择程序示例的文件
目录结构
目录结构

清单 1 是 图 1 所示页面的 facelet — index.xhtml:

清单 1. facelet (/web/index.xhtml)
<?xml version="1.0" encoding="UTF-8"?>
<!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:f="http://java.sun.com/jsf/core"
      xmlns:h="http://java.sun.com/jsf/html" 
      xmlns:corejsf="http://corejsf.com"
      xmlns:util="http://java.sun.com/jsf/composite/util">

   <h:head>
      <h:outputStylesheet library="css" name="styles.css"/>
      <title>#{msgs.windowTitle}</title>
   </h:head>
   
   <h:body> 
      <h:outputStylesheet library="css" name="styles.css"/>
      
      <h:outputText value="#{msgs.fontSizeHeading}"
        style="padding-left: 30px; font-size: 2em;"/>
      
      <h:panelGrid columns="3" style="padding-left: 80px;">
        <util:icon id="minus" image="#{resource['images:minus.gif']}">
          <f:actionListener for="link" type="com.clarity.FontSelectionListener"/>
        </util:icon>

        <util:icon id="plus" image="#{resource['images:plus.gif']}">
          <f:actionListener for="link" type="com.clarity.FontSelectionListener"/>

        </util:icon>
        
        <h:outputText id="readout" value="#{fontSettings.size}em"/>
      </h:panelGrid>
            
      <h:outputText id="fontPreview" value="Aa" 
                 style="font-size: #{fontSettings.size}em; font-style: italic"/>

   </h:body>
</html>

清单 1 中的 facelet 为图标组件声明一个名称空间,并在页面中使用该组件。这使用了 JSF 2.0 复合组件 101,详细信息请参见 “JSF 2 简介,第 2 部分:模板及复合组件。”

请注意,两个图标都为图标的 link 组件配备了一个动作侦听器。当用户单击图标的链接时,JSF 调用服务器上的该侦听器,如清单 2 所示。

清单 2. 侦听器 (com/clarity/FontSelectionListener.java)
package com.clarity;

import javax.el.ELResolver;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.event.AbortProcessingException;
import javax.faces.event.ActionEvent;
import javax.faces.event.ActionListener;

public class FontSelectionListener implements ActionListener {
   @Override
   public void processAction(ActionEvent event)
         throws AbortProcessingException {
    FacesContext c = FacesContext.getCurrentInstance();
    ELResolver elResolver = c.getApplication().getELResolver();
    FontSettings fs = (FontSettings) 
      elResolver.getValue(c.getELContext(), null, "fontSettings");
    
    if (((UIComponent)event.getSource()).getClientId().startsWith("minus"))
      fs.decrement();
    else
      fs.increment();
   }
}

清单 2 中,我查看触发事件的客户端标识符是否是 minus,如果是,我就知道用户单击了减号图标,然后我将减小字体大小。否则,我将增大字体大小。

请注意,侦听器会获取对 fontSettings 托管 bean 的引用。它通过获取对 Expression Language Resolver 的引用来完成此任务,只要给定了托管 bean 的名称,后者知道如何找到它们。fontSettings bean 如清单 3 所示:

清单 3. 字体设置 bean (com/clarity/FontSettings.java)
package com.clarity;

import java.io.Serializable;

import javax.inject.Named; 
import javax.enterprise.context.SessionScoped; 

@Named
@SessionScoped
public class FontSettings implements Serializable {
   private static int INCREMENT = 1;
   private int size = 1;
   
   public int getSize() { return size; }
   public void setSize(int newValue) { size = newValue; }

   public void increment() { size += INCREMENT; }
   public void decrement() { size -= INCREMENT; }
}

前面的 3 个清单展示应用程序中除图标复合组件以外的所有代码。接下来将介绍图标复合组件。

实现图标复合组件

图标有 3 个需求:

  • 图像必须是可配置的。
  • 用户单击图像时触发的动作是可配置的。
  • 图标必须支持 Ajax。

在清单 4 中,我满足了前两个需求:

清单 4. <util:icon> 复合组件,版本 1 (/resources/util/icon.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="image" required="true"/>
      <composite:actionSource name="link"  targets="#{cc.clientId}:iconForm:link"/>
    </composite:interface>

    <composite:implementation>
      <div id="#{cc.clientId}">
         <h:form id="iconForm">
            <h:commandLink id="link" immediate="true">
              <h:graphicImage value="#{cc.attrs.image}"/>
            </h:commandLink>
         </h:form>
       </div>
    </composite:implementation>    
</html>

清单 4 中的图标组件声明一个 image 属性和一个名为 linkactionSource。该 actionSource清单 1 中用作 <f:actionListener>for 属性的值。如果还不太明白,您可以查看 “JSF 2 简介,第 2 部分:模板及复合组件” 了解动作源如何处理复合组件,其中简短讨论了一个与 清单 4 中类似的图标实现。

清单 4 中的图标组件实现允许页面作者配置图标的外观和行为,但不允许作者将 Ajax 行为附加到组件。完成实现之后,如果用户单击一个图像,JSF 会提交整个页面,在收到返回值时完全重新绘制该页面。

现在您将看到如何让页面作者将 Ajax 添加到图标组件。

使用 <composite:clientBehavior> 添加 Ajax 支持

为了让页面作者能够将 Ajax 添加到 <util:icon> 组件内的链接,我使用 <composite:clientBehavior> 标记,如清单 5 所示:

清单 5. <util:icon 复合组件,版本 2
<!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="image" required="true"/>
      <composite:actionSource   name="link"  targets="#{cc.clientId}:iconForm:link"/>
     <composite:clientBehavior name="click" 
                              event="action"
                            targets="#{cc.clientId}:iconForm:link"/>
    </composite:interface>

    <composite:implementation>
      <div id="#{cc.clientId}">
         <h:form id="iconForm">
            <h:commandLink id="link" immediate="true">
              <h:graphicImage value="#{cc.attrs.image}"/>
            </h:commandLink>
         </h:form>
       </div>
    </composite:implementation>    
</html>

<composite:clientBehavior> 标记为包含在复合组件中的一个组件公开一个 Ajax 事件。在 清单 5 中,我使用逻辑名称 click 声明了一个客户端行为,该名称与一个真实事件相关联 — 图标链接触发的 action 事件。下面总结了 <composite:clientBehavior> 标记的有效属性:

  • name:页面作者使用的事件名称。
  • defaulttruefalse。如果为 true,则使用 name 属性指定的事件为默认事件。
  • event:事件的实际名称。
  • targets:组件 ID 的空格分隔列表,JSF 将行为重新定位到这些组件。

现在页面作者可以将 Ajax 行为附加到图标组件了,如清单 6 所示:

清单 6. facelet,版本 2
<?xml version="1.0" encoding="UTF-8"?>
<!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:f="http://java.sun.com/jsf/core"
      xmlns:h="http://java.sun.com/jsf/html" 
      xmlns:corejsf="http://corejsf.com"
      xmlns:util="http://java.sun.com/jsf/composite/util">

   <h:head>
      <h:outputStylesheet library="css" name="styles.css"/>
      <title>#{msgs.windowTitle}</title>
   </h:head>
   
   <h:body> 
      <h:outputStylesheet library="css" name="styles.css"/>
      
      <h:outputText value="#{msgs.fontSizeHeading}"
        style="padding-left: 30px; font-size: 2em;"/>
      
      <h:panelGrid columns="3" style="padding-left: 80px;">
        <util:icon id="minus" image="#{resource['images:minus.gif']}">
          <f:ajax event="click" render=":readout :fontPreview"/>
          <f:actionListener for="link" type="com.clarity.FontSelectionListener"/>
        </util:icon>

        <util:icon id="plus" image="#{resource['images:plus.gif']}">
          <f:ajax event="click" render=":readout :fontPreview"/>
          <f:actionListener for="link" type="com.clarity.FontSelectionListener"/>
        </util:icon>
        
        <h:outputText id="readout" value="#{fontSettings.size}em"/>
      </h:panelGrid>
            
      <h:outputText id="fontPreview" value="Aa" 
                 style="font-size: #{fontSettings.size}em; font-style: italic"/>

   </h:body>
</html>

清单 6 中,我向图标添加了一个 <f:ajax> 标记。当用户单击图标时,JSF 对服务器进行 Ajax 调用;当调用返回时,JSF 呈现 readoutfontPreview 组件。

如果让我选择,我可以在图标的接口中向 <composite:clientBehavior> 标记添加一个 default="true" 属性。这使页面作者不必指定 click 事件,将 清单 6 中的 <f:ajax> 标记精减为一个 <f:ajax render=":readout :fontPreview">

结束语

在本文中,我展示了 JSF 2 如何(尽管使用了未经验证的标记)使页面作者能够轻松地将 Ajax 功能添加到复合组件。确实,可以看到,您可以使页面作者能够将发送大量 HTTP 请求的复合组件转变为发送 Ajax 请求的组件。所有这些都只需在复合组件的接口中添加一行 XML 代码来完成。

在 JSF 2 简介的下一期,我将离开复合组件的话题,探讨如何在 JSF 2 中使用 Contexts and Dependency Injection (CDI)。


下载资源


相关主题


评论

添加或订阅评论,请先登录注册

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Java technology, Web development
ArticleID=500180
ArticleTitle=JSF 2 简介: 后来添加的 Ajax 复合组件
publish-date=07132010