Facelets是JSF更好的外衣

是由 Sun 公司在 dev.java.net 上的一个开源项目,其主页为:facelets.dev.java.net。为什么说 Facelets 更适合JSF?笔者认为,主要是基于以下特性:
Facelets基于xml,它是组件树更自然的一种描述方式(xml天生就是一种树形结构描述语言)。

Facelets的模版技术,使它更适合网页开发

Facelets支持复合组件,并且,组件的定义方式更简单

Facelets的 jsfc 技术对 html 设计器更友好

与JSP相比,Facelets无需运行前编译,并且,Facelets 还适合对生成的组件树做cache,从而使运行期更轻量,效率更高

以下对Facelets的模版及复合组件技术做一个简单介绍。

首先,我们举一个场景:假设我们现在要做一个向导,该向导包含两个页面,分别是录入用户的姓名和其它信息.

暂且不考虑 Facelets,这两个页面的代码分别如下:

dialog1.xhtml:

<f:view xmlns="http://www.w3.org/1999/xhtml" xmlns:f="http://java.sun.com/jsf/core"
  xmlns:w="http://www.apusic.com/jsf/widget" xmlns:layout="http://www.apusic.com/jsf/layout"
  renderKitId="AJAX" xmlns:h="http://java.sun.com/jsf/html">
  <w:page title="Dialog" style="font-family: Verdana; font-size: 10pt; margin: 0px; padding: 0px">
    <w:form>
    <layout:panelGrid columns="2" width="100%" style="padding-left: 20px">
      <layout:cell colspan="1" rowspan="1" width="99%">
        <div style="font-size: 11pt; font-weight: bold;">Input Name</div>
      </layout:cell>
      <layout:cell colspan="1" rowspan="2" style="align: right">
        <img src="images/wizard.gif" />
      </layout:cell>
      <layout:cell colspan="1" rowspan="2">
        <div style="font-size: 10pt; font-weight: normal;">Please input your name</div>
      </layout:cell>
    </layout:panelGrid>
    <w:separator />
    <layout:panelGrid columns="2" style="height: 250px">
      <layout:cell style="vertical-align: top">
        <h:outputLabel value="Name:"/>
      </layout:cell>
      <layout:cell style="vertical-align: top">
        <w:textField id="name" width="250"/>
      </layout:cell>
    </layout:panelGrid>
    <w:separator />
    <layout:panelGrid columns="5" style="align: right" width="100%">
      <layout:cell style="width: 90%"> </layout:cell>
      <w:button value="Back"/>
      <w:button value="Next"/>
      <w:button value="Finished"/>
      <w:button value="Cancel"/>
    </layout:panelGrid>
    </w:form>
  </w:page>
</f:view>
dialog2.xhtml

<f:view xmlns="http://www.w3.org/1999/xhtml" xmlns:f="http://java.sun.com/jsf/core"
  xmlns:w="http://www.apusic.com/jsf/widget" xmlns:layout="http://www.apusic.com/jsf/layout"
  renderKitId="AJAX" xmlns:h="http://java.sun.com/jsf/html">
  <w:page title="Dialog" style="font-family: Verdana; font-size: 10pt; margin: 0px; padding: 0px">
    <w:form>
    <layout:panelGrid columns="2" width="100%" style="padding-left: 20px">
      <layout:cell colspan="1" rowspan="1" width="99%">
        <div style="font-size: 11pt; font-weight: bold;">Input Others</div>
      </layout:cell>
      <layout:cell colspan="1" rowspan="2" style="align: right">
        <img src="images/wizard.gif" />
      </layout:cell>
      <layout:cell colspan="1" rowspan="2">
        <div style="font-size: 10pt; font-weight: normal;">Please input your others information</div>
      </layout:cell>
    </layout:panelGrid>
    <w:separator />
    <layout:panelGrid columns="2" style="height: 250px">
      <layout:cell style="vertical-align: top">
        <h:outputLabel value="Others:"/>
      </layout:cell>
     <layout:cell style="vertical-align: top">
       <w:combo id="others" width="250"/>
     </layout:cell>
    </layout:panelGrid>
  <w:separator />
  <layout:panelGrid columns="5" style="align: right" width="100%">
    <layout:cell style="width: 90%"> </layout:cell>
      <w:button value="Back"/>
      <w:button value="Next"/>
      <w:button value="Finished"/>
      <w:button value="Cancel"/>
    </layout:panelGrid>
    </w:form>
  </w:page>
</f:view>
首先非常抱歉的是,笔者对HTML/CSS并不是很懂,因此,上述的代码示例有许多可以改进的地方。但无庸置疑的是: 上述两个页面有太多的重复性代码。如果我们用纯粹的JSP/Servlet技术来开发此应用,可以用 Tiles 来消除这种重复;如果用 AOM 来开发,则可以通过 Facelets 来达到同样的目的。

模版技术
针对上述示例,我们首先定义一个模版文件dialog_template.xhtml。

<f:view xmlns="http://www.w3.org/1999/xhtml" xmlns:f="http://java.sun.com/jsf/core"
  xmlns:w="http://www.apusic.com/jsf/widget" xmlns:layout="http://www.apusic.com/jsf/layout"
  renderKitId="AJAX" xmlns:h="http://java.sun.com/jsf/html" xmlns:ui="http://java.sun.com/jsf/facelets">
  <w:page title="Dialog" style="font-family: Verdana; font-size: 10pt; margin: 0px; padding: 0px"> 
    <layout:panelGrid columns="2" width="100%" style="padding-left: 20px">
      <layout:cell colspan="1" rowspan="1" width="99%">
        <div style="font-size: 11pt; font-weight: bold;">         
            <ui:insert name="title">default title</ui:insert>
        </div>
      </layout:cell>
      <layout:cell colspan="1" rowspan="2" style="align: right">
        <img src="images/wizard.gif" />
      </layout:cell>
      <layout:cell colspan="1" rowspan="2">
        <div style="font-size: 10pt; font-weight: normal;">
            <ui:insert name="description">default description</ui:insert>
        </div>
      </layout:cell>
    </layout:panelGrid>
    <w:separator />
<ui:insert name="content">empty content</ui:insert>
    <w:separator />
    <layout:panelGrid columns="5" style="align: right" width="100%">
      <layout:cell style="width: 90%"></layout:cell>
      <w:button value="Back" />
      <w:button value="Next" />
      <w:button value="Finished" />
      <w:button value="Cancel" />
    </layout:panelGrid>
  </w:page>
</f:view>
请注意,我们在上述模版文件中,分别插入三个<ui:insert/>标记,每个标记都有一个name属性,其值分别是:title、description、content。 这三个标记,本质上就相当于定义了三个“锚点”,至于具体的内容,随时可以替换。

现在,我们再来看看 dialog1 应该怎么使用这个模版。

<html xmlns="http://www.w3.org/1999/xhtml" xmlns:f="http://java.sun.com/jsf/core"
  xmlns:w="http://www.apusic.com/jsf/widget" xmlns:layout="http://www.apusic.com/jsf/layout"
  xmlns:h="http://java.sun.com/jsf/html"  xmlns:ui="http://java.sun.com/jsf/facelets">
   This text should be skiped.
<ui:composition template="/dialog_template.xhtml">
   This text should be skiped too.
<ui:define name="title">Input Name</ui:define>
<ui:define name="description">Please input your name</ui:define>

<ui:define name="content">
      <layout:panelGrid columns="2" style="height: 250px">
        <layout:cell style="vertical-align: top">
          <h:outputLabel value="Name:" />
        </layout:cell>
        <layout:cell style="vertical-align: top">
          <w:textField id="name" width="250" />
        </layout:cell>
      </layout:panelGrid>
    </ui:define>
  </ui:composition>
</html>
我们可以观察:首先,<ui:composition>指定使用哪个模版文件,然后通过<ui:define>对模版文件中每个可供插入的“<ui:insert>锚点”进行定义。在 上述的dialog1.xhtml文件中,我们针对模版文件(dialog_template.xhtml)的三个锚点,分别定义了相应的具体内容。在运行期,这些具体的内容, 将会被插入到模版文件中定义的锚点位置。 这里需要提醒注意的是:<ui:composition>标记和<ui:define>标记以外的所有内容都会被忽略,并且,最外面的<html>标记, 只是为了定义一个根元素,以便在这个根元素中声明命名空间,至于你是用<html>还是<f:view>,甚至是其它的乱七八糟的 标记,如<abcde>等等,其实都无所谓。笔者建议各位同学使用<html>作为根元素,一是因为<html>所属的命名空间已经被声明过了(xmlns="http://www.w3.org/1999/xhtml"), 二是因为它对设计器更友好(哪个网页设计器不认识<html/>标记呢?)。

Facelets的相关知识毕竟不是本文的重点,读者可以参考其它更加专业的文档,但为了方便对模版技术的理解,最后再把修订后的 dialog2.xhtml 页面源码附上:

<html xmlns="http://www.w3.org/1999/xhtml" xmlns:f="http://java.sun.com/jsf/core"
  xmlns:w="http://www.apusic.com/jsf/widget" xmlns:layout="http://www.apusic.com/jsf/layout"
  xmlns:h="http://java.sun.com/jsf/html"  xmlns:ui="http://java.sun.com/jsf/facelets">
  <ui:composition template="/dialog_template.xhtml">
    <ui:define name="title">Input Others</ui:define>
    <ui:define name="description">Please input your others information</ui:define>
    <ui:define name="content">
      <layout:panelGrid columns="2" style="height: 250px">
        <layout:cell style="vertical-align: top">
          <h:outputLabel value="Others:" />
        </layout:cell>
        <layout:cell style="vertical-align: top">
          <w:combo id="others" width="250" />
        </layout:cell>
      </layout:panelGrid>
    </ui:define>
  </ui:composition>
</html>
复合组件
事实上,笔者认为:复合组件才是 Facelets 的精华所在,而复合组件加上AOM的模型事件,则更是完美的搭配。

回顾Dialog的例子,我们发觉:back、next、finish、cancel这四个按钮,其实经常组合在一起,在许多的场景下都可以重复使用。 那么,我们希望把它组装成一个自定义的“复合组件”,以达到代码重用的目的。

首先,我们准备一个buttons.xhtml作为该复合组件的模版:

<f:view xmlns="http://www.w3.org/1999/xhtml" xmlns:f="http://java.sun.com/jsf/core"
  xmlns:w="http://www.apusic.com/jsf/widget" xmlns:layout="http://www.apusic.com/jsf/layout"
  xmlns:h="http://java.sun.com/jsf/html" xmlns:ui="http://java.sun.com/jsf/facelets">
  <w:page>
    This text above will not be displayed.
    It's purpose is for design tools.
    <ui:composition>
      <layout:panelGrid columns="5" style="align: right" width="100%">
        <layout:cell style="width: 90%" />
        <w:button value="Back" action="#{onBack}" />
        <w:button value="Next" action="#{onNext}" />
        <w:button value="Finish" action="#{onFinish}" />
        <w:button value="Cancel" action="#{onCancel}" />
      </layout:panelGrid>
    </ui:composition>
  </w:page>
</f:view>
同样需要注意的是:上述文件中真正有用的东西是<ui:composition>标记之内的内容,至于外面的<f:view>、<w:page>等标记, 纯粹是为了让设计器更好的工作而准备的。

在buttons.xhtml中,我们定义了四个按钮,并且,分别指定它们的 action为 #{onBack}、#{onNext}、#{onFinished}、#{onCancel}。然后我们再为这个组件准备一个描述文件:buttons.taglib.xml。

<!DOCTYPE facelet-taglib PUBLIC
  "-//Sun Microsystems, Inc.//DTD Facelet Taglib 1.0//EN"
  "http://java.sun.com/dtd/web-facesconfig_1_0.dtd">
<facelet-taglib>
  <namespace>http://www.mycompany.com/aom</namespace>
  <tag>
    <tag-name>buttons</tag-name>
    <source>buttons.xhtml</source>
  </tag>
</facelet-taglib>
在上面这个文件中,我们声明了一个新的tag,其名称为"buttons",它所属于的命名空间是:"http://www.mycompany.com/aom",并且,这个自定义组件的内容来源自 source属性指定的模版文件buttons.xhtml。

怎么告诉Facelets我们新做了一个复合组件呢?在 web.xml 中增加以下配置:

<context-param>
  <param-name>facelets.LIBRARIES</param-name>
  <param-value>/buttons.taglib.xml</param-value>
</context-param>
如果你有多个复合组件的描述文件,就以分号隔开。至此,一个复合组件就准备完毕了,我们来看如何使用这个复合组件。我们准备一个 test_buttons.xhtml来调用新做的这个复合组件,如下所示:

<f:view xmlns="http://www.w3.org/1999/xhtml" xmlns:f="http://java.sun.com/jsf/core"
  xmlns:w="http://www.apusic.com/jsf/widget" xmlns:layout="http://www.apusic.com/jsf/layout"
  xmlns:my="http://www.mycompany.com/aom" renderKitId="AJAX"
  xmlns:ui="http://java.sun.com/jsf/facelets">
  <w:page title="Test buttons component">
    <w:form>
      <h2>Test the buttons component.</h2>
      <p/>
      <my:buttons onBack="#{.onBack}" onNext="#{TestButtonsBean.onNext}"
         onFinish="#{TestButtonsBean.onFinish}" onCancel="#{TestButtonsBean.onCancel}"/>
    </w:form>   
  </w:page>
</f:view>
其中,TestButtonsBean如下所示:

@ManagedBean(name="TestButtonsBean", scope=ManagedBeanScope.SESSION)
public class TestButtonsBean {

  public String onBack() {
    System.out.println("onBack");
    return null;
  }

  public String onNext() {
    System.out.println("onNext");
    return null;
  }

  public String onFinish() {
    System.out.println("onFinish");
    return null;
  }

  public String onCancel() {
    System.out.println("onCancel");
    return null;
  }
}
通过上例可以看出:在 Facelets 下完成一个复合组件是比较简单的一件事情,

但有的同学要问了:这种在 <my:buttons onBack="#{testButtonsBean.onBack}"> 中直接引入 EL 表达式的风格,好像不是那么 IoVC ,有没有更好的解决方案?

答案是:“有。通过模型事件,则是一个更加优雅的解决方案。”

我们将模版文件buttons.xhtml修订如下:

<f:view xmlns="http://www.w3.org/1999/xhtml"  xmlns:f="http://java.sun.com/jsf/core"
  xmlns:w="http://www.apusic.com/jsf/widget"  xmlns:layout="http://www.apusic.com/jsf/layout" renderKitId="AJAX"
  xmlns:h="http://java.sun.com/jsf/html"  xmlns:ui="http://java.sun.com/jsf/facelets">
  <w:page>
    This text above will not be displayed.
    It's purpose is for design tools.
    <ui:composition>
      <layout:panelGrid columns="5" style="align: right" width="100%">
        <layout:cell style="width: 90%" />
        <w:button value="Back" action="#{buttonsBean.onBack}" />
        <w:button value="Next" action="#{buttonsBean.onNext}" />
        <w:button value="Finish" action="#{buttonsBean.onFinish}" />
        <w:button value="Cancel" action="#{buttonsBean.onCancel}" />
      </layout:panelGrid>
    </ui:composition>
  </w:page>
</f:view>
请注意,我们在模版文件中,直接将上述 Button 的 action 绑定在了一个 “buttonsBean” 上,由于这个 “buttonsBean” 是无状态的,所以,它的生命周期声明为none,以下是示例代码:

@ManagedBean(name = "buttonsBean", scope = ManagedBeanScope.NONE)
public class ButtonsBean {

  private EventBroadcaster event = EventBroadcaster.getInstance();
 
  public Object onBack() {
    event.broadcast(this, "com.mycompany.buttons.onBack");
    return null;
  }

  public Object onNext() {
    event.broadcast(this, "com.mycompany.buttons.onNext");
    return null;
  }

  public Object onFinish() {
    event.broadcast(this, "com.mycompany.buttons.onFinish");
    return null;
  }

  public Object onCancel() {
    event.broadcast(this, "com.mycompany.buttons.onCancel");
    return null;
  }
}
换言之,当点击不同的按钮时,buttonsBean 会发送不同的模型事件,事件类型以不同的字符串区分。

然后,我们再修订一下test_buttons.xhtml,看上述这个复合组件,是如何使用的:

<f:view xmlns="http://www.w3.org/1999/xhtml" xmlns:f="http://java.sun.com/jsf/core"
  xmlns:w="http://www.apusic.com/jsf/widget" xmlns:layout="http://www.apusic.com/jsf/layout"
  xmlns:my="http://www.mycompany.com/aom" renderKitId="AJAX"
  xmlns:ui="http://java.sun.com/jsf/facelets">
  <w:page title="Test buttons component">
    <w:form>
      <h2>Test the buttons component.</h2>
      <p/>
      <my:buttons/>
    </w:form>   
  </w:page>
</f:view>
可以看到,此处,我们只是声明一个<my:buttons/>,并没有将其绑定任何EL表达式。那么,如果我要响应 onBack 按钮点击事件该怎么办呢?很简单,只要有一个 EventListener,监听 "com.mycompany.buttons.onBack"事件即可。以下是 TestButtonsBean 的示例代码:

@ManagedBean(name="TestButtonsBean", scope=ManagedBeanScope.SESSION)
public class TestButtons {

  @EventListener("com.mycompany.buttons.onBack")
  public void onBack() {
    System.out.println("onBack event");
  }
}
通过上例可以看出:通过模型事件,我们能够设计一个更加优雅的复合组件。

你可能感兴趣的:(xml,jsp,XHTML,JSF,sun)