在阅读本文之前,你必须熟悉这两个流行的web开发框架-Extjs和JSF。熟悉他们基本的开发技术及基本原理。本文将简要介绍包装的过程及思想,以及Fence包装时的实例。
一、阅读本文你所需要了解的知识
1、ExtJS组件的概念、书写方式、与服务端交互方式、组件组合方式等;
2、JSF1.2+的技术原理、生命周期过程、组件概念及渲染器的概念;
3、熟悉Http,ajax的基本知识,基本概念及使用方法;
4、你所使用的JDK必须在1.5+,并熟悉其一些新特性,如泛型、元数据、注释等;
在以上技术的基础上,我们开始吧!
二、将Ext组件转化成JSF的组件java类
Ext是一套完整的API,组件全面丰富,如果你要一个一个改成JSF组件的话,估计你会累的半死,再加上ExtApi的不断更新,你的组件类将无法保证 与Ext一致。所以必须要有工具自动生成JSF组件类。这里生成的方式可以就多样了,比喻使用dom4j解析Api文档等多中方式,本人是就地取材,使用 Ext的Xtemplate的功能生成的,呵呵,此处不赘述了。
所有的JSF自定义组件都是继承自UIComponentBase,由于我们都是输出组件,所以直接继承UIOutPut组件,以Menu的Textitem为例,生成完整的代码如下:
从代码中我们可以看出,我在生成组件的时候,放弃了Ext api中的方法和事件,只包含了config,并且生成的时候完全遵循Ext的继承逻辑结构,这样组件的属性在JSF类中将与Ext完全一致。
package ext.menu; import javax.el.ValueExpression; import javax.faces.context.FacesContext; import ext.annotation.InstanceOf; import ext.annotation.ParseConfigMode; import ext.annotation.PersistenceMode; import ext.annotation.ReferenceMode; import ext.annotation.XType; /** *Note:This java code is auto generated by abner,do not edit it. Adds a static * text string to a menu, usually used as either a heading or group separator. */ @XType("menutextitem") //ext bug @InstanceOf("Ext.menu.TextItem") @ParseConfigMode(name = "items", pmode = PersistenceMode.ParentProperty, rmode = ReferenceMode.Config) public class TextItem extends BaseItem{ public static final String COMPONENT_TYPE = "Ext.menu.TextItem"; public static final String COMPONENT_FAMILY = "Ext.menu.TextItem"; /** * * Create a new {@link TextItem} instance with default property values. * */ public TextItem() { super(); setRendererType(COMPONENT_FAMILY); } public String getFamily() { return (COMPONENT_FAMILY); } private Boolean hideOnClick; /** * True to hide the containing menu after this itemis clicked (defaults to * false) */ public Boolean getHideOnClick() { if (null != this.hideOnClick) { return this.hideOnClick; } ValueExpression _ve = getValueExpression("hideOnClick"); if (_ve != null) { return (Boolean) _ve.getValue(getFacesContext().getELContext()); } else { return null; } } /** * * Set the value of the hideOnClick property. * */ public void setHideOnClick(Boolean hideOnClick) { this.hideOnClick = hideOnClick; this.handleConfig("hideOnClick", hideOnClick); } private String itemCls; /** * The default CSS class to use for text items(defaults to "x-menu-text") */ public String getItemCls() { if (null != this.itemCls) { return this.itemCls; } ValueExpression _ve = getValueExpression("itemCls"); if (_ve != null) { return (String) _ve.getValue(getFacesContext().getELContext()); } else { return null; } } /** * * Set the value of the itemCls property. * */ public void setItemCls(String itemCls) { this.itemCls = itemCls; this.handleConfig("itemCls", itemCls); } private String text; /** * The text to display for this item (defaults to'') */ public String getText() { if (null != this.text) { return this.text; } ValueExpression _ve = getValueExpression("text"); if (_ve != null) { return (String) _ve.getValue(getFacesContext().getELContext()); } else { return null; } } /** * * Set the value of the text property. * */ public void setText(String text) { this.text = text; this.handleConfig("text", text); } private Object[] _values; public Object saveState(FacesContext _context) { if (_values == null) { _values = new Object[4]; } _values[0] = super.saveState(_context); _values[1] = hideOnClick; _values[2] = itemCls; _values[3] = text; return _values; } public void restoreState(FacesContext _context, Object _state) { _values = (Object[]) _state; super.restoreState(_context, _values[0]); this.hideOnClick = (Boolean) _values[1]; this.handleConfig("hideOnClick", this.hideOnClick); this.itemCls = (String) _values[2]; this.handleConfig("itemCls", this.itemCls); this.text = (String) _values[3]; this.handleConfig("text", this.text); } }
三、渲染组件
从上面的代码中有个关键的地方在于类的注释,如下:
@XType("menutextitem") //ext bug @InstanceOf("Ext.menu.TextItem") @ParseConfigMode(name = "items", pmode = PersistenceMode.ParentProperty, rmode = ReferenceMode.Config)
通过JSF配置组件的renderer之后,在渲染器中就可以找到此组件的元数据,一看即明白:InstanceOf代表组件渲染时候的实例类,如此组件 渲染后就是 var j_id = new Ext.menu.TextItem({....});当然所有的属性就会变成ext的config。在这个过程中将会有一些复杂的处理,比喻 config类型。生成后模式,这个就主要靠ParseConfigMode来控制了。当然无法一一将其细节写出来,到时候发布源码了大家自然可以看到。
四、处理特殊重要的组件
在Ext中主要的容器是panel及其子类,数据输出主要靠Form中的field和store,所以对这些组件当然有各自的渲染器。例如Field的渲 染,要保证field在JSF中生效,则必须实现ValueHolder接口、要保证按钮与JSF整合,则必须要实现ActionSource2接口 (1.2)等,如果你熟悉JSF组件的开发,此处就不再赘述,我们主要的是渲染Ext组件。
五、Ajax的处理
在Ext中formpanel及store主要是靠ajax来与服务端交互数据,所以支持ajax当然是最重要的。本然在渲染处理ajax时,完全遵循 Ext的api,在渲染过程中给按钮动态添加事件即可,在事件中带上参数,如Ext_ajax表示这个请求是ajax请求,则在后台即通过不同的方式处 理,此处就是用到JSF的生命周期了,在ViewHanler及ViewRoot中(都支持装饰模式)分别都拦截Ajax请求,这样你就是随意输出想输出 的数据结构,本人多数采用Xml格式输出数据,当然有些要与Ext一致,如Store有时是直接输出的数据Json格式。如果按钮渲染的重要代码:
.......................................................................代码片段 if (this.isTypeOf(commandBtn, "submit")) { Boolean stSubmit = form.getStandardSubmit(); if (stSubmit != null && stSubmit.booleanValue()) { String hiddenId = form.getClientId(context) + ExtFormPanelRenderer.EVENT_SUFFIX; String funBody = hiddenId + ".setValue(src.getId());"; sb.append(funBody); hiddenId = form.getClientId(context) + ExtFormPanelRenderer.VIEWSTATE_SUFFIX; funBody = hiddenId + ".setValue(Fence.getViewState());"; sb.append(funBody); } sb.append(form.getVar()); sb.append(".getForm().submit("); if (stSubmit == null || !stSubmit.booleanValue()) { sb.append(encodeFormCommandOptions(context, form, commandBtn, "submit")); } sb.append(");"); } else if (this.isTypeOf(commandBtn, "load")) { sb.append(form.getVar()); sb.append(".getForm().load("); sb.append(encodeFormCommandOptions(context, form, commandBtn,"load")); sb.append(");"); } else if (this.isTypeOf(commandBtn, "reset")) { sb.append(form.getVar()); sb.append(".getForm().reset();"); } ............................ ................. if (action != null) { if (action.getFailure() == null) action.setFailure(JSUtils.ActionFailureCallBack); if (action.getSuccess() == null) action.setSuccess(JSUtils.ActionSuccessCallBack); if (action.getUrl() == null) action.setUrl(Ext.PREFIX_RAW_VALUE + ScriptManager.AJAX_PATH); if (action.getWaitMsg() == null) action.setWaitMsg("Loading..."); params.putAll(ExtBasicRenderer.getParamList(action)); } else { action = new Submit(); action.setFailure(JSUtils.ActionFailureCallBack); action.setSuccess(JSUtils.ActionSuccessCallBack); action.setUrl(Ext.PREFIX_RAW_VALUE + ScriptManager.AJAX_PATH); action.setWaitMsg("Loading..."); } ...................................................
六、如何显示界面
按照传统的Ext写页面的方式,每个页面包含一个或者多个JS,然后在Js文件中写Ext脚本。而包装成JSF组件之后,这部分工作已经交由JSF框架来 处理了,将所有渲染的组件生成的JS通过一个对象存储,在页面渲染时,作为一个JS资源嵌套在页面中,这样请求的资源输出页面生成的脚本文件即可。
<script type="text/javascript" src="/extjs/faces/abner.fence.script.js?st=1261213194245"><!--{12612165030800}-->script>
在生成的页面有这么一句,此处及包含整个页面生成的所有脚本,而脚本是通过一个Servlet输出的。到此从页面书写 一个JSF组件标签到最终生成页面的过程及完成!
包装的效果可以查看本版块帖子:http://www.iteye.com/topic/548626