JSF 学习之 编写自定义控件(第一部分)

JSF 学习之 编写自定义控件(第一部分)

JSF 学习之 编写自定义控件

参考资料:
1.Sun的 Java EE 5 turorial的第十三章Creating Custom UI Components
        http://java.sun.com/javaee/5/docs/tutorial/doc/bnavg.html

2. http://www.coreservlets.com/JSF-Tutorial/ 上的section 14
3. 《core JavaServer™ Faces》Chapter 9. Custom Components
       Prentice.Hall.PTR.Core.JavaServer.Faces.Jun.2004.eBook-DDU.chm
4. 《Java.Server.Faces编程(中文).pdf》 第十二,十三章
5. Apache 的子项目myfaces 的源代码。
http://myfaces.apache.org/  
myfaces是一个开源的JSF实现项目。Tomahawk 是myfaces的一个子项目,里面包含像日历选择,popup弹出框,javascript菜单,tree树形控件,html在线编辑器等常用控件。对 jsf基本控件是一个很好的扩展。两者的代码都可以在    http://apache.mirror.phpchina.com/myfaces/source/ 找到。高手说源码之前了无秘密,想自己写自定义控件的,可以下载下来看看,看别人高手是怎么做的吧。
6. Java Enterprise Edition 5 API Documentation
    可以在这里下载到http://www.allimant.org/javadoc/index.php


我摸索了好一段时间终于实现了一个用户登录状态条控件。一个简单的jsf自定义控件,主要是想看看是怎么实现自定义控件的属性,事件等功能的。
         在JSF中编写一个自定义控件(自定义tag ?)大概要实现一个 component类、一个tag 类、还有定义控件属性的 *.tld文件。 如果你要把控件显示处理部分独立出来的话,可能还要实现一个Rendererr类。

以下是详细代码,所有代码基于JSF1.2 :

1. component类 ,在文件 UIUserBar.java 中实现。
package widebright;

import javax.faces.component.*;
import javax.faces.context.*;
import java.io.*;
import java.util.*;
import javax.faces.context.FacesContext;
import javax.el.ValueExpression ;
import javax.el.MethodExpression;
import javax.faces.event.*;
import javax.el.ELException;;

//继承 UIComponentBase 这个face控件基本类,如果想支持action 这个常用jsf控件属性,那么要实现ActionSource2 这个接口才行。
public class UIUserBar extends UIComponentBase // implements ActionSource2
{
        //不是实现 id 等基本的属性了,因为jsf里面默认已经实现了id 等基本的属性的支持了
        private ArrayList<URL> urls = null;
        private Boolean userlogin = false;
        MethodExpression _userLeaveListener =null;

   @Override
   public String getFamily() {
     //Specifying the Component Family
     //If your custom component class delegates rendering,
     //it needs to override the getFamily method of UIComponent
     //to return the identifier of a component family, which is used to
     //refer to a component or set of components that can be rendered by a
     //renderer or set of renderers. The component family is used along
     //with the renderer type to look up renderers that can render the component.
    
    
     //如果没有另外单独实现renderer,返回null就行了。 如果要自己提供renderer ,
     //必须返回一个在配置文件中component-family 节注册的renderer
          return(null);  
   }

    
     //decode 中处理从用户那里传过来的 值
   @Override
   public void decode(FacesContext context) {
     super.decode(context);
       //这里可以获取用户输入数据了, 因为这个控件没有输入内容的,就不实现了
     String clientId = this.getClientId(context);
     Map paramMap = context.getExternalContext().getRequestParameterMap();
    // if (paramMap.containsKey(clientId) ){
    
      this.queueEvent(new UserLeaveEvent(this));
     
   // }
    
   }

   //需要在encodeBegin 或者encodeEnd 中输出 html标签
   //不过好像说是带 输入 的就要在encodeEnd中实现实现,
   @Override
   public void encodeBegin(FacesContext context) throws IOException {
  
      if (context == null)
      {
          throw new NullPointerException();
      }
     
     ExternalContext externalContext= context.getExternalContext();
     Map sessionMap = externalContext.getSessionMap();
    
     ResponseWriter writer = context.getResponseWriter();
    
        writer.startElement("div", this );
        writer.writeAttribute("id", this.getId(), null);
      
        writer.startElement("nobr", this );
       
      
        //输出链接
        if (urls != null){
         for (URL tmp : urls ){
       writer.startElement("a", this );
       writer.writeAttribute("href", tmp.url,null);
       writer.writeAttribute("target", "_blank",null);
        writer.write(tmp.text);
       writer.endElement("a");
      
       writer.write(" | ");
         }
    
        }else {
         ValueExpression expression = getValueExpression("urls");
         if (expression != null)
         {
         ArrayList<URL> tmpUrls = (ArrayList<URL>)expression.getValue(getFacesContext().getELContext());
          for (URL tmp : tmpUrls ){
        writer.startElement("a", this );
        writer.writeAttribute("href", tmp.url,null);
        writer.writeAttribute("target", "_blank",null);
         writer.write(tmp.text);
        writer.endElement("a");
      
        writer.write(" | ");
          }
         }
          
        
        }
    
       
       
     writer.startElement("a", this );
     writer.writeAttribute("href", ");
     writer.writeAttribute("target", "_blank",null);
    writer.write("widebright的百度空间");
     writer.endElement("a");
    
     writer.write(" | ");
    
     if (sessionMap.containsKey("username")) // 用户已经登录
     {
      writer.startElement("a", this );
      writer.writeAttribute("href", ");
      writer.writeAttribute("target", "_blank",null);
       writer.write("退出登录");
      writer.endElement("a");
      
     }
     else //用户没有登录   
     {
      writer.startElement("a", this );
      writer.writeAttribute("href", ");
      writer.writeAttribute("target", "_blank",null);
       writer.write("登录");
      writer.endElement("a");
     
     }   
      
     writer.endElement("nobr");
    
     writer.endElement("div");
     
   }

    //需要在encodeBegin 或者encodeEnd 中输出 html标签
    //不过好像说是带 输入 的就要在这里实现,不然在encodeBegin 实现也是可以的。
   @Override
   public void encodeEnd(FacesContext context) throws IOException {
      //都在 encodeBegin 里面 实现了,这里就不写了, 也可以留一部分到这里再做也可以得
    
   }
  
   //saveState 和 restoreState 是为了保持持久状态,一般不用做这个都可以的了。如果你不需要这种功能的话
     @Override
     public Object saveState(FacesContext facesContext)
     {
      Object[] values = new Object[30];
      values[0] = super.saveState(facesContext);
      values[1] = urls;
      values[2] = _userLeaveListener;
      values[3] = userlogin ;
      return values;
     }
     @Override
     public void restoreState(FacesContext facesContext, Object state)
     {
       Object[] values = (Object[])state;
       super.restoreState(facesContext,values[0]);
       urls = (ArrayList<URL>)values[1];
       _userLeaveListener = (MethodExpression ) values[2] ;
       userlogin= (Boolean)values[3];
     }
       
     public MethodExpression getUserLeaveListener()
     {
         return _userLeaveListener ;
     }

     public void setUserLeaveListener(MethodExpression userLeaveListener )
     {
          _userLeaveListener = userLeaveListener;
     }

     public void broadcast(FacesEvent event) throws AbortProcessingException
     {
          super.broadcast(event);

         MethodExpression userLeaveListenerExpression = getUserLeaveListener();
       
         if (userLeaveListenerExpression != null && event instanceof UserLeaveEvent )
         {
             try
             {
             userLeaveListenerExpression.invoke(getFacesContext().getELContext(), new Object[]{event});
             }
             catch (ELException e)
             {
                 Throwable cause = e.getCause();
                 if (cause != null && cause instanceof AbortProcessingException)
                 {
                     throw (AbortProcessingException)cause;
                 }
                 else
                 {
                     throw e;
                 }
             }
         }
     }
    
    
    
    
     public ArrayList<URL> getUrls(){
      if (urls!= null){
         return urls;
          }
          ValueExpression expression = getValueExpression("urls");
          if (expression != null)
          {
             return (ArrayList<URL>)expression.getValue(getFacesContext().getELContext());
          }
          return null;
     
    }

   public void setUrls(ArrayList<URL> urls) {
    this.urls =   urls;
   }
   public boolean isUserlogin() {
         if (userlogin != null) return userlogin.booleanValue();
         ValueExpression expression = getValueExpression("userlogin");
        
         Boolean v = expression != null ? (Boolean) expression.getValue(getFacesContext().getELContext()) : null;
         return v != null ? v.booleanValue() : false;
   
   }

   public void setUserlogin(boolean userlogin) {
    this.userlogin = Boolean.valueOf(userlogin);
   }
  
  

}

getFamily 函数定义类型。
decode 函数处理用户输入,典型的可以在整理判断用户返回里面有没有自己控件id,然后使用queueEvent 函数触发事件。
encodeBegin,encodeEnd 生成html编码,如果没有打算自己边学Renderer类的,就是在这里生成自己控件的呈现html代码了。这两个函数选择一个就可以完成功能了,不过有特殊情况一定要使用encodeEnd才行。
saveState 和restoreState 是保持控件状态。
broadcast 应该是事件处理的,不了解到自己看一下jsf的生命周期图。我自己是通过MethodExpression 的 invoke方法来调用事件的方法了。

剩下的都是为了支持 控件 属性/方法 的代码了。为了自己的属性支持JSF表达式可以使用ValueExpression 和MethodExpression 类型的变量了。具体看我前面列出来的参考资料。

2. Tag 类。

Tag类负责把jsp页面上的属性映射到 前面的componet 类对应的属性。 就是做componet 类 和jsp文件的中介了。主要是为了实现空间属性而写的getter和setter方法。可以看到大量使用ValueExpression和MethodExpression ,都是为了支持JSF表达式属性的的。SetProperties 方法就是把 属性映射到componet 类 的了。注意: id 等基本属性,默认在jsf中已经实现支持了,所以就不用写额外的get/set代码了,只要在 *.tld中文件中定义就行了。
UserBarTag.java文件内容:
package widebright;


import java.util.ArrayList;

import javax.faces.webapp.*;
import javax.faces.application.Application;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.el.ValueExpression ;
import javax.el.MethodExpression;


//因为UIComponentELTag 也实现了jsp的支持,所以要引用 jsp-api.jar el-api.jar 等包才行,在Tomcat6.0的lib目录下找的到,
//所以要在buildpath里面添加tomcat的lib目录, 然后就可以编译成功了

//另外jsf文档建议 JSF1.2 里面使用UIComponentELTag 而不是以前的UIComponentTag 类了 (UIComponentTag 是for jsf 1.1的)

//Tag 类负责创建 omponent 类(组件类) 并且把 页面书写的标签对应的属性值传给 omponent 类对应的属性。
public class UserBarTag extends UIComponentELTag {

//attributes id, rendered and binding are handled by UIComponentTag
//UIComponentTag 中默认已经提供了对 id ,binding等基本属性的支持了。所以自己写的UserBarTag 子类
//里面就不用再多做一遍了。
   
     private ValueExpression urls =null;
     private ValueExpression userlogin =null;
     private MethodExpression userleave;
   
    
    // public boolean getIsUserLogin(){
    //        return this.isUserLogin;
    // }
     
   public MethodExpression getUserleave() {
    return userleave;
   }
   public void setUserleave(MethodExpression userleave) {
    this.userleave = userleave;
   }
   public ValueExpression getUrls() {
    return urls;
   }
   public void setUrls(ValueExpression urls) {
    this.urls = urls;
   }

  
     public String getComponentType() {
       // Associates tag with component in faces-config.xml
        return("UserBar");
   }
   public String getRendererType() {
       // Component renders itself in encodeBegin,
       // so return null.
       return(null);
   }
   
   public void release() {

      // always call the superclass method

      super.release();

     // divId = null;
      urls = null;
  
   }

   public void setProperties(UIComponent component) {
     // always call the superclass method
     super.setProperties(component);
     
    // component.getAttributes().put("isUserLogin", isUserLogin);
    
// 在jsp页面上的id属性,默认已经有实现了,所以在自己的UserBarTag,UIUserBar类里面就不用 写 id属性的实现了。
//   String a =   this.getId();
//   String b = component.getId();
  
     if (urls !=null)
     {
      if(urls.isLiteralText()) { //看有没有包含表达式
             String text =urls.getExpressionString();

             ArrayList<URL> arrayUrls = new ArrayList<URL>();
             String [] rows = text.split(";");
             for (int i =0 ;i< rows.length ; i++){
              String [] url =    rows[i].split(",");      
              arrayUrls.add(new URL(url[0],url[1], url[2]) );
             }
             component.getAttributes().put("urls", arrayUrls);
      } else {
      
       //可以直接在这里就获取ValueExpression的值,然后就可以不用在UIuserBar中用getValueExpression("urls")来获取值了
       //自己选择用什么方法把
//        ArrayList<URL> arrayUrls = new ArrayList<URL>();
//        arrayUrls = ( ArrayList<URL>) urls.getValue(getFacesContext().getELContext());
//        component.getAttributes().put("urls", arrayUrls);
       
        //可以直接用setValueExpression 传过去,但这样要在UIuserBar中用getValueExpression("urls"); 来处理
           component.setValueExpression("urls", urls);
     
      }  
      
     }  
    
     if ( userlogin !=null)
     {  
              if (userlogin .isLiteralText())
              {
                  component.getAttributes().put("userlogin", Boolean.valueOf(userlogin.getExpressionString()));
              }
              else
              {
                  component.setValueExpression("userlogin", userlogin);
              }
          
     }
     if ( userleave !=null)
     {  
              if (userleave .isLiteralText())
              {
                 
              }
              else
              {
                  ((UIUserBar)component ).setUserLeaveListener(userleave);
              }
          
     }
  
    }
  
   public ValueExpression getUserlogin() {
    return userlogin;
   }
   public void setUserlogin(ValueExpression userlogin) {
    this.userlogin = userlogin;
   }


  
  
}

3. UserBar.tld 文件,声明控件属性,主要是给jsp编辑器等提供自动化帮助等作用的吧。注意以下属性,方法是怎么定义的, <uri> 那个也很有意思,使用的时候就知道了,呵呵。可以参考http://java.sun.com/javaee/5/docs/tutorial/doc/bnamu.html 来获取tld文件的说明。Tld文件最后应该放在 web-inf目录下的吧。


<?xml version="1.0" encoding="UTF-8" ?>
<taglib>
<tlib-version>1.0</tlib-version>
<jsp-version>2.1</jsp-version>
<short-name>userbar</short-name>
<uri>http://hi.baidu.com/widebright</uri>

<tag>
   <description>用于在网页开头输出用户登录等链接</description>
   <name>userBar</name>
   <tag-class>widebright.UserBarTag</tag-class>
   <body-content>empty</body-content>
   <attribute>
    <name>id</name>
   </attribute>
     <attribute>
         <description>
                The ValueExpression linking this component to a property in a backing bean
            </description>
            <name>
                binding
            </name>
            <required>
                false
            </required>
            <deferred-value>
                <type>
                    widebright.UIUserBar
                </type>
            </deferred-value>
  
   </attribute>
   <attribute>
    <name>urls</name>
    <description>Additional links to display</description>
    <required>false</required>
    <deferred-value>
     <type>java.util.ArrayList</type>
    </deferred-value>
   </attribute>
   <attribute>
    <name>userlogin</name>
    <description>If the user has logined .</description>
    <required>false</required>
    <deferred-value>
     <type>java.lang.String</type>
    </deferred-value>
   </attribute>

   <attribute>
    <name>userleave</name>
    <required>false</required>
    <description>User leave event handler</description>
    <deferred-method>
     <method-signature>
      void userLeaveListener(widebright.UserLeaveEvent)
     </method-signature>
    </deferred-method>

   </attribute>
</tag>
</taglib>

4. 其他的还有一个自己写的辅助类url.java
package widebright;

import javax.faces.component.UIComponent;
import javax.faces.event.FacesEvent;
import javax.faces.event.FacesListener;
import javax.faces.event.PhaseId;

public class UserLeaveEvent
             extends FacesEvent
{
    private static final long serialVersionUID = -2514646300612129099L;
   
    public UserLeaveEvent (UIComponent component)
    {
        super(component);
        setPhaseId(PhaseId.APPLY_REQUEST_VALUES);
    }
    public boolean isAppropriateListener(FacesListener listener)
    {
        return listener instanceof UserLeaveListener;
    }

    public void processListener(FacesListener listener)
    {
        ((UserLeaveListener)listener).processUserLeave(this);
    }

}

为了提供事件而写的Event 和listener类
UserLeaveEvent.java

package widebright;

import javax.faces.component.UIComponent;
import javax.faces.event.FacesEvent;
import javax.faces.event.FacesListener;
import javax.faces.event.PhaseId;

public class UserLeaveEvent
             extends FacesEvent
{
    private static final long serialVersionUID = -2514646300612129099L;
   
    public UserLeaveEvent (UIComponent component)
    {
        super(component);
        setPhaseId(PhaseId.APPLY_REQUEST_VALUES);
    }
    public boolean isAppropriateListener(FacesListener listener)
    {
        return listener instanceof UserLeaveListener;
    }

    public void processListener(FacesListener listener)
    {
        ((UserLeaveListener)listener).processUserLeave(this);
    }

}

UserLeaveListener.jave:
package widebright;

import javax.faces.event.AbortProcessingException;
import javax.faces.event.FacesListener;

public interface UserLeaveListener {
    public abstract void processUserLeave(UserLeaveEvent tabChangeEvent)
    throws AbortProcessingException;
}

如何使用这个自定义控件,参考第二篇文章

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值