Node.js的Web模板引擎ejs的入门使用教程
Node开源模板的选择很多,但推荐像我这样的老人去用EJS,有ClassicASP/PHP/JSP的经验用起EJS来的确可以很自然,也就是说,你能够在<%...%>块中安排JavaScript代码,利用最传统的方式<%=输出变量%>(另外<%-输出变量是不会对&等符号进行转义的)。安装EJS命令如下:
npminstallejs
JS调用
JS调用的方法主要有两个:
ejs.compile(str,options); //=>Function ejs.render(str,options); //=>str
实际上EJS可以游离于Express独立使用的,例如:
varejs=require(''),str=require('fs').readFileSync(__dirname+'/list.ejs','utf8');
varret=ejs.render(str,{
names:['foo','bar','baz']
});
console.log(ret);
见ejs.render(),第一个参数是模板的字符串,模板如下。
<%if(names.length){%>
<ul>
<%names.forEach(function(name){%>
<lifoo='<%=name+"'"%>'><%=name%></li>
<%})%>
</ul>
<%}%>
names成了本地变量。
选项参数
第二个参数是数据,一般是一个对象。而这个对象又可以视作为选项,也就是说数据和选择都在同一个对象身上。
如果不想每次都都磁盘,可需要缓存模板,设定options.filename 即可。例如:
varejs=require('../')
,fs=require('fs')
,path=__dirname+'/functions.ejs'
,str=fs.readFileSync(path,'utf8');
varusers=[];
users.push({name:'Tobi',age:2,species:'ferret'})
users.push({name:'Loki',age:2,species:'ferret'})
users.push({name:'Jane',age:6,species:'ferret'})
varret=ejs.render(str,{
users:users,
filename:path
});
console.log(ret);
inculde指令
而且,如果要如
<ul>
<%users.forEach(function(user){%>
<%includeuser/show%>
<%})%>
</ul>
般插入公共模板,也就是引入文件,必须要设置filename选项才能启动include特性,不然include无从知晓所在目录。
模板:
<h1>Users</h1>
<%functionuser(user){%>
<li><strong><%=user.name%></strong>isa<%=user.age%>yearold<%=user.species%>.</li>
<%}%>
<ul>
<%users.map(user)%>
</ul>
EJS支持编译模板。经过模板编译后就没有IO操作,会非常快,而且可以公用本地变量。下面例子user/show忽略ejs扩展名:
<ul>
<%users.forEach(function(user){%>
<%includeuser/show%>
<%})%>
</ul>
自定义CLOSETOKEN
如果打算使用<h1>{{=title}}</h1>般非<%%>标识,也可以自定义的。
varejs=require('ejs');
ejs.open='{{';
ejs.close='}}';
格式化输出也可以哦。
ejs.filters.last=function(obj){
returnobj[obj.length-1];
};
调用:<p><%=:users|last%></p>EJS也支持浏览器环境。
<html>
<head>
<scriptsrc="../ejs.js"></script>
<scriptid="users"type="text/template">
<%if(names.length){%>
<ul>
<%names.forEach(function(name){%>
<li><%=name%></li>
<%})%>
</ul>
<%}%>
</script>
<script>
onload=function(){
varusers=document.getElementById('users').innerHTML;
varnames=['loki','tobi','jane'];
varhtml=ejs.render(users,{names:names});
document.body.innerHTML=html;
}
</script>
</head>
<body>
</body>
</html>
不知道EJS能否输出多层JSON对象呢?
对了,有网友爆料说,jQ大神John若干年前写过20行的模板,汗颜,与EJS相似但短小精悍!
简单实用的js模板引擎
不足50行的js模板引擎,支持各种js语法:
<scriptid="test_list"type="text/html">
<%=
for(vari=0,l=p.list.length;i<l;i++){
varstu=p.list[i];
=%>
<tr>
<td<%=if(i==0){=%>class="first"<%=}=%>><%==stu.name=%></td>
<td><%==stu.age=%></td>
<td><%==(stu.address||'')=%></td>
<tr>
<%=
}
=%>
</script>
“<%=xxx=%>”内是js逻辑代码,“<%==xxx=%>”内是直接输出的变量,类似php的echo的作用。“p”是调用下面build方法时的k-v对象参数,也可以在调用“newJTemp”时设置成别的参数名
调用:
$(function(){
vartemp=newJTemp('test_list'),
html=temp.build(
{list:[
{name:'张三',age:13,address:'北京'},
{name:'李四',age:17,address:'天津'},
{name:'王五',age:13}
]});
$('table').html(html);
});
上面的temp生成以后,可以多次调用build方法,生成html。以下是模板引擎的代码:
varJTemp=function(){
functionTemp(htmlId,p){
p=p||{};//配置信息,大部分情况可以缺省
this.htmlId=htmlId;
this.fun;
this.oName=p.oName||'p';
this.TEMP_S=p.tempS||'<%=';
this.TEMP_E=p.tempE||'=%>';
this.getFun();
}
Temp.prototype={
getFun:function(){
var_=this,
str=$('#'+_.htmlId).html();
if(!str)_.err('error:notemp!!');
varstr_='var'+_.oName+'=this,f=\'\';',
s=str.indexOf(_.TEMP_S),
e=-1,
p,
sl=_.TEMP_S.length,
el=_.TEMP_E.length;
for(;s>=0;){
e=str.indexOf(_.TEMP_E);
if(e<s)alert(':(ERROR!!');
str_+='f+=\''+str.substring(0,s)+'\';';
p=_.trim(str.substring(s+sl,e));
if(p.indexOf('=')!==0){//js语句
str_+=p;
}else{//普通语句
str_+='f+='+p.substring(1)+';';
}
str=str.substring(e+el);
s=str.indexOf(_.TEMP_S);
}
str_+='f+=\''+str+'\';';
str_=str_.replace(/\n/g,'');//处理换行
varfs=str_+'returnf;';
this.fun=Function(fs);
},
build:function(p){
returnthis.fun.call(p);
},
err:function(s){
alert(s);
},
trim:function(s){
returns.trim?s.trim():s.replace(/(^\s*)|(\s*$)/g,"");
}
};
returnTemp;
}();
核心是将模板代码转变成了一个拼接字符串的function,每次拿数据call这个function。
因为主要是给手机(webkit)用的,所以没有考虑字符串拼接的效率问题,如果需要给IE使用,最好将字符串拼接方法改为Array.push()的形式。
ejs模板布局layout
1.如果不愿意使用默认的layout.ejs,可自行指定。例如:
res.render("index",{"title":"test","layout":"main"});
//或
res.render("index",{"title":"test","layout":"main.ejs"});
2.如果不愿意使用layout,则可以设置layout为false,例如:
res.render("index",{"layout":false});
3.如果不想每个请求都单独设置一次。可以使用全局设置:
app.set("viewoptions",{
"layout":false
});
4.ejs里,默认的闭合标记是<% ..%>,我们也可以定义自己的标签。例如:
app.set("viewoptions",{
"open":"{{",
"close":"}}"
});
5.局部布局
在web应用中,经常会需要重复显示某个内容,例如:用户评论功能,需要重复显示出每一条用户的评论,这个时候,我们可以通过循环来实现。但是也可以使用【局部模版】(partial)来实现。例如:
首先我们建一个局部的模版./views/comment.ejs:
<divclass="comment_item"> <divclass="comment_user"><%=comment.user%></div> <divclass="comment_content"><%=comment.content%></div> </div>
注意:这里是comment.xxxx
然后在./views/index.ejs中,通过partial调用comment
thisis<%=title%>!
<br/>
<%-partial("comment",comments)%>
注意:这里是partial("comment.ejs",comments);<--单词要用复数。
最后是在router中,调用index.ejs。
app.get("/",function(req,res){
res.render("index",{"title":"test","layout":false,"comments":[
{"user":"gainover","content":"test1"},
{"user":"zongzi","content":"test2"},
{"user":"maomao","content":"test3"}
]});
});
注意:代码里的comments和index.ejs的comments变量名称一致,而partial所调用的comment.ejs中,则采用comment的单数形式。
在列表显示时,我们通常会遇到的场景是,对第一个元素或者最后一个元素加以特殊显示。在partial中,我们可以通过express内置的变量来判断当前对象是否是第一个元素或者最后一个元素,例如:
<divclass="comment_item<%if(firstInCollection){%>firtitem<%}%>">
<divclass="comment_user"><%=comment.user%></div>:
<divclass="comment_content"><%=comment.content%></div>
</div>
这样第一条评论的class里就会多一个firstitem。
类似的内置变量还有:
(1)firstInCollection如果是数组的第一个元素,则为true
(2)indexInCollection当前元素在数组里的索引
(3)lastInCollection如果是数组的最后一个元素,则为true
(4)collectionLength数组的长度
最后是partial调用模版时的路径查找问题:
(1)partial("edit")会查找同目录下的edit.ejs文件。
(2)partial("../message")会查找上一级目录的message.ejs文件。
(3)partial("users")会查找users.ejs文件,如果不存在users.ejs,则会查找/users/index.ejs文件。
(4)<%=users%>会对内容进行转义,想不转义,可以用<%-users%>