在高手林立的javaeye舞台上,ajax实在是不值得提及的一个小东西,而现在旧事重提,就是想提提另一种ajax思维。

 

我们知道在ajax应用中,概括起来就是客户端一个异步请求,服务器端返回请求的数据,然后通过js和css更新页面显示的内容,在这个简单的过程中,略显麻烦的往往是返回的数据内容在页面的解析问题。如果只是简单的数字或string,我们用字符串返回就可以了,但当遇到返回的数据结构比较复杂时,使用字符串返回就会使得代码丑陋,难以维护,若使用xml或json格式返回会稍好一些,但这样的数据结构仍然在前端需要一定的解析处理,我觉得有时候我们把服务器端的数据直接构造好再写回到客户端会更加地简单和方便。

 

这种思路的具体做法就是,使用freemarker模板技术,把ajax请求的数据通过模板渲染好,然后将渲染好的模板数据写回到客户端,这样一来,不管你ajax请求的数据格式是何种数据结构,都交给freemarker来处理它,所以你ajax请求的是Map,List,Model,String等都没有问题,通过模板标签组织好数据,然后就当成字符串写回到客户端,对于客户端而言,我的每个ajax请求,你都是给我返回已经组织好数据结构的字符串,我不用做任何处理,就可以直接使用,这简化了前端的解析工作,相当于使用freemarker来屏蔽了这项麻烦的解析工作,而对于freemarker而言,它有很强的数据表现能力,可以循环迭代,逻辑判断等,所以数据的表现由freemarker来完成并不会很难。

 

我知道在很多项目中,常常会采用在java代码中拼凑html标签的方式将数据构造好,然后一并写向客户端,这种方式在一些开源项目中也是存在的,例如log4j,它在HTMLLayout中也是Java代码中嵌入html代码,只是这种方式不好,当html代码变更时,我们需要维护java代码,而通过模板技术,就只要维护模板就可以了。

 

然而这种思路其实也没有什么神奇的,会不会给你的开发带来便利,需要你酌情考虑,下面是这种方式一个具体使用的实例。

 

1.ajax请求处理的Action:

package com.javaeye.hnylj.action;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletResponse;

import org.apache.log4j.Logger;
import org.apache.struts2.interceptor.ServletResponseAware;

import com.javaeye.hnylj.constant.Constant;
import com.javaeye.hnylj.ftl.FreemarkerProcessor;
import com.javaeye.hnylj.model.ProductInfo;
import com.javaeye.hnylj.service.ProductService;
import com.opensymphony.xwork2.ActionSupport;

/**
 * ajax返回数据处理测试
 * 
 * @since Apr 13, 2010
 */
public class AjaxNewIdeaAction extends ActionSupport implements ServletResponseAware {

	private Logger logger = Logger.getLogger(AjaxNewIdeaAction.class);
	
	private static final long serialVersionUID = 3398440661649729663L;
	
	private HttpServletResponse response;
	
	private FreemarkerProcessor freemarkerProcessor;
	
	private ProductService productService;
	
	public void setServletResponse(HttpServletResponse response) {
		this.response = response;
	}

	public ProductService getProductService() {
		return productService;
	}

	public void setProductService(ProductService productService) {
		this.productService = productService;
	}
	
	/**
	 * ajax请求处理
	 * 
	 * @return
	 * @throws Exception
	 */
	public String ajaxRequestProductList() throws Exception {
		//获取product信息,productService由spring注入
		List<ProductInfo> productInfoList = productService.queryProductByProductId();
		logger.info("product size: " + productInfoList.size());
		Map<Object, Object> productInfoMap = new HashMap<Object, Object>();
		productInfoMap.put("productInfo", productInfoList);
		freemarkerProcessor = new FreemarkerProcessor();
		//得到渲染好的模板内容
		String result = freemarkerProcessor.init(Constant.PRODUCTINFO_TEMPLATE, productInfoMap, Constant.TEMPLATE_RELATIVE_PATH);
		response.getWriter().write(result);
		productInfoMap.clear();
		logger.info("product info ajax request successfully");
		return null;
	}
}

 

2.freemarker处理类:

package com.javaeye.hnylj.ftl;

import java.io.IOException;
import java.io.StringWriter;
import java.util.Locale;
import java.util.Map;

import org.apache.struts2.ServletActionContext;

import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;

/**
 * Freemarker处理类
 * 
 * @since Apr 13, 2010
 */
public class FreemarkerProcessor {
	
	/**
	 * 初始化模板引擎
	 * 
	 * @param ftl 模板名称
	 * @param map 模板中需要的参数集合
	 * @param relativePath 模板相对于根路径的相对路径
	 * @throws IOException
	 * @throws TemplateException
	 */
	public String init(String ftl, Map<Object,Object> map, String relativePath) throws IOException, TemplateException {
		Configuration freemarkerCfg = new Configuration();
	    freemarkerCfg.setServletContextForTemplateLoading(ServletActionContext.getServletContext(), relativePath);
	    freemarkerCfg.setEncoding(Locale.getDefault(), "UTF-8");
	    Template template = freemarkerCfg.getTemplate(ftl);
	    template.setEncoding("UTF-8");
	    
	    StringWriter result = new StringWriter();
	    template.process(map, result);
	    return result.toString();
	}
}

 

3.页面使用: 

<%@ page language="java" pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
	<title>ajax</title>
	<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
	<meta http-equiv="pragma" content="no-cache"/>
	<meta http-equiv="cache-control" content="no-cache"/>
	<meta http-equiv="expires" content="0"/>    
	<script language="JavaScript" src="${pageContext.request.contextPath}/js/jquery.js"></script>
	<script language="JavaScript" src="${pageContext.request.contextPath}/js/product.js"></script>
  </head>
  
  <body>
    <div id="main_content">
	  <div class="tab_1"><a href="/index.html">首页</a></div>
	  <div class="tab_2"><a href="javascript:showProduct('flag')">产品列表</a></div>
	  <div id="flag" class="item"></div>
    </div>
  </body>
</html>

当我们点击产品列表就会发起ajax请求。

 

4.product.js内容:

function showProduct(obj) {
	$.ajax({
		url:"/product/ajaxRequestProductList.action",
		type: "POST",
		cache: false,
		success: function(html) {
		   document.getElementById(obj).innerHTML=html;
		},
	    error: function(){
	        document.getElementById(obj).innerHTML="网络连接超时,无法显示数据!";
	        return;
	    }
	});
}

 

5.productInfo.ftl模板内容:

<div class="content">
	<#if productInfo?size != 0> 
	<#list productInfo as productInfo>
	<div class="preview">
	  <div class="preview_detail">
	  	${productInfo.productName}
	  </div>
	  <div class="preview_detail">
	  	${productInfo.productPrice}
	  </div>
	  <div class="preview_detail">
		<a href="/product/productDetail.action?productId=${productInfo.productId}">
		 ${productInfo.productPrice}
		</a>
	  </div>
	</div>
	</#list>
	<#else>    
	<div class="preview">
	  抱歉,没有找到相关信息!
	</div>
	</#if>
</div>

 

主要代码如上,省去了一些其他的代码。

在具体项目中,并不是每一个ajax都采用这种方式,推荐那些返回的数据结构比较复杂时采用这种方式,例如一个ajax的分页列表,一个ajax动态菜单树就可以使用它。另外,这种方式会不会带来性能的问题,我觉得不需要太担忧,因为它最多就是多使用了一点内存。