在这篇文章(不敢妄称教程,最多称之为学习笔记)里,我会从头开始实现客户端模板的效果。不过你不要期望能够在这里找到可以直接拿去使用直接复用灵活度很高的代码,因为我只是在叙述如何制造一样武器,叙述制造的过程,而实际的生产,请自行完成。
请注意:我的代码并不是很健壮的,我忽略了实际工作中可能碰到的很多可能性,这也不是我实际工作中真正应用的代码,我进行了很大程度的简化,排除了那些和客户端模板直接关系并不是很大的内容(但是你在自己用的时候却很可能必须加上!),目的是为了让大家把注意力集中到模板上。
我这里列几个被我排除的功能点吧:innerHTML的标签补完、数据的编码、防止数据过多造成浏览器假死……
在文章后面的评论中,很多朋友都推荐了TrimPath的JavascriptTemplate,恕我无知,这似乎是我第一次看到它,不过它的实现确实比我的文章中所及要复杂得多,它的考虑范围也比我广。对客户端模板有兴趣的同学们可以去研究一下它的源码,希望我的文章对于大家在看它源码时有一点点的作用(如果有作用我会觉得万分荣幸……)
让我们从头开始
模板这个概念我想大家都很熟悉了吧,就好像dw里的模板、php里的模板、word里的模板……这里就不多解释了。我们来说说我们用模板的目的。
一般来说,我们应用模板的原因都不外乎这两点:我不想老是做重复的劳动(如dw和word的模板),我想把表现和数据分开,因为我是程序员,我不懂界面的事或者我希望能够很方便地修改(如php的模板或jsp的taglib)。而我现在要说的,就是要做第二点:分离表现和数据,让html做它该做的事情,而其它的,交给模板来做吧。
呵呵,其实这两个理由是有相当一部分重复的,因为避免做重复的劳动,实际上就是将需要重复做的事情抽取出来做成模板,而对于网站、对于程序来说,这块重复的工作就是表现!而我一开始考虑客户端模板的原因也是因为我不想做三个页面,而这三个页面的修改频率也许会非常高
那模板应该用在哪里呢?到处都用吗?错!客户端模板的技术其实现在应该算相当成熟了,我在不少地方都看见过类似的实现,而目前最有名的就是q-zone了,你在q-zone的某处查看源代码,你会发现一大堆类似[%或<%或{%的标记,有时候会觉得自己是不是眼花,居然在客户端看见asp的代码?下面就是q-zone的典型代码:
<li><a href="javascript:checkGoCmtPriv('[%=@albumid%]','[%=@lloccode%]')" title="[%=@content%] 由 [%=@nick[('__VAR__').unHtmlReplace()]%] 于[%=@datetime%]发表 " class="mode_photo_a">[%=@content[procContent('__VAR__')]%]</a><img class="author-display" style="cursor:hand" onclick="DelRecentCmt('[%=@albumid%]', '[%=@lloccode%]', '[%=@cmt_id%]')" src="http://imgcache.qq.com/qzone/client/photo/images/icon_del.gif" alt="删除该最新评论" /></li>
我为什么反对到处使用客户端模板呢?理由有二:
合理地使用一项技术,无论它多么时髦,多么酷,被多少专家目为不二选择,适合你才最重要!
我的想法是将客户端模板技术应用于那些使用ajax(或类似技术)来动态增改页面内容的地方,因为使用了ajax的技术的目的首要就是提高用户的体验,而不是搜索引擎的友好,比如留言的动态加载,比如评价的异步写入,比如rss内容的自动抓取(如果你告诉我你整个站都应用了ajax,你的整个站完全不需要刷新,你认为你给用户非常好的体验,请容许我bs你,谢谢)。对于这种需求,我们就可以放心大胆地使用客户端模板技术而不用担心我上面提及的两个理由(当然,性能还是得注意,别太大意了)。
第一个实例:简单的模板
q-zone的客户端模板技术应该是相当成熟的吧,有兴趣的筒子们可以去看看它是如何实现的,而下面我来说一下我的客户端模板。
先声明一点:我使用的是json(在这里就不赘述json了,有兴趣的去看 http://json.org,在实例中我也许会再做进一步的说明),而不是xml来做数据源。主要原因是json对js来说非常好理解,而且我可以用多种方式来加载,不需要一定要用ajax。
现在让我们开始我们的历程吧
让我们来假想这么一个应用吧,我们现在要来做一个局部刷新的留言板。
既然只是一个demo,我就可以只实现客户端的功能,而不去管服务端是什么了,我唯一的要求就是服务端返回给我的数据是json格式的。
下面这是一条留言板中的数据结构:
id 序列号
name 留言者姓名
mail 信箱
title 标题
content 内容
time 留言时间
而这是对应这个结构的json数据形式:
{
id:1234,
name:'路人甲',
mail:'somebodyA@foo.com',
title:'测试一下这个留言板好用不',
content:'测试就是测试,不要问我为什么测试,也不要问我为了什么测试,反正我就是要测试,你能把我怎么着?',
time:'2007-03-21 12:33:43'
}
我们现在先用普通的方式来实现ajax留言板的数据获取吧:
demo1:
运行代码框
获取一个json或xml字符串,解析它们,将它们的内容塞到一个个html对象的innerHTML里。呵呵,是不是很眼熟?
或许你的写法和我不同,你也许写得比我好看,写得比我健全,你的代码里没硬编码一点html代码(我指的是那段<a href="..."></a>),那又如何?你还是写了一堆innerHTML~
能不能更简单一点呢?我想把页面结构从js中剔除出去,我不需要知道我要填充的是什么样的结构,那个结构由页面设计师帮我设计好了,我只需要把数据传到客户端,然后用某种方式塞到指定的结构里。这样会不会更好?
我试图使用客户端模板来实现这样的效果,下面就是改写后的实例:
demo 2:
运行代码框
看一下前后两个demo的区别:
demo1中的:
...
...
function getData(){
/*假设data就是用xmlhttp获取到的数据*/
var ph = document.getElementById('post_header');
var pt = document.getElementById('post_title');
var pc = document.getElementById('post_content');
ph.innerHTML = "("+data.id+")<a href='mailto:"+data.mail+"'>"+data.name+"</a>于"+data.time+"发表:";
pt.innerHTML = data.title;
pc.innerHTML = data.content;
}
...
...
<div id="post">
<fieldset>
<legend id="post_header">标题</legend>
<h4 id="post_title"></h4>
<p id="post_content"></p>
</fieldset>
</div>
...
...
demo2中的:
...
...
function getData(){
var p = document.getElementById('post');
var t = p.innerHTML;
t = t.replace(/<!--{(.*?)}-->/g,function(a,b){
var v = "";
try{
v = eval("data."+b);
}
catch(e){}
return v;
});
p.innerHTML = t;
}
...
...
<div id="post">
<fieldset>
<legend id="post_header">(<!--{id}-->)<a href='mailto:<!--{mail}-->'><!--{name}--></a>于<!--{time}-->发表:</legend>
<h4 id="post_title"><!--{title}--></h4>
<p id="post_content"><!--{content}--></p>
</fieldset>
...
...
看到区别了吗?我的模板实现和传统的ajax实现不同,我是将数据的结构从js中抽取出来,提前嵌入到html代码中(我用<!--{...}-->来代表模板信息),从而做到降低js代码中代码与页面结构的耦合。
看过这个demo,我们来具体说说客户端模板的实现原理吧:
是不是很简单呢?接下来,我们要说一些更复杂的应用。
第二个实例:升级版的模板
在看到第一个实例时,有些人可能就会说了,留言板的数据每页显示都不止一条吧?你的代码一次只能执行一条,那太不实用了吧。
呵呵,对于多条数据的我们可以采用for循环来实现,这是一个例子:
demo3:
运行代码框
问题是解决了……不过……如果我的数据是多层数组呢?再次修改代码?也许,我们该考虑把原来看起来只有简单替换的模板进行一些扩展了。
如何做呢?考虑到以后的扩展性,我决定采用松耦合的方式,写一个模板的主程序,然后在里面调用一堆堆的小函数……算了,作为程序员,还是用程序说话吧……
这是个和模板无关的demo,只是用来说明我的上面这句话:
demo4:
运行代码框
大致明白我说的意思了吧?