前面实现了简单的组件开发,但是组件对ajax支持的不够好,看这个代码
<d4j:textBox binding="#{textBoxTest2.textBox}" /> <d4j:button value="Click Me" actionListener="#{textBoxTest2.click}"> <f:ajax execute="@form" render="textBox2" /> </d4j:button> <d4j:textBox id="textBox2" binding="#{textBoxTest2.textBox2}" />
目标效果是,在左边的textBox中输入文字,点击按钮后文字显示在右边的textBox中,但是实际上效果和预计的有差距
这是因为JSF的ajax更新只能替换目标ID的HTML元素,这种方式对基本的HTML控件有效果,但是我们的textBox是用js渲染出来的,所以必须用js代码来控制页面更新。
有两种方案解决,一种是将组件的ID渲染到一个<span>的标签上,<span>内再包含<script>,这样更新时替换<span>块,也就替换了<script>块,便可以执行所需的js代码。第二种是替换jsf.js。修改里面处理ajax更新的代码。
第一种方案,修改TextBoxRenderer.java
package org.dojo4j.component.form; import java.io.IOException; import javax.faces.application.ResourceDependencies; import javax.faces.application.ResourceDependency; import javax.faces.component.UIComponent; import javax.faces.context.FacesContext; import javax.faces.context.ResponseWriter; import javax.faces.render.FacesRenderer; import com.sun.faces.renderkit.Attribute; import com.sun.faces.renderkit.AttributeManager; import com.sun.faces.renderkit.RenderKitUtils; import com.sun.faces.renderkit.html_basic.TextRenderer; @FacesRenderer(componentFamily = TextBox.COMPONENT_FAMILY, rendererType = TextBox.COMPONENT_TYPE) @ResourceDependencies({ @ResourceDependency(name = "dijit/form/TextBox.js", target = "head")}) // 直接继承com.sun.faces.renderkit.html_basic.TextRenderer,修改部分渲染代码 public class TextBoxRenderer extends TextRenderer { @Override protected void getEndTextToRender(FacesContext context, UIComponent component, String currentValue) throws IOException { ResponseWriter writer = context.getResponseWriter(); assert (writer != null); //!context.getPartialViewContext().isPartialRequest()是非ajax方式渲染,说明组件在页面首次打开时渲染 if (!context.getPartialViewContext().isPartialRequest()) { String styleClass = (String) component.getAttributes().get("styleClass"); writer.startElement("input", component); //注释掉这一句,否则会给input设置ID属性 // writeIdAttributeIfNecessary(context, writer, component); writer.writeAttribute("type", "text", null); String clientId = component.getClientId(context); writer.writeAttribute("name", clientId, "clientId"); // 加入id label data-dojo-id data-dojo-type属性 writer.writeAttribute("id", clientId + "_obj", null);//修改ID,与script块的ID区分开 writer.writeAttribute("data-dojo-id", clientId + "_obj", null); writer.writeAttribute("data-dojo-type", "dijit/form/TextBox", null); if ("off".equals(component.getAttributes().get("autocomplete"))) { writer.writeAttribute("autocomplete", "off", "autocomplete"); } if (currentValue != null) { writer.writeAttribute("value", currentValue, "value"); } if (null != styleClass) { writer.writeAttribute("class", styleClass, "styleClass"); } RenderKitUtils.renderPassThruAttributes(context, writer, component, INPUT_ATTRIBUTES, getNonOnChangeBehaviors(component)); RenderKitUtils.renderXHTMLStyleBooleanAttributes(writer, component); RenderKitUtils.renderOnchange(context, component, false); writer.endElement("input"); //生成一个span,专门用来做ajax更新用,把组件的ID赋给span块 writer.startElement("span", component); writer.writeAttribute("id", clientId, null); writer.writeAttribute("style", "display:none;", null); writer.endElement("span"); } else { //ajax响应时返回的代码,jsf会替换目标ID的HTML元素,即上面生成的span元素 String clientId = component.getClientId(context); TextBox textBox = (TextBox) component; Object value = textBox.getValue(); writer.startElement("span", component); writer.writeAttribute("id", clientId, null); writer.writeAttribute("style", "display:none;", null); writer.startElement("script", component); writer.writeAttribute("type", "text/javascript", null); //用脚本变更dojo控件的属性值 //writer.write("dijit.registry.byId('"+clientId+"_obj').set('value', '" + (value == null ? "" : value) + "');"); writer.write("setValue(\"" + (value == null ? "" : value) + "\");"); writer.endElement("script"); writer.endElement("span"); } } // 以下是从com.sun.faces.renderkit.html_basic.TextRenderer复制的private代码 private static final Attribute[] INPUT_ATTRIBUTES = AttributeManager.getAttributes(AttributeManager.Key.INPUTTEXT); }
为了不对JSF原有的jsf.js文件造成侵入,我复制一份jsf.js文件放在自己的项目中,进行修改,并保证修改结果既能满足我们的需要,又不影响原有的功能。目前JSF2.0的ajax仅实现了update,其余的delete、insert、attributes、eval、extension虽然在jsf.js中实现了,但在后面java中没有实现,也不知道以后会不会实现,因此目前只能修改update中的代码,判断更新的组件是否是我们自定义的组件,如果是,则直接运行ajax返回的结果,如果不是,则按原来的代码继续执行。
src/META-INF/resources/javax.faces/dojo4j-jsf-uncompressed.js
...... } else if (id === "javax.faces.ViewHead") { throw new Error("javax.faces.ViewHead not supported - browsers cannot reliably replace the head's contents"); } else { if (window.dijit && dijit.registry && dijit.registry.byId) { var d = dijit.registry.byId(id); if (d) { scripts = stripScripts(src); runScripts(scripts); return; } } var d = $(id); if (!d) { throw new Error("During update: " + id + " not found"); } ...... jsf.separatorchar = ':'; jsf.specversion = 22000;
DojoFilter.java
if (uri.contains(ResourceHandler.RESOURCE_IDENTIFIER)) { // 替换jsf.js String jsfjs = null; if (uri.indexOf(ResourceHandler.RESOURCE_IDENTIFIER + "/jsf.js") > -1) { jsfjs = ResourceHandler.RESOURCE_IDENTIFIER + "/dojo4j-jsf" + ("Development".equals(request.getParameter("stage")) ? "-uncompressed.js" : ".js"); } String facesServletMapping = getFacesMapping(request); // 只处理facesServletMapping为.xxx后缀的情况,不处理facesServletMapping为/xxx为前缀的情况 if (!Util.isPrefixMapped(facesServletMapping)) { // 只有从引用资源的页面URL中才能取得Faces Servlet的后缀 String referer = request.getHeader("Referer"); if (referer != null && referer.trim().length() > 0) { if (referer.indexOf('?') > -1) { referer = referer.substring(0, referer.indexOf('?')); } if (referer.indexOf('.') > -1) { String subfix = referer.substring(referer.lastIndexOf('.')); if (jsfjs == null) { // 给没有Faces Servlet的后缀的资源加上后缀,确保通过JSF框架来解析资源文件 if (!facesServletMapping.equals(subfix)) { String url = request.getServletPath() + subfix; // System.out.println(url); request.getRequestDispatcher(url).forward(request, response); return; } } else { jsfjs = jsfjs + subfix; } } } } else { if (jsfjs != null) { jsfjs = facesServletMapping + jsfjs; } } if (jsfjs != null) { request.getRequestDispatcher(jsfjs).forward(request, response); return; } } chain.doFilter(req, resp); }
if (!context.getPartialViewContext().isPartialRequest()) { String styleClass = (String) component.getAttributes().get("styleClass"); writer.startElement("input", component); ...... writer.endElement("input"); } else { //ajax更新时输出<script type="text/javascript">......</script>代码块,页面会执行块中的javascript代码 String clientId = component.getClientId(context); TextBox textBox = (TextBox) component; Object value = textBox.getValue(); writer.startElement("script", component); writer.writeAttribute("type", "text/javascript", null); writer.write("dijit.registry.byId(\"" + clientId + "\").set(\"value\", " + (value == null ? "null" : "\"" + value + "\"") + ");"); writer.endElement("script"); }