JSF2.0实战 - 10、自定义Ajax更新方式

前面实现了简单的组件开发,但是组件对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}" />

JSF2.0实战 - 10、自定义Ajax更新方式_第1张图片

目标效果是,在左边的textBox中输入文字,点击按钮后文字显示在右边的textBox中,但是实际上效果和预计的有差距

JSF2.0实战 - 10、自定义Ajax更新方式_第2张图片

这是因为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);
}

这种方式的缺陷是把组件的ID赋给了纯粹为了ajax更新才产生的<span>标签,而页面上真正的控件却被迫用另外的ID,当需要用javascript控制真正的控件时因为ID的原因容易混淆出错。另一种方案不会有这种缺陷,就是替换jsf.js文件。

为了不对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;

还要在filter中做修改,当系统访问到jsf.js时,返回我们修改过的jsf.js文件内容,而不是JSF2.0原来的jsf.js文件。

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);
}

TextBoxRenderer.java

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");
}

下载代码

你可能感兴趣的:(框架,前端,JSF,dojo,JSF2.0)