
功能实现方法是,在每个子系统登录完成之后,调用获取系统列表的接口,用js渲染一个下拉菜单出来,该接口返回的格式为:
data: [ {"sortOrder": 1,"isCurrent": true, "systemHttpUrl": "http://xxxx:8080/permission","systemName": "统一权限管理系统" }, {"sortOrder": 2,"isCurrent": false, "systemHttpUrl": "http://xxxx:8080/systemA","systemName": "业务系统A" }, {"sortOrder": 3,"isCurrent": false, "systemHttpUrl": "http://xxxx:8080/systemB","systemName": "业务系统B" }]如果我们不采用模板引擎,那么传统的方式去解析这个数据并把它转变成html串的方法通常是:function data2Html(data) { data = data || []; var html = ["<ul class="nav navbar-nav navbar-left nav-system">", "<li class="dropdown">", "<a href="javascript:;" class="dropdown-toggle" data-toggle="dropdown" title="切换系统">"],l = data.length; if(l < 2) {l == 1 && html.push(data[0].systemName || "");html.push("</a></li></ul>");return html.join(""); } var curSysAry = data.filter(function(s){ return s.isCurrent; }); html.push(curSysAry[0].systemName + " <i class="fa fa-caret-down"></i></a><ul class="dropdown-menu">"); data.sort(function(a, b){ return a.sortOrder - b.sortOrder;}); for(var i = 0; i < l; i++) {i && html.push("<li role="separator" class="divider"></li>");html.push("<li><a href="" + data[i].systemHttpUrl + "" target="_self">" + data[i].systemName + "</a></li>"); } html.push("</ul></li></ul>"); return html.join("");}这种拼接字符串的方式有诸多弊端://通过一些根据属性名称对应的标记定义模板var _template = ["<ul class="nav navbar-nav navbar-left nav-system">"," <li class="dropdown">"," <a href="javascript:;" class="dropdown-toggle" data-toggle="dropdown" title="切换系统">","{{curSystemName}} {{#multiple}}<i class="fa fa-caret-down"></i>{{/multiple}}"," </a>"," {{#multiple}}<ul class="dropdown-menu">","{{#systems}}"," {{^first}}<li role="separator" class="divider"></li>{{/first}}"," <li>","<a href="{{{systemHttpUrl}}}" target="_self">{{systemName}}</a>"," </li>","{{/systems}}"," </ul>{{/multiple}}"," </li>","</ul>" ].join("");//初始化这个模板Mustache.parse(_template);function data2Html(data) { data = data || []; var curSysAry = data.filter(function(s){ return s.isCurrent; }); data.sort(function(a, b){ return a.sortOrder - b.sortOrder;}); data = data.map(function(s, i){s.first = i == 0; return s}); //模板渲染成字符串 return Mustache.render(_template, {curSystemName: curSysAry.length ? curSysAry[0].systemName : "",multiple: !!data.length,systems: data });}对比两个代码,会发现后面的代码,相对于前面的有以下这些优点:<script id="tpl" type="text/html"> Hello {{name}}!</script>然后在编译模板之前,通过获取tpl的innerHTML定义原始模板串:var tpl = document.getElementById("tpl").innerHTML.trim();具体要用哪种方式来定义模板,可以参考下面的建议:
Mustache.parse(tpl);要注意的是,经过预编译之后的tpl已经不再是原来的模板串了,连数据类型都变成数组类型了,这都是预编译的结果。
var htmlAfterRendered = Mustache.render(tpl1, obj);obj引用的是一个数据源对象,mustache会把模板中那些属性标签,根据约定的规则,替换成对象的内容。htmlAfterRendered就是替换之后的字符串,你可以用它完成你需要的DOM操作。
<script id="tpl1" type="text/html"> -{{prop}}-</script><script> var tpl1 = document.getElementById("tpl1").innerHTML.trim(); Mustache.parse(tpl1); //测试falsy值 console.log(Mustache.render(tpl1, {prop: ""}));//-- console.log(Mustache.render(tpl1, {prop: 0}));//-0- console.log(Mustache.render(tpl1, {prop: null}));//-- console.log(Mustache.render(tpl1, {prop: undefined}));//-- console.log(Mustache.render(tpl1, {prop: false}));//-false- console.log(Mustache.render(tpl1, {prop: NaN}));//-NaN- //测试简单对象 console.log(Mustache.render(tpl1, {prop: {name: "jason"}}));//-[object Object]- //测试数组 console.log(Mustache.render(tpl1, {prop: [{name: "jason"}, {name: "frank"}]}));//-[object Object],[object Object]- //测试日期对象 console.log(Mustache.render(tpl1, {prop: new Date()}));//-Mon Jan 18 2016 15:38:46 GMT+0800 (中国标准时间)- //测试自定义toString的简单对象 var obj1 = {name: "jason"}; obj1.toString = function () {return this.name; }; console.log(Mustache.render(tpl1, {prop: obj1}));//-jason- //测试boolean number string console.log(Mustache.render(tpl1, {prop: true}));//-true- console.log(Mustache.render(tpl1, {prop: 1.2}));//-1.2- console.log(Mustache.render(tpl1, {prop: "yes"}));//-yes- //测试function console.log(Mustache.render(tpl1, {prop: function () {} }));//-- console.log(Mustache.render(tpl1, {prop: function () { return "it"s a fun"} }));//-it's a fun- console.log(Mustache.render(tpl1, {prop: function () { return false;} }));//-false- console.log(Mustache.render(tpl1, {prop: function(){ return function (text, render) {return "<b>" + render(text) + "</b>" };} })); //-function (text, render) { // return "<b>" + render(text) + "</b>" //}-</script>mustache渲染{{prop}}标签的逻辑是:console.log(Mustache.render(tpl1, {prop: function () { return "it"s a fun"}}));//-it's a fun-如果要阻止这种编码行为,只要把标签形式改成{{{prop}}}就可以了:<script id="tpl1" type="text/html"> -{{{prop}}}-</script>console.log(Mustache.render(tpl1, { prop: function () {return "it"s a fun" }}));//-it"s a fun-5. {{#prop}}{{/prop}}标签<script id="tpl2" type="text/html"> -{{#prop}}content{{/prop}}-</script><script> var tpl2 = document.getElementById("tpl2").innerHTML.trim(); Mustache.parse(tpl2); //测试falsy值 console.log(Mustache.render(tpl2, {prop: ""}));//-- console.log(Mustache.render(tpl2, {prop: 0}));//-- console.log(Mustache.render(tpl2, {prop: null}));//-- console.log(Mustache.render(tpl2, {prop: undefined}));//-- console.log(Mustache.render(tpl2, {prop: false}));//-- console.log(Mustache.render(tpl2, {prop: NaN}));//-- //测试空数组 console.log(Mustache.render(tpl2, {prop: []}));//-- //测试不存在的属性 console.log(Mustache.render(tpl2, {prop2: true}));//-- //测试function console.log(Mustache.render(tpl2, {prop: function () {} }));//-- console.log(Mustache.render(tpl2, {prop: function () { return false;} }));//-- console.log(Mustache.render(tpl2, {prop: function() { return [];} }));//--//测试简单对象 console.log(Mustache.render(tpl2, {prop: {name: "jason"}}));//-content- //测试日期对象 console.log(Mustache.render(tpl2, {prop: new Date()}));//-content- //测试boolean number string console.log(Mustache.render(tpl2, {prop: true}));//-content- console.log(Mustache.render(tpl2, {prop: 1.2}));//-content- console.log(Mustache.render(tpl2, {prop: "yes"}));//-content- //测试返回非falsy,非空数组的function console.log(Mustache.render(tpl2, {prop: function () { return "it"s a fun"} }));//-content-</script>以上用例中特殊点的就是prop属性引用的是一个函数的时候,{{#prop}}会自动调用这个函数,并把函数的返回值作为if-else渲染逻辑的判断依据,也就是说如果这个函数返回的是falsy值或者是空数组的时候,那么这对标签之间的内容还是不会显示。<script id="tpl2" type="text/html"> -{{#prop}}{{name}},{{/prop}}-</script><script>var tpl2 = document.getElementById("tpl2").innerHTML.trim();Mustache.parse(tpl2);console.log(Mustache.render(tpl2, {prop: [{name: "jason"}, {name: "frank"}]}));//-jason,frank,-</script>从这个测试结果中可以看到,{{#prop}}{{/prop}}之间的模板内容根据prop所引用的数组迭代了两次,并且在这对标签内部直接通过{{name}}标签,输出了数组元素对象上的name属性对应的值。<script id="tpl2" type="text/html"> -{{#prop}}{{name}},{{/prop}}-</script><script>var tpl2 = document.getElementById("tpl2").innerHTML.trim();Mustache.parse(tpl2);console.log(Mustache.render(tpl2, { prop: function(){return [{name: "jason"}, {name: "frank"}]; }}));//-jason,frank,-</script><script id="tpl2" type="text/html"> -{{#prop}}content{{/prop}}-</script><script>var tpl2 = document.getElementById("tpl2").innerHTML.trim();Mustache.parse(tpl2);console.log(Mustache.render(tpl2, {prop: function(){ return function (text, render) {return "<b>" + render(text) + "</b>" };} }));//-<b>content</b>-</script>6. {{^prop}}{{/prop}}标签<script id="tpl2" type="text/html"> -{{^prop}}content{{/prop}}-</script><script> var tpl2 = document.getElementById("tpl2").innerHTML.trim(); Mustache.parse(tpl2); //测试falsy值 console.log(Mustache.render(tpl2, {prop: ""}));//-content- console.log(Mustache.render(tpl2, {prop: 0}));//-content- console.log(Mustache.render(tpl2, {prop: null}));//-content- console.log(Mustache.render(tpl2, {prop: undefined}));//-content- console.log(Mustache.render(tpl2, {prop: false}));//-content- console.log(Mustache.render(tpl2, {prop: NaN}));//-content- // 测试空数组 console.log(Mustache.render(tpl2, {prop: []}));//-content- // 测试不存在的属性 console.log(Mustache.render(tpl2, {prop2: true}));//-content- //测试function console.log(Mustache.render(tpl2, {prop: function () {} }));//-content- console.log(Mustache.render(tpl2, {prop: function () { return false;} }));//-content- console.log(Mustache.render(tpl2, {prop: function () { return [];} }));//-content- //测试简单对象 console.log(Mustache.render(tpl2, {prop: {name: "jason"}}));//-- //测试日期对象 console.log(Mustache.render(tpl2, {prop: new Date()}));//-- // 测试非空数组 console.log(Mustache.render(tpl2, {prop: [{name: "jason"},{name: "tom"}]}));//-- //测试boolean number string console.log(Mustache.render(tpl2, {prop: true}));//-- console.log(Mustache.render(tpl2, {prop: 1.2}));//-- console.log(Mustache.render(tpl2, {prop: "yes"}));//--//测试返回非falsy,非空数组的function console.log(Mustache.render(tpl2, {prop: function () { return "it"s a fun"} }));//-- //测试返回function的function console.log(Mustache.render(tpl2, {prop: function () { return function(text,render){return "<b>" + render(text) +"</b>" }} }));//--</script>7. 渲染上下文<script id="tpl2" type="text/html"> -{{#person}}{{#student}}{{#address}}address: {{home}},age: {{age}}{{/address}}{{/student}}{{/person}}-</script><script> var tpl2 = document.getElementById("tpl2").innerHTML.trim(); var obj2 = {age: 20,person: { student: {address: { home: "xxxxx"} }} }; console.log(Mustache.render(tpl2, obj2));//-address: xxxxx,age: 20-</script>上面这个例子中,在渲染{{#address}}{{/address}}时,上下文对象已经变成了obj2.person.student.address所引用的对象,所以{{home}}渲染时用到的就是obj2.person.student.address.home属性,而{{age}}渲染时,由于obj2.person.student.address不存在age属性,所以就会到上层上下文中查找,一直到obj2对象才找到,于是就把obj2.age当成了渲染结果。<script id="tpl2" type="text/html"> -address: {{person.student.address.home}},age: {{age}}-</script><script> var tpl2 = document.getElementById("tpl2").innerHTML.trim(); var obj2 = {age: 20,person: { student: {address: { home: "xxxxx"} }} }; console.log(Mustache.render(tpl2, obj2));//-address: xxxxx,age: 20-</script>这种方法其实很好理解,只要知道当前的上下文对象,再根据属性操作串person.student.address.home,当然就能找到需要的值了。