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%>