Maison > interface Web > js tutoriel > Explication détaillée des techniques du moteur de modèle Javascript moustache.js_javascript

Explication détaillée des techniques du moteur de modèle Javascript moustache.js_javascript

WBOY
Libérer: 2016-05-16 15:19:08
original
1086 Les gens l'ont consulté

Cet article résume ses méthodes d'utilisation et une certaine expérience d'utilisation. Le contenu n'est pas très avancé, il s'agit d'un contenu purement introductif, il suffit d'y jeter un œil. Cependant, si vous n'avez pas utilisé ce type de bibliothèque de moteur JavaScript, cet article vaut toujours la peine d'être lu. Je pense qu'après avoir compris ses fonctions puissantes et son utilisation simple, vous aurez hâte de l'utiliser dans votre travail.

1. Commençons par un besoin simple et réel
Actuellement, l'entreprise a construit une plate-forme de développement unifiée. Le backend encapsule l'interface MVC et l'interface pour l'ajout, la suppression, la modification et les requêtes de données. Sur le front-end, j'ai utilisé bootstrap pour écrire manuellement divers composants afin de créer un développement. Le framework CAS a été intégré. Sur la base de CAS, nous avons d'abord construit un système de gestion des autorités unifié. Ce système est le premier sous-système de notre plateforme de développement. Il est utilisé pour gérer et configurer les menus et les autorisations de tous les sous-systèmes et gérer la structure organisationnelle et. utilisateurs de l'ensemble de l'entreprise. Plus tard, nous avons développé successivement le système commercial A et le système commercial B. Puisque ces trois sous-systèmes correspondent à trois projets Java, lors du déploiement final, trois applications ont été déployées dans Tomcat. Il y a maintenant une exigence :

.
  • 1) Après vous être connecté à chaque système, cliquez sur le nom du système pour développer un menu déroulant qui affiche tous les sous-systèmes autorisés
  • 2) Ensuite, l'utilisateur peut basculer vers le système sélectionné en cliquant sur d'autres sous-systèmes. Après avoir atteint d'autres systèmes, puisque ce menu déroulant est créé, il peut également revenir de ce système ;
  • 3) Si l'utilisateur ne dispose que d'une seule autorisation système, le menu déroulant ne s'affichera pas.

Les exigences sont en fait assez simples. Le prototype ressemble probablement à ceci :

La fonction est implémentée en appelant l'interface pour obtenir la liste des systèmes après la connexion de chaque sous-système et en utilisant js pour afficher un menu déroulant. Le format renvoyé par l'interface est :

.
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"
 }
]
Copier après la connexion

Si nous n'utilisons pas de moteur de modèle, la manière traditionnelle d'analyser ces données et de les convertir en chaîne HTML est généralement :

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('');
}

Copier après la connexion

Cette méthode d'épissage des cordes présente de nombreux inconvénients :

  • 1) Gênant, surtout lorsque la logique d'épissage est compliquée et que la chaîne épissée est très longue
  • 2) Ce n'est pas facile à entretenir. Si vous ne faites pas attention, vous obtiendrez une mauvaise correspondance d'étiquette
  • 3) La structure n'est pas claire.

L'outil qui peut simplifier ce scénario est le moteur de modèle. L'arrière-plan technique du moteur de modèle est le premier si vous avez utilisé jsp, vous devez savoir que jsp est un modèle, utilisé pour analyser et présenter des données. Les moteurs de modèles ont également Il existe des vitesses, des marqueurs gratuits, etc. Il existe de nombreux moteurs de modèles front-end, et moustache.js est relativement populaire, avec plus de 8 000 likes sur git. Si nous utilisons moustache.js pour résoudre ce problème, cela peut ressembler à ceci :

.

//通过一些根据属性名称对应的标记定义模板
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 &#63; curSysAry[0].systemName : '',
  multiple: !!data.length,
  systems: data
 });
}

Copier après la connexion

En comparant les deux codes, vous constaterez que ce dernier code présente les avantages suivants par rapport au précédent :

  • 1) La structure est claire, tout le HTML à rendre est défini dans une seule position et il n'y a pas d'épissage
  • 2) La logique est claire. Ces balises dans le modèle correspondent en fait aux noms d'attribut des objets transmis lors du rendu du modèle
  • 3) Facile à maintenir. Si vous souhaitez ajouter ou supprimer des balises, il vous suffit d'ajuster le tableau correspondant au modèle.

Grâce à cet exemple, vous devriez pouvoir avoir une compréhension générale des moteurs de modèles. Ce type d'outil est de plus en plus courant dans le développement front-end, en particulier dans les applications qui séparent le front-end et le back-end. est déjà la base de telles applications. Le contenu de l'architecture. moustache.js est une implémentation de moteur très simple et facile à utiliser. Le contenu suivant présentera une par une les configurations de modèles couramment utilisées de cet outil avec des exemples pratiques. J'espère que vous apprécierez davantage cet outil :)

.

2. Comment utiliser la moustache
L'utilisation de moustache est très simple. Introduisez d'abord son fichier js via la balise script, puis suivez les étapes ci-dessous :
1) Définir la chaîne de modèle
Il existe deux manières de définir un modèle. La première manière est comme vu dans la partie précédente. Elle est directement définie dans le code js en utilisant [...].join(''). consiste à ajouter directement le modèle à Le contenu est défini en html à l'aide du script :

<script id="tpl" type="text/html">
 Hello {{name}}!
</script>
Copier après la connexion

Puis avant de compiler le modèle, définissez la chaîne du modèle d'origine en récupérant le innerHTML de tpl :

var tpl = document.getElementById('tpl').innerHTML.trim();
Copier après la connexion

具体要用哪种方式来定义模板,可以参考下面的建议:
如果这个模板要用于多个页面,推荐把模板定义在js代码中;如果这个模板只用于当前页面,推荐直接定义到script标签中,管理更方便。
2)预编译模板
假设原始模板串已经定义好,并用tpl变量来引用,就可以通过下面的代码来预编译模板:

Mustache.parse(tpl);
Copier après la connexion

要注意的是,经过预编译之后的tpl已经不再是原来的模板串了,连数据类型都变成数组类型了,这都是预编译的结果。
3)渲染模板
渲染方式很简单:

var htmlAfterRendered = Mustache.render(tpl1, obj);
Copier après la connexion

obj引用的是一个数据源对象,mustache会把模板中那些属性标签,根据约定的规则,替换成对象的内容。htmlAfterRendered就是替换之后的字符串,你可以用它完成你需要的DOM操作。

3. mustache的思想
mustache的核心是标签和logic-less。从前面的代码中可以看到定义模板时,使用了{{name}}这样的标记,还有{{#systems}}{{/systems}},这就是mustache的标签,只不过它用{{}}替代了<>,以免跟html标签的<>混淆。logic-less,可以翻译为轻逻辑,因为在定义模板的时候不会用到if-else,不会有循环式的编码,一切都用标签来解决,它的标签非常简单,但是能应付所有场景,阅读完本文之后,你会惊讶地发现,只要用以下几个标签几乎就能解决所有的问题:
{{prop}}
{{{prop}}}
{{#prop}}{{/prop}}
{{^prop}}{{/prop}}

4. {{prop}}标签
这个标签是mustache模板里用的最多的,可以将数据源对象上prop属性对应的值,转换成字符串进行输出,以下是同一个属性,对应不同类型的值,在经过mustache渲染之后输出结果的测试(前后那根短横线的作用是为了让这个标签的渲染结果看起来更清楚):

<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&#39;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) + "<&#x2F;b>"
 //}-

</script>

Copier après la connexion

mustache渲染{{prop}}标签的逻辑是:

  • 1)如果prop引用的值是null或undefined,则渲染成空串;
  • 2)如果prop引用的是一个函数,则在渲染时自动执行这个函数,并把这个函数的返回值作为渲染结果,假如这个返回值为null或者undefined,那么渲染结果仍然为空串,否则把返回值转成字符串作为渲染结果(注意最后一个用例,直接把函数代码渲染出来了);
  • 3)其它场景,直接把prop引用的值转成字符串作为渲染结果。

由于默认情况下,mustache在渲染prop时,都是对prop的原始值进行url编码或者html编码之后再输出的,所以有一个用例的渲染结果,把英文的单引号,转成了html实体符:

console.log(Mustache.render(tpl1, {
  prop: function () {
   return 'it\'s a fun'
  }
}));//-it&#39;s a fun-
Copier après la connexion

如果要阻止这种编码行为,只要把标签形式改成{{{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-
Copier après la connexion

5. {{#prop}}{{/prop}}标签
这对标签的作用非常强大,可以同时完成if-else和for-each以及动态渲染的模板功能。在这对标签之间,可以定义其它模板内容,嵌套所有标签。接下来看看mustache如何利用这个对标签完成这三个模板功能。

1) if-else渲染

只有prop属性在数据源对象上存在,并且不为falsy值(javascript 6个falsy值:null,undefined,NaN,0,false,空字符串),并且不为空数组的情况下,标签之间的内容才会被渲染,否则都不会被渲染:

<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>
Copier après la connexion

以上用例中特殊点的就是prop属性引用的是一个函数的时候,{{#prop}}会自动调用这个函数,并把函数的返回值作为if-else渲染逻辑的判断依据,也就是说如果这个函数返回的是falsy值或者是空数组的时候,那么这对标签之间的内容还是不会显示。

2)for-each渲染

当prop属性所引用的是一个非空数组时,这对标签之间的内容将会根据数组大小进行迭代,并且当数组元素为对象时,还会把该对象作为每一次迭代的上下文,以便迭代时的标签可以直接引用数组元素上的属性:

<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>
Copier après la connexion

从这个测试结果中可以看到,{{#prop}}{{/prop}}之间的模板内容根据prop所引用的数组迭代了两次,并且在这对标签内部直接通过{{name}}标签,输出了数组元素对象上的name属性对应的值。

如果prop属性所引用的是一个函数,但是这个函数返回值是一个数组类型,那么仍然会进行for-each渲染:

<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>
Copier après la connexion

3) 动态渲染

当prop属性所引用的是一个函数,并且这个函数的返回值还是一个函数的话,mustache会再次调用这个返回的函数,并给它传递2个参数:text表示原来的模板内容,render表示mustache内部的执行渲染的对象,以便在这个函数内部可以通过这render对象,结合原来的模板内容,自定义渲染的逻辑,并把函数的返回值作为渲染结果(这个返回值渲染的逻辑跟{{prop}}标签完全一样):

<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>
Copier après la connexion

6. {{^prop}}{{/prop}}标签
这对标签,与{{#prop}}{{/prop}}的if-else渲染执行相反逻辑,即只有在prop属性不存在,或者引用的是一个falsy值,或者是一个空数组的时候才会显示标签之间的内容,否则不会显示:

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

Copier après la connexion

7. 渲染上下文
mustache有一个渲染上下文栈的概念,在模板渲染的开始的时候,把数据源对象作为当前的渲染上下文 ,并压入上下文栈。在遇到{{#prop}}标签的时候,如果prop引用的是一个对象或者是一个非空的对象数组,或者prop引用的是一个函数,并且这个函数返回的是一个对象或者是一个非空的对象数组,就会把这个对象或者数组的元素作为当前渲染上下文,并压入上下文栈,当这个标签渲染完毕的时候,才会把该上下文弹出,恢复上一层标签所使用的上下文。由于{{#prop}}标签可以多层嵌套,所以在有的模板渲染的时候,会有多层上下文存在。mustache在解析标签时,根据标签名称查找当前上下文对象是否存在该属性,如果不存在就会到上层上下文对象中查找,只要在某一层找到,就会用该层上下文对象的值来渲染。

<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>
Copier après la connexion

上面这个例子中,在渲染{{#address}}{{/address}}时,上下文对象已经变成了obj2.person.student.address所引用的对象,所以{{home}}渲染时用到的就是obj2.person.student.address.home属性,而{{age}}渲染时,由于obj2.person.student.address不存在age属性,所以就会到上层上下文中查找,一直到obj2对象才找到,于是就把obj2.age当成了渲染结果。

还有一种方法,不用通过{{#prop}}创建新的上下文,也可以做到递归渲染属性的属性:

<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>
Copier après la connexion

这种方法其实很好理解,只要知道当前的上下文对象,再根据属性操作串person.student.address.home,当然就能找到需要的值了。

本文介绍了一个非常好用的前端模板引擎,涵盖的内容包含了在日常工作肯定会用到的知识点,希望大家喜欢。

Étiquettes associées:
source:php.cn
Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal