mirror of https://github.com/layui/layui
feat: 升级 2.11 核心 (#2560)
commit
c4e134fe91
|
@ -1,59 +1,59 @@
|
|||
<pre class="layui-code" lay-options="{preview: true, layout: ['preview'], copy: false, tools: ['full'], addTools: null}">
|
||||
<pre class="layui-code" lay-options="{preview: true, layout: ['preview'], codeStyle: 'max-height: 520px;', copy: false, tools: ['full'], addTools: null}">
|
||||
<textarea>
|
||||
{{!
|
||||
<style>
|
||||
.laytpl-demo{border: 1px solid #eee;}
|
||||
.laytpl-demo:first-child{border-right: none;}
|
||||
.laytpl-demo>textarea{position: relative; display: block; width:100%; height: 300px; padding: 11px; border: 0; box-sizing: border-box; resize: none; background-color: #fff; font-family: Courier New; font-size: 13px;}
|
||||
.laytpl-demo>div:first-child{height: 32px; line-height: 32px; padding: 6px 11px; border-bottom: 1px solid #eee; background-color: #F8F9FA;}
|
||||
.laytpl-demo{border: 1px solid #eee;}
|
||||
.laytpl-demo:first-child{border-right: none;}
|
||||
.laytpl-demo>textarea{position: relative; display: block; width:100%; height: 320px; padding: 11px; border: 0; box-sizing: border-box; resize: none; background-color: #fff; font-family: Courier New; font-size: 13px;}
|
||||
.laytpl-demo>div:first-child{height: 32px; line-height: 32px; padding: 6px 11px; border-bottom: 1px solid #eee; background-color: #F8F9FA;}
|
||||
.laytpl-demo .layui-tabs{top: -1px;}
|
||||
|
||||
#ID-tpl-view-body {
|
||||
max-height: 320px; overflow: auto; clear: both;
|
||||
}
|
||||
#ID-tpl-view-body > div {
|
||||
display: none;
|
||||
}
|
||||
.laytpl-demo pre {
|
||||
margin: 0; padding: 16px; background-color: #1F1F1F; color: #F8F9FA; font-family: 'Courier New',Consolas, monospace;
|
||||
}
|
||||
</style>
|
||||
<div class="layui-row">
|
||||
<div class="layui-col-xs6 laytpl-demo">
|
||||
<div>模板</div>
|
||||
<textarea id="ID-tpl-src">
|
||||
<h3>{{= d.title }}</h3>
|
||||
<ul>
|
||||
{{# layui.each(d.list, function(index, item){ }}
|
||||
<li>
|
||||
<span>{{= item.modname }}</span>
|
||||
<span>{{= item.alias }}:</span>
|
||||
<span>{{= item.site || '' }}</span>
|
||||
</li>
|
||||
{{# }); }}
|
||||
|
||||
{{# if(d.list.length === 0){ }}
|
||||
无数据
|
||||
{{# } }}
|
||||
</ul>
|
||||
</textarea>
|
||||
<div>
|
||||
<div style="cursor: pointer;" id="ID-tpl-src-title">
|
||||
<strong>模板(旧版本)</strong>
|
||||
<i class="layui-icon layui-icon-down layui-font-12"></i>
|
||||
</div>
|
||||
</div>
|
||||
<textarea id="ID-tpl-src"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="layui-col-xs6 laytpl-demo">
|
||||
<div>数据</div>
|
||||
<div><strong>数据</strong></div>
|
||||
<textarea id="ID-tpl-data">
|
||||
{
|
||||
"title": "Layui 常用模块",
|
||||
"title": "Layui 常用组件",
|
||||
"desc": "<a style=\"color:blue;\">一段带 HTML 内容的描述</a>",
|
||||
"list": [
|
||||
{
|
||||
"modname": "弹层",
|
||||
"alias": "layer",
|
||||
"site": "layer.domain.com"
|
||||
"title": "弹层",
|
||||
"name": "layer"
|
||||
},
|
||||
{
|
||||
"modname": "表单",
|
||||
"alias": "form"
|
||||
"title": "表单",
|
||||
"name": "form"
|
||||
},
|
||||
{
|
||||
"modname": "表格",
|
||||
"alias": "table"
|
||||
"title": "表格",
|
||||
"name": "table"
|
||||
},
|
||||
{
|
||||
"modname": "日期",
|
||||
"alias": "laydate"
|
||||
"title": "日期选择器",
|
||||
"name": "laydate"
|
||||
},
|
||||
{
|
||||
"modname": "上传",
|
||||
"alias": "upload"
|
||||
"title": "标签页",
|
||||
"name": "tabs"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -61,75 +61,198 @@
|
|||
</div>
|
||||
<div class="layui-col-xs12 laytpl-demo" style="border-top: none;">
|
||||
<div class="layui-row">
|
||||
<div class="layui-col-xs6">视图</div>
|
||||
<div class="layui-col-xs6" style="text-align: right">
|
||||
<span id="ID-tpl-viewtime"></span>
|
||||
<div class="layui-col-xs6 layui-tabs" id="ID-tpl-view-header">
|
||||
<ul class="layui-tabs-header">
|
||||
<li><strong>渲染结果</strong></li>
|
||||
<li><strong>源码</strong></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-padding-sm" id="ID-tpl-view" style="max-height: 300px; padding: 16px; overflow: auto;">…</div>
|
||||
<div class="layui-col-xs6" style="text-align: right">
|
||||
<span class="layui-badge" id="ID-tpl-view-time"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div id="ID-tpl-view-body">
|
||||
<div class="layui-show layui-padding-3 layui-text" id="ID-tpl-view"></div>
|
||||
<div><pre id="ID-tpl-view-code" ></pre></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-clear"></div>
|
||||
|
||||
<!-- import layui -->
|
||||
<!-- 新版本模板 -->
|
||||
<script type="text/html" id="ID-tpl-template-modern">
|
||||
<p>转义输出:{{= d.desc }}</p>
|
||||
<p>原文输出:{{- d.desc }}</p>
|
||||
|
||||
{{#注释标签 - 仅在模板中显示,不在视图中输出 }}
|
||||
|
||||
<p>{{! 忽略标签,可显示原始标签:
|
||||
{{ let a = 0; }} {{= escape }} {{- source }} {{#comments }} {{! ignore !}}
|
||||
!}}</p>
|
||||
|
||||
{{#标签空主体测试 }}
|
||||
{{}} {{ }} {{ }} {{= }} {{=}} {{= }}
|
||||
|
||||
<p>
|
||||
条件语句:
|
||||
{{= d.list.length ? d.title : '' }}
|
||||
{{ if(d.title){}} - #AAAA{{='A'}}{{ } }}
|
||||
</p>
|
||||
<p>循环语句:</p>
|
||||
<ul>
|
||||
{{ d.list.forEach(function(item) { }}
|
||||
<li>
|
||||
<span>{{= item.title }}</span>
|
||||
<span>{{= item.name }}</span>
|
||||
</li>
|
||||
{{ }); }}
|
||||
</ul>
|
||||
{{ if (d.list.length === 0) { }}
|
||||
无数据
|
||||
{{} }}
|
||||
</script>
|
||||
|
||||
<!-- 旧版本模板 -->
|
||||
<script type="text/html" id="ID-tpl-template-legacy">
|
||||
<p>转义输出:{{= d.desc }}</p>
|
||||
<p>原文输出:{{- d.desc }}</p>
|
||||
<p>
|
||||
条件语句:
|
||||
{{= d.list.length ? d.title : '' }}
|
||||
{{#if(d.title){}} - #AAAA{{='A'}}{{#}}}
|
||||
</p>
|
||||
<p>循环语句:</p>
|
||||
<ul>
|
||||
{{#d.list.forEach(function(item) { }}
|
||||
<li>
|
||||
<span>{{= item.title }}</span>
|
||||
<span>{{= item.name }}</span>
|
||||
</li>
|
||||
{{#}); }}
|
||||
</ul>
|
||||
{{#if (d.list.length === 0) { }}
|
||||
无数据
|
||||
{{#} }}
|
||||
</script>
|
||||
|
||||
<!-- import layui -->
|
||||
<script>
|
||||
layui.use(function(){
|
||||
layui.use(['laytpl', 'util', 'tabs', 'dropdown'], function() {
|
||||
var laytpl = layui.laytpl;
|
||||
var util = layui.util;
|
||||
var tabs = layui.tabs;
|
||||
var dropdown = layui.dropdown;
|
||||
var $ = layui.$;
|
||||
|
||||
// 默认设置
|
||||
laytpl.config({
|
||||
// tagStyle: 'modern' // 初始化标签风格
|
||||
});
|
||||
|
||||
// 获取模板和数据
|
||||
var get = function(type){
|
||||
var getData = function(type) {
|
||||
return {
|
||||
template: $('#ID-tpl-src').val(), // 获取模板
|
||||
data: function(){ // 获取数据
|
||||
try {
|
||||
return JSON.parse($('#ID-tpl-data').val());
|
||||
} catch(e){
|
||||
} catch(e) {
|
||||
$('#ID-tpl-view').html(e);
|
||||
}
|
||||
}()
|
||||
};
|
||||
};
|
||||
|
||||
var data = get();
|
||||
|
||||
// 耗时计算
|
||||
var startTime = new Date().getTime(), timer = function(startTime, title){
|
||||
var endTime = new Date().getTime();
|
||||
$('#ID-tpl-viewtime').html((title || '模板解析耗时:')+ (endTime - startTime) + 'ms');
|
||||
|
||||
// 视图渲染
|
||||
var renderView = function(html, startTime) {
|
||||
timer(startTime);
|
||||
$('#ID-tpl-view').html(html);
|
||||
$('#ID-tpl-view-code').html(util.escape(html));
|
||||
};
|
||||
|
||||
// 渲染模板
|
||||
var thisTpl = laytpl(data.template);
|
||||
// 生成模板
|
||||
var createTemplate = function(opts) {
|
||||
opts = $.extend({
|
||||
tagStyle: 'legacy'
|
||||
}, opts);
|
||||
|
||||
// 执行渲染
|
||||
thisTpl.render(data.data, function(view){
|
||||
timer(startTime);
|
||||
$('#ID-tpl-view').html(view);
|
||||
// 初始化模板
|
||||
var elem = $('#ID-tpl-template-'+ opts.tagStyle);
|
||||
$('#ID-tpl-src').val(elem.html().replace(/^\s+/g, ''));
|
||||
|
||||
return opts;
|
||||
};
|
||||
var tplConfig = createTemplate();
|
||||
var data = getData();
|
||||
|
||||
// 耗时计算
|
||||
var timer = function(startTime, title) {
|
||||
var endTime = new Date();
|
||||
$('#ID-tpl-view-time').html((title || '本次渲染总耗时:')+ (endTime - startTime) + 'ms');
|
||||
};
|
||||
var startTime = new Date();
|
||||
|
||||
// 创建一个模板实例
|
||||
var templateInst = laytpl(data.template, {
|
||||
condense: false, // 不处理连续空白符,即保留模板原始结构
|
||||
tagStyle: tplConfig.tagStyle
|
||||
});
|
||||
|
||||
|
||||
// 初始渲染
|
||||
templateInst.render(data.data, function(html) {
|
||||
renderView(html, startTime);
|
||||
});
|
||||
|
||||
// 编辑
|
||||
$('.laytpl-demo textarea').on('input propertychange', function(){
|
||||
var data = get();
|
||||
if(!data.data) return;
|
||||
|
||||
// 计算模板渲染耗时
|
||||
var startTime = new Date().getTime();
|
||||
|
||||
// 若模板有变化,则重新解析模板;若模板没变,数据有变化,则从模板缓存中直接渲染(效率大增)
|
||||
if(this.id === 'ID-tpl-src'){
|
||||
thisTpl.parse(data.template, data.data); // 解析模板
|
||||
$('.laytpl-demo textarea').on('input', function() {
|
||||
var data = getData();
|
||||
var startTime = new Date();
|
||||
|
||||
// 若模板有变化,则重新编译模板
|
||||
if (this.id === 'ID-tpl-src') {
|
||||
templateInst.compile(data.template);
|
||||
}
|
||||
|
||||
// 执行渲染
|
||||
thisTpl.render(data.data, function(view){
|
||||
timer(startTime);
|
||||
$('#ID-tpl-view').html(view);
|
||||
|
||||
// 若模板没变,数据有变化,则从模板缓存中直接渲染数据(效率大增)
|
||||
templateInst.render(data.data, function(html) {
|
||||
renderView(html, startTime);
|
||||
});
|
||||
});
|
||||
|
||||
// 视图结果 tabs
|
||||
tabs.render({
|
||||
elem: '#ID-tpl-view-header',
|
||||
body: ['#ID-tpl-view-body', '>div']
|
||||
});
|
||||
|
||||
// 切换模板
|
||||
dropdown.render({
|
||||
elem: '#ID-tpl-src-title',
|
||||
data: [{
|
||||
title: '新版本模板',
|
||||
tagStyle: 'modern'
|
||||
}, {
|
||||
title: '旧版本模板',
|
||||
tagStyle: 'legacy'
|
||||
}],
|
||||
click: function(obj){
|
||||
createTemplate({
|
||||
tagStyle: obj.tagStyle
|
||||
});
|
||||
this.elem.children('strong').html(obj.title);
|
||||
|
||||
// 同步设置标签风格
|
||||
templateInst.config.tagStyle = obj.tagStyle;
|
||||
|
||||
var data = getData();
|
||||
var startTime = new Date();
|
||||
|
||||
// 重新渲染
|
||||
templateInst.compile(data.template).render(data.data, function(html) {
|
||||
renderView(html, startTime);
|
||||
});
|
||||
}
|
||||
})
|
||||
});
|
||||
</script>
|
||||
!}}
|
||||
</textarea>
|
||||
</script>!}}</textarea>
|
||||
</pre>
|
||||
|
|
|
@ -7,72 +7,85 @@
|
|||
<tr>
|
||||
<th>标签</th>
|
||||
<th>描述</th>
|
||||
</tr>
|
||||
<th>类型</th>
|
||||
<th>默认值</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>{{!
|
||||
<tr>
|
||||
<td>{{= }}</td>
|
||||
<td>open</td>
|
||||
<td>
|
||||
|
||||
转义输出。若字段存在 HTML,将进行转义。
|
||||
|
||||
```
|
||||
<h2>{{= d.title }}</h2>
|
||||
```
|
||||
用于设置起始界定符
|
||||
|
||||
</td>
|
||||
<td>string</td>
|
||||
<td>
|
||||
|
||||
`{{`
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{- }} <sup>2.8+</sup></td>
|
||||
<td>close</td>
|
||||
<td>
|
||||
|
||||
原始输出。若字段存在 HTML,将正常渲染。
|
||||
|
||||
```
|
||||
<div>{{- d.content }}</div>
|
||||
```
|
||||
用于设置结束界定符
|
||||
|
||||
该语句一般在需要正常渲染 HTML 时用到,但若字段存在 script 等标签,为防止 xss 问题,可采用 `{{= }}` 进行转义输出。
|
||||
</td>
|
||||
<td>string</td>
|
||||
<td>
|
||||
|
||||
> ### 注意
|
||||
> 由于 `2.6.11` 版本对 laytpl 语句进行了重要调整,原 `{{ }}` 语法即等同 `{{- }}`,升级版本时,请进行相应调整。可参考:https://gitee.com/layui/layui/issues/I5AXSP
|
||||
`}}`
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{# }}</td>
|
||||
<td>cache <sup>2.11+</sup></td>
|
||||
<td>
|
||||
|
||||
JavaScript 语句。一般用于逻辑处理。
|
||||
|
||||
```
|
||||
<div>
|
||||
{{#
|
||||
var fn = function(){
|
||||
return '2017-08-18';
|
||||
};
|
||||
}}
|
||||
{{# if(true){ }}
|
||||
开始日期:{{= fn() }}
|
||||
{{# } else { }}
|
||||
已截止
|
||||
{{# } }}
|
||||
</div>
|
||||
```
|
||||
是否开启模板缓存,以便下次渲染时不重新编译模板
|
||||
|
||||
</td>
|
||||
</tr>!}}
|
||||
<tr>
|
||||
<td>{{!{{! !}}!}}</td>
|
||||
<td>boolean</td>
|
||||
<td>
|
||||
|
||||
对一段指定的模板区域进行过滤,即不解析该区域的模板。
|
||||
|
||||
```
|
||||
{{! {{! 这里面的模板不会被解析 !}} !}}
|
||||
```
|
||||
`true`
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tr>
|
||||
<td>condense <sup>2.11+</sup></td>
|
||||
<td>
|
||||
|
||||
是否压缩模板空白符,如:将多个连续的空白符压缩为单个空格
|
||||
|
||||
</td>
|
||||
<td>boolean</td>
|
||||
<td>
|
||||
|
||||
`true`
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>tagStyle<br><sup>2.11+</sup></td>
|
||||
<td>
|
||||
|
||||
设置标签风格。可选值:
|
||||
|
||||
- `legacy`: 采用 `< 2.11` 旧版本的标签风格
|
||||
- `modern`: 采用 `2.11+` 新版本的标签风格
|
||||
|
||||
为了保持向下兼容,默认仍然采用旧版本的标签风格,但在后续版本可能会将默认值设置为 `modern`,因此,**实际使用时,建议显式设置该选项值,以免升级时产生不兼容的问题**。
|
||||
|
||||
</td>
|
||||
<td>string</td>
|
||||
<td>
|
||||
|
||||
`legacy`
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>!}}
|
||||
</table>
|
||||
|
|
|
@ -0,0 +1,86 @@
|
|||
<table class="layui-table">
|
||||
<colgroup>
|
||||
<col width="150">
|
||||
<col>
|
||||
</colgroup>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>标签</th>
|
||||
<th>描述</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>{{!
|
||||
<tr>
|
||||
<td>{{= }}</td>
|
||||
<td>
|
||||
|
||||
转义输出。若字段存在 HTML,将进行转义。
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{- }} <sup>2.8+</sup></td>
|
||||
<td>
|
||||
|
||||
原文输出。即不对 HTML 字符进行转义,但需做好 XSS 防护。
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{# }} <sup>旧</sup></td>
|
||||
<td>
|
||||
|
||||
**旧版本风格**(`< 2.11`)「Scriptlet 标签」。一般用于流程控制,如:
|
||||
|
||||
```js
|
||||
{{#if (d.title) { }}
|
||||
标题:{{= d.title }}
|
||||
{{#} else { }}
|
||||
默认标题
|
||||
{{#} }}
|
||||
```
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ }} <sup>新</sup><sup>2.11+</sup></td>
|
||||
<td>
|
||||
|
||||
**新版本风格**「Scriptlet 标签」。一般用于流程控制,如:
|
||||
|
||||
```js
|
||||
{{ if (d.title) { }}
|
||||
标题:{{= d.title }}
|
||||
{{ } else { }}
|
||||
默认标题
|
||||
{{ } }}
|
||||
```
|
||||
|
||||
需设置 `tagStyle: 'modern'` 后生效,否则模板会报错。
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{# }} <sup>新</sup><sup>2.11+</sup></td>
|
||||
<td>
|
||||
|
||||
**新版本风格**「注释标签」。即仅在模板中显示,不在视图中输出。
|
||||
|
||||
需设置 `tagStyle: 'modern'` 后生效,否则会被视为旧版本的 Scriptlet 标签。
|
||||
|
||||
</td>
|
||||
</tr>!}}
|
||||
<tr>
|
||||
<td>{{!{{! !}}!}}</td>
|
||||
<td>
|
||||
|
||||
忽略标签。即该区域中的标签不会被解析,一般用于输出原始标签。如:
|
||||
|
||||
```js
|
||||
{{! {{! 这里面的 {{= escape }} 等模板标签不会被解析 !}} !}}
|
||||
```
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
|
@ -5,11 +5,11 @@ toc: true
|
|||
|
||||
# 模板引擎
|
||||
|
||||
> `laytpl` 是 Layui 的一款轻量 JavaScript 模板引擎,在字符解析上有着比较出色的表现。
|
||||
> `laytpl` 是 Layui 内置的 JavaScript 模板引擎,采用原生控制流,在模板解析上有着比较出色的表现。
|
||||
|
||||
<h2 id="test" lay-toc="{hot: true}" style="margin-bottom: 0;">在线测试</h2>
|
||||
|
||||
在以下*模板*或*数据*中进行编辑,下方*视图*将呈现对应结果。
|
||||
对文本框中的*模板*或*数据*进行编辑,下方将呈现对应的*渲染结果*。注:自 <sup>2.11+</sup> 版本开始,你可以设置 `tagStyle: 'modern'` 让模板采用新的标签风格。为了保持向下兼容,默认仍然采用旧版本的标签风格。
|
||||
|
||||
<div>
|
||||
{{- d.include("/laytpl/detail/demo.md") }}
|
||||
|
@ -20,135 +20,256 @@ toc: true
|
|||
| API | 描述 |
|
||||
| --- | --- |
|
||||
| var laytpl = layui.laytpl | 获得 `laytpl` 模块。 |
|
||||
| [laytpl(str, options).render(data, callback)](#render) | laytpl 组件渲染,核心方法。 |
|
||||
| [laytpl.config(options)](#config) | 配置 laytpl 全局属性 |
|
||||
| [var templateInst = laytpl(template, options)](#laytpl) | 创建模板实例。 |
|
||||
| [laytpl.config(options)](#config) | 设置基础选项默认值 |
|
||||
| [laytpl.extendVars(variables)](#variables) <sup>2.11+</sup> | 扩展模板内部变量 |
|
||||
|
||||
<h2 id="render" lay-toc="{level: 2, hot: true, title: '解析和渲染'}">模板解析和渲染</h2>
|
||||
<h3 id="laytpl" lay-toc="{level: 2, hot: true}">创建模板实例</h3>
|
||||
|
||||
`laytpl(str, options).render(data, callback);`
|
||||
`var templateInst = laytpl(template, options)`
|
||||
|
||||
- 参数 `str` : 模板原始字符
|
||||
- 参数 `options` <sup>2.8+</sup> : 当前模板实例的属性选项。可选项详见:[#属性选项](#config)
|
||||
- 参数 `data` : 模板数据
|
||||
- 参数 `callback` : 模板渲染完毕的回调函数,并返回渲染后的字符
|
||||
- 参数 `template` : 原始模板字符
|
||||
- 参数 `options` <sup>2.8+</sup> : 当前模板实例的选项。详见下述:[#基础选项](#options)
|
||||
|
||||
该方法返回一个模板编译器实例,用于对模板进行数据渲染等操作,实例对象的成员有:
|
||||
|
||||
| 实例成员 | 描述 |
|
||||
| --- | --- |
|
||||
| templateInst.render(data, callback) | 给模板实例进行数据渲染,返回渲染后的 HTML 字符 |
|
||||
| templateInst.compile(template) <sup>2.11+</sup> | 编译新的模板,会强制清除旧模板缓存 |
|
||||
| templateInst.config <sup>2.11+</sup> | 获取当前模板实例的配置选项 |
|
||||
|
||||
通过将模板编译与渲染两个环节分开,我们可以在模板仅编译一次的情况下,对其渲染不同的数据,如:
|
||||
|
||||
{{!
|
||||
```js
|
||||
var laytpl = layui.laytpl;
|
||||
|
||||
```
|
||||
layui.use('laytpl', function(){
|
||||
var laytpl = layui.laytpl;
|
||||
// 创建模板实例
|
||||
var templateInst = laytpl('{{= d.name }}是一名{{= d.role }}');
|
||||
|
||||
// 直接解析字符
|
||||
laytpl('{{= d.name }}是一名前端工程师').render({
|
||||
name: '张三'
|
||||
}, function(str){
|
||||
console.log(str); // 张三是一名前端工程师
|
||||
});
|
||||
// 数据渲染 1
|
||||
templateInst.render({
|
||||
name: '张三',
|
||||
role: '全栈开发者'
|
||||
}, function(html) {
|
||||
console.log(html); // 张三是一名全栈开发者
|
||||
});
|
||||
|
||||
// 同步写法
|
||||
var str = laytpl('{{= d.name }}是一名前端工程师').render({
|
||||
name: '张三'
|
||||
});
|
||||
console.log(str); // 张三是一名前端工程师
|
||||
// 数据渲染 2
|
||||
var html = templateInst.render({
|
||||
name: '王五',
|
||||
role: '架构师'
|
||||
});
|
||||
```
|
||||
!}}
|
||||
|
||||
若模板字符较大,可存放在页面某个标签中,如:
|
||||
若每次需要对不同的模板进行编译和数据渲染,你也可以使用链式写法,如:
|
||||
|
||||
{{!
|
||||
```js
|
||||
laytpl('{{= d.name }}是一名{{= d.role }}').render({
|
||||
name: '张三',
|
||||
role: '全栈开发者'
|
||||
}, function(html) {
|
||||
console.log(html); // 张三是一名全栈开发者
|
||||
});
|
||||
```
|
||||
<script id="TPL" type="text/html">
|
||||
!}}
|
||||
|
||||
若模板字符较大,你可以将模板存放在页面某个标签中,如:
|
||||
|
||||
{{!
|
||||
```js
|
||||
<script id="ID-demo-tpl" type="text/html">
|
||||
<h3>{{= d.name }}</h3>
|
||||
<p>性别:{{= d.sex ? '男' : '女' }}</p>
|
||||
<p>角色:{{= d.role }}</p>
|
||||
</script>
|
||||
|
||||
<div id="view"></div>
|
||||
<div id="ID-demo-view"></div>
|
||||
|
||||
<!-- import layui -->
|
||||
<script>
|
||||
layui.use(function(){
|
||||
var laytpl = layui.laytpl;
|
||||
|
||||
// 渲染
|
||||
var data = {
|
||||
name: '张三',
|
||||
sex: 1
|
||||
var template = document.getElementById('ID-demo-tpl').innerHTML; // 获取模板字符
|
||||
var target = document.getElementById('ID-demo-view'); // 输出结果的目标元素
|
||||
var data = { // 数据
|
||||
"name": "张三",
|
||||
"role": "全栈开发者"
|
||||
};
|
||||
var getTpl = document.getElementById('TPL').innerHTML; // 获取模板字符
|
||||
var elemView = document.getElementById('view'); // 视图对象
|
||||
// 渲染并输出结果
|
||||
laytpl(getTpl).render(data, function(str){
|
||||
elemView.innerHTML = str;
|
||||
laytpl(template).render(data, function(html) {
|
||||
target.innerHTML = html;
|
||||
});
|
||||
});
|
||||
</script>
|
||||
```
|
||||
|
||||
!}}
|
||||
|
||||
在实际使用时,若模板通用,而数据不同,为减少模板解析的开销,可将语句分开书写,如。
|
||||
实际使用时,若模板通用,而数据不同,为了避免对模板进行不必要的重复编译,推荐将创建模板实例与数据渲染分开书写。
|
||||
|
||||
```
|
||||
var compile = laytpl(str); // 模板解析
|
||||
compile.render(data, callback); // 模板渲染
|
||||
```
|
||||
<h3 id="options" lay-toc="{level: 2, hot: true}">基础选项</h3>
|
||||
|
||||
<h2 id="grammar" lay-toc="{level: 2, hot: true}">标签语法</h2>
|
||||
创建模板实例时,你还可以对其设置一些选项,如:
|
||||
|
||||
{{!
|
||||
```js
|
||||
// 创建模板实例
|
||||
var templateInst = laytpl(`
|
||||
{{ let role = d.role || '全栈开发者'; }}
|
||||
{{= d.name }}是一名{{= role }}
|
||||
`, {
|
||||
tagStyle: 'modern' // 采用新版本的标签风格
|
||||
});
|
||||
var html = templateInst.render({ name: '张三' });
|
||||
```
|
||||
!}}
|
||||
|
||||
支持设置的完整选项如下:
|
||||
|
||||
<div>
|
||||
{{- d.include("/laytpl/detail/options.md") }}
|
||||
</div>
|
||||
|
||||
> ### 注意
|
||||
> 开发者在使用模板语法时,需确保模板中的 JS 语句不来自于页面用户输入,即必须在页面开发者自身的可控范围内,否则请避免使用该模板引擎。
|
||||
<h3 id="tags" lay-toc="{level: 2, hot: true}">标签规则</h3>
|
||||
|
||||
<h2 id="config" lay-toc="{level: 2}">选项配置</h2>
|
||||
<div>
|
||||
{{- d.include("/laytpl/detail/tags.md") }}
|
||||
</div>
|
||||
|
||||
#### ⚡ 请注意:
|
||||
> *开发者在使用模板标签时,需确保模板中待输出的内容在开发者自身的可控范围内,尤其对于用户输入的字符要做好 XSS 防护,否则请避免使用该模板引擎,以免产生 XSS 安全隐患*。
|
||||
|
||||
<h3 id="include" lay-toc="{level: 2, hot: true}">导入子模板 <sup>2.11+</sup></h3>
|
||||
|
||||
{{!
|
||||
laytpl 支持在模板中通过添加 `{{- include(id, data) }}` 语句引入子模板。`include` 语句参数解释:
|
||||
|
||||
- `id` : 子模板 ID
|
||||
- `data` : 向子模版传入的数据
|
||||
|
||||
为了引入的子模板不被转义,因此这里应该使用 `{{- }}`,即对子模板进行原文输出。示例:
|
||||
|
||||
<pre class="layui-code" lay-options="{preview: true, layout: ['code', 'preview'], codeStyle: 'max-height: 520px;', tools: ['full']}">
|
||||
<textarea>
|
||||
<script id="ID-demo-tpl-header" type="text/html">
|
||||
<div>头部公共模板</div>
|
||||
</script>
|
||||
<script id="ID-demo-tpl-list" type="text/html">
|
||||
<ul>
|
||||
{{ d.items.forEach(function(item, index) { }}
|
||||
<li>
|
||||
<span>{{= item.title }}</span>
|
||||
{{ if(item.children) { }}
|
||||
{{- include('ID-demo-tpl-list', { items: item.children }) }}
|
||||
{{ } }}
|
||||
</li>
|
||||
{{ }); }}
|
||||
</ul>
|
||||
</script>
|
||||
<script id="ID-demo-tpl-main" type="text/html">
|
||||
{{- include('ID-demo-tpl-header') }}
|
||||
<h3>循环输出:</h3>
|
||||
{{- include('ID-demo-tpl-list', { items: d.items }) }}
|
||||
</script>
|
||||
<div id="ID-demo-view"></div>
|
||||
|
||||
<!-- import layui -->
|
||||
<script>
|
||||
layui.use(function() {
|
||||
var laytpl = layui.laytpl;
|
||||
|
||||
var template = document.getElementById('ID-demo-tpl-main').innerHTML; // 获取模板字符
|
||||
var target = document.getElementById('ID-demo-view'); // 输出结果的目标元素
|
||||
var data = {
|
||||
items: [{"title": "list 1", "children": [{"title": "list 1-1", "children": [{"title": "list 1-1-1"}]}, {"title": "list 1-2"}]},{"title": "list 2", "children": [{"title": "list 2-1"}]},{"title": "list 3"}]
|
||||
};
|
||||
|
||||
// 创建模板实例
|
||||
var templateInst = laytpl(template, {
|
||||
tagStyle: 'modern' // 采用新版本的标签风格
|
||||
});
|
||||
|
||||
// 渲染并输出结果
|
||||
templateInst.render(data, function(html) {
|
||||
target.innerHTML = html;
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</textarea>
|
||||
</pre>
|
||||
|
||||
!}}
|
||||
|
||||
若在 Node.js 环境,可通过 `laytpl.extendVars()` 方法重置 `include` 语句实现模板文件的导入。
|
||||
|
||||
<h3 id="config" lay-toc="{level: 2}">设置选项默认值</h3>
|
||||
|
||||
`laytpl.config(options);`
|
||||
|
||||
- 参数 `options` : 属性选项。可选项详见下表
|
||||
- 参数 `options`: 基础选项
|
||||
|
||||
| 属性 | 描述 |
|
||||
| --- | --- |
|
||||
| open | 标签符前缀 |
|
||||
| close | 标签符后缀 |
|
||||
你可以设置任意选项的默认值,如:
|
||||
|
||||
### 全局配置
|
||||
|
||||
若模板默认的标签符与其他模板存在冲突,可通过该方法重新设置标签符,如:
|
||||
|
||||
```
|
||||
{{!
|
||||
```js
|
||||
laytpl.config({
|
||||
open: '<%',
|
||||
close: '%>'
|
||||
open: '<%', // 自定义起始界定符
|
||||
close: '%>', // 自定义起始界定符
|
||||
tagStyle: 'modern' // 采用新版本的标签风格
|
||||
});
|
||||
|
||||
// 模板语法将默认采用上述定义的标签符书写
|
||||
laytpl(`
|
||||
<%# var job = ["前端工程师"]; %>
|
||||
<%= d.name %>是一名<%= job[d.type] %>。
|
||||
`).render({
|
||||
// 创建模板实例
|
||||
var templateInst = laytpl(`
|
||||
<% var roles = ["前端工程师","全栈工程师","架构师"]; %>
|
||||
<%= d.name %>是一名<%= roles[d.role] %>
|
||||
`);
|
||||
// 渲染
|
||||
templateInst.render({
|
||||
name: '张三',
|
||||
type: 0
|
||||
role: 1
|
||||
}, function(string){
|
||||
console.log(string); // 张三是一名前端工程师。
|
||||
console.log(string); // 张三是一名全栈工程师
|
||||
});
|
||||
```
|
||||
!}}
|
||||
|
||||
### 局部配置 <sup>2.8+</sup>
|
||||
<h3 id="variables" lay-toc="{level: 2}">扩展模板内变量</h3>
|
||||
|
||||
若不想受到上述全局配置的影响,可在 `laytpl(str, options)` 方法的第二个参数中设置当前模板的局部属性,如:
|
||||
`laytpl.extendVars(variables)`
|
||||
|
||||
```
|
||||
laytpl('<%= d.name %>是一名前端工程师', {
|
||||
open: '<%',
|
||||
close: '%>'
|
||||
}).render({name: '张三'}, function(string){
|
||||
console.log(string); // 张三是一名前端工程师。
|
||||
- 参数 `variables` : 扩展的变量列表,变量值通常是一个函数
|
||||
|
||||
事实上 laytpl 内置了一些模板内部方法,如 `_escape, include`。你可以对它们进行重构,或扩展更多内部变量,如:
|
||||
|
||||
{{!
|
||||
```js
|
||||
// 扩展模板内部变量
|
||||
laytpl.extendVars({
|
||||
// 重构 include 方法,实现引入模板文件
|
||||
include: function(filename, data) {
|
||||
// …
|
||||
},
|
||||
// 添加 toDataString 方法
|
||||
toDataString: function(date) {
|
||||
date = date || new Date();
|
||||
return new Date(date).toLocaleDateString();
|
||||
}
|
||||
});
|
||||
|
||||
// 在模板中使用扩展的变量
|
||||
var templateInst = laytpl('日期:{{= toDataString(d.time) }}');
|
||||
templateInst.render({ time: 1742745600000 }, function(html) {
|
||||
console.log(html);
|
||||
});
|
||||
```
|
||||
!}}
|
||||
|
||||
## 💖 心语
|
||||
|
||||
## 贴士
|
||||
我们在 `2.11` 版本对 laytpl 完成了重要重构,使其能够具备应对更多复杂模板结构的解析能力。同时,为了与业界常用的 JavaScript 模板引擎 ejs 对齐,我们新增了与 ejs 相同的标签规则,这意味着同一套模板可以在 laytpl 和 ejs 中任意切换。
|
||||
|
||||
> Layui table 等组件的动态模板功能,均采用 laytpl 驱动。 laytpl 亦可承载单页面应用开发中的视图模板。
|
||||
作为 Layui 为数不多的一个纯功能型的模块,laytpl 承载了一些重要组件的功能支撑,如 table, dropdown 等,使得它们也能够自定义动态模板,增强了组件的可定制化。当然,laytpl 也可以作为前端单页面应用及 Express 等 Web 框架的视图引擎。
|
||||
|
||||
|
|
147
docs/modules.md
147
docs/modules.md
|
@ -3,7 +3,7 @@ title: 模块系统
|
|||
toc: true
|
||||
---
|
||||
|
||||
<h1 id="idea" lay-toc="{title: '概要'}">模块系统</h1>
|
||||
<h1 id="idea" lay-toc="{title: '概要'}">模块系统</h1>
|
||||
|
||||
> Layui 制定了一套适合自身应用场景的轻量级模块规范,以便在不同规模的项目中,也能对前端代码进行很好的管理或维护。 Layui 的轻量级模块系统,并非有意违背 CommonJS 和 ES Module ,而是试图以更简单的方式去诠释高效,这种对*返璞归真*的执念源于在主流标准尚未完全普及的前 ES5 时代,后来也成为 Layui 独特的表达方式,而沿用至今。
|
||||
|
||||
|
@ -13,20 +13,20 @@ toc: true
|
|||
// 定义模块(通常单独作为一个 JS 文件)
|
||||
layui.define([mods], function(exports){
|
||||
// …
|
||||
|
||||
|
||||
exports('mod1', api); // 输出模块
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
// 使用模块
|
||||
layui.use(['mod1'], function(args){
|
||||
var mod1 = layui.mod1;
|
||||
|
||||
|
||||
// …
|
||||
});
|
||||
```
|
||||
|
||||
我们可以将其视为「像使用普通 API 一样来管理模块」,在此前提下,组件的承载也变得轻松自如,我们完全可以游刃在以浏览器为宿主的原生态的 HTML/CSS/JavaScript 的开发模式中,而不必卷入层出不穷的主流框架的浪潮之中,给心灵一个栖息之所。
|
||||
|
||||
|
||||
当然,Layui 自然也不是一个模块加载器,而是一套相对完整的 UI 解决方案,但与 Bootstrap 又并不相同,除了 HTML+CSS 本身的静态化处理,Layui 的组件更倾向于 JavaScript 的动态化渲染,并为之提供了相对丰富和统一的 API,使用时,只需稍加熟悉,便可在各种交互中应付自如。
|
||||
|
||||
|
||||
|
@ -41,42 +41,42 @@ layui.use(['mod1'], function(args){
|
|||
/** demo.js **/
|
||||
layui.define(function(exports){
|
||||
// do something
|
||||
|
||||
|
||||
// 输出 demo 模块
|
||||
exports('demo', {
|
||||
msg: 'Hello Demo'
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
// 若该模块需要依赖别的模块,则在 `mods` 参数中声明即可:
|
||||
// layui.define(['layer', 'form'], callback);
|
||||
```
|
||||
|
||||
如上所示,`callback` 返回的 `exports` 参数是一个函数,它接受两个参数:参数一为*模块名*,参数二为*模块接口*。
|
||||
|
||||
|
||||
另外, `callback` 将会在初次加载该模块时被自动执行。而有时,在某些特殊场景中可能需要再次执行该 `callback`,那么可以通过 `layui.factory(mod)` 方法获得。如:
|
||||
|
||||
```
|
||||
var demoCallback = layui.factory('demo'); // 得到定义 demo 模块时的 `callback`
|
||||
```
|
||||
```
|
||||
|
||||
- **模块命名空间**
|
||||
|
||||
Layui 定义的模块将会被绑定在 `layui` 对象下,如:`var demo = layui.demo;` 每个模块都有一个特定命名,且无法被占用,所以你无需担心模块的命名空间被污染,除非通过 `layui.disuse([mods])` 方法弃用已定义的模块。
|
||||
|
||||
|
||||
以下是定义一个「依赖 Layui 内置模块」的模块示例:
|
||||
|
||||
```
|
||||
layui.define(['layer', 'laydate'], function(exports){
|
||||
var layer = layui.layer // 获得 layer 模块
|
||||
var laydate = layui.laydate; // 获得 laydate 模块
|
||||
|
||||
|
||||
// 输出模块
|
||||
exports('demo', {}); // 模块名 demo 未被占用,此时模块定义成功
|
||||
// exports('layer', {}); // 模块名 layer 已经存在,此时模块定义失败
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
|
||||
同样的,在「扩展模块」时,也同样不能命名已经存在的模块名。
|
||||
|
||||
|
||||
|
@ -94,17 +94,17 @@ layui.define(['layer', 'laydate'], function(exports){
|
|||
layui.use(['layer', 'table'], function(){
|
||||
var layer = layui.layer;
|
||||
var table = layui.table;
|
||||
|
||||
|
||||
// do something
|
||||
});
|
||||
|
||||
|
||||
// 使用所有内置模块(layui v2.6 开始支持)
|
||||
layui.use(function(){
|
||||
var layer = layui.layer;
|
||||
var table = layui.table;
|
||||
var laydate = layui.laydate;
|
||||
// …
|
||||
|
||||
|
||||
// do something
|
||||
});
|
||||
```
|
||||
|
@ -115,7 +115,7 @@ layui.use(function(){
|
|||
layui.use(['layer', 'table'], function(layer, table){
|
||||
// 使用 layer
|
||||
layer.msg('test');
|
||||
|
||||
|
||||
// 使用 table
|
||||
table.render({});
|
||||
});
|
||||
|
@ -127,64 +127,97 @@ layui.use(['layer', 'table'], function(layer, table){
|
|||
|
||||
```
|
||||
// 在单页面视图碎片渲染时,再次调用「定义模块」时的 `callback`
|
||||
layui.use('demo', layui.factory('demo'));
|
||||
layui.use('demo', layui.factory('demo'));
|
||||
```
|
||||
|
||||
<h2 id="extend" lay-toc="">扩展模块</h2>
|
||||
<h2 id="extend" lay-toc="{hot: true}">扩展模块</h2>
|
||||
|
||||
`layui.extend(obj);`
|
||||
`layui.extend(settings);`
|
||||
|
||||
- 参数 `obj` 是一个对象,必选,用于声明模块别名。
|
||||
- 参数 `settings` : 扩展模块的相关配置,如模块名、模块路径等。
|
||||
|
||||
除了 Layui 的内置模块,在实际项目开发时,必不可少也需要扩展模块(可以简单理解为符合 layui 模块规范的 JS 文件)。 现在,让我们尝试着扩展一个 Layui 第三方模块:
|
||||
除了 Layui 的内置模块,在实际项目开发时,必不可少也需要扩展模块。我们在前文的「模块命名空间」提到,模块名具有唯一性,即不可被占用,因此我们扩展的模块必须是一个未被定义过的模块名。
|
||||
|
||||
1. **创建模块**
|
||||
现在,让我们尝试扩展一个 Layui 第三方模块。
|
||||
|
||||
我们在前文的「模块命名空间」提到,模块名具有唯一性,即不可被占用,因此我们扩展的模块必须是一个未被定义过的模块名。假设为:`firstMod`,然后新建一个 `firstMod.js` 文件并放入项目的任意目录中(最好不要放入到 Layui 原始目录)
|
||||
### 扩展遵循 Layui 规范的模块
|
||||
|
||||
2. **编写模块**
|
||||
1. **创建模块和定义模块**
|
||||
|
||||
接下来我们开始定义 `firstMod` 模块,并编写该模块主体代码。
|
||||
假设创建一个模块名为 `testModule` 的模块,新建 `testModule.js` 文件并放入项目的任意目录中(但应避免放入到 Layui 原始目录)。接着我们开始定义 `testModule` 模块,并编写该模块主体代码。
|
||||
|
||||
```
|
||||
```js
|
||||
/**
|
||||
* 编写一个 firstMod 模块
|
||||
* 定义 testModule 模块
|
||||
**/
|
||||
layui.define(function(exports){ // 也可以依赖其他模块
|
||||
var obj = {
|
||||
hello: function(str){
|
||||
alert('Hello '+ (str || 'firstMod'));
|
||||
alert('Hello '+ (str || 'TestModule'));
|
||||
}
|
||||
};
|
||||
|
||||
// 输出 firstMod 接口
|
||||
exports('firstMod', obj);
|
||||
|
||||
// 输出 testModule 接口
|
||||
exports('testModule', obj);
|
||||
});
|
||||
```
|
||||
|
||||
3. **声明模块**
|
||||
2. **声明模块和使用模块**
|
||||
|
||||
现在,我们只需声明模块名及模块文件路径,即完成模块扩展。
|
||||
|
||||
```
|
||||
// 假设 firstMod 模块文件所在路径在: /js/layui_exts/firstMod.js
|
||||
```js
|
||||
// 假设 testModule 模块文件所在路径在:/js/layui_exts/testModule.js
|
||||
layui.config({
|
||||
base: '/js/layui_exts/' // 配置 Layui 第三方扩展模块存放的基础目录
|
||||
base: '/js/layui_exts/' // 设置用于扩展模块的基础路径
|
||||
}).extend({
|
||||
firstMod: 'firstMod', // 定义模块名和模块文件路径,继承 layui.config 的 base 路径
|
||||
// mod2: 'mod2' // 可同时声明其他更多模块
|
||||
testModule: 'testModule', // 定义模块名和模块路径,会前置追加 base 基础路径
|
||||
// test1: 'test1' // 还可同时声明其他更多模块
|
||||
});
|
||||
|
||||
// 也可以不继承 layui.config 的 base 路径,即单独指定路径
|
||||
|
||||
// 也可以不前置追加 base 基础路径,即设置单独路径
|
||||
layui.extend({
|
||||
firstMod: '{/}/js/layui_exts/firstMod' // 开头特定符 {/} 即代表采用单独路径
|
||||
testModule: '{/}/js/layui_exts/testModule' // 开头特定符 {/} 即代表采用单独路径
|
||||
});
|
||||
|
||||
|
||||
// 然后我们就可以像使用内置模块一样使用扩展模块
|
||||
layui.use(['firstMod'], function(){
|
||||
var firstMod = layui.firstMod;
|
||||
|
||||
firstMod.hello('World');
|
||||
layui.use(['testModule', 'test1'], function(){
|
||||
var testModule = layui.testModule;
|
||||
// var test1 = layui.test1;
|
||||
|
||||
testModule.hello('World');
|
||||
});
|
||||
```
|
||||
|
||||
<h3 id="extend-external-modules" class="ws-anchor">扩展任意外部模块 <sup>2.11+</sup></h3>
|
||||
|
||||
我们在 `2.11.0` 版本新增了无缝扩展任意外部模块的支持,即无需遵循 Layui 模块规范的第三方库也能通过 Layui 去加载,并且无需对外部模块做任何的代码改动,只需在 `layui.extend()` 方法中声明模块名、路径和接口即可。
|
||||
|
||||
当声明的模块接受的是一个 `object` 类型时,即意味着声明任意外部模块。声明外部模块的对象由以下选项组成:
|
||||
|
||||
- `src` : 模块路径,可以是项目的相对路径,也可以是任意外部模块的公共 CDN 地址;
|
||||
- `api` : 接口名称,通常是模块提供的全局对象
|
||||
|
||||
下面是一个扩展任意外部模块的示例:
|
||||
|
||||
```js
|
||||
// 扩展任意外部模块
|
||||
layui.extend({
|
||||
marked: {
|
||||
src: 'https://cdnjs.cloudflare.com/ajax/libs/marked/15.0.7/marked.min.js', // 模块路径
|
||||
api: 'marked' // 接口名称
|
||||
},
|
||||
Prism: {
|
||||
src: 'https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/prism.min.js',
|
||||
api: 'Prism'
|
||||
}
|
||||
});
|
||||
|
||||
// 加载扩展模块
|
||||
layui.use(['marked', 'Prism'], function() {
|
||||
console.log('任意外部模块 loaded: ')
|
||||
console.log(' > marked: ', layui.marked);
|
||||
console.log(' > Prism: ', layui.Prism);
|
||||
});
|
||||
```
|
||||
|
||||
|
@ -196,7 +229,7 @@ layui.use(['firstMod'], function(){
|
|||
在不同的页面中,可能需要用到不同的业务模块。以首页为例:
|
||||
|
||||
```
|
||||
<script src="/js/layui/layui.js"></script>
|
||||
<script src="/js/layui/layui.js"></script>
|
||||
<script>
|
||||
layui.config({
|
||||
base: '/js/modules/' // 业务模块所在目录
|
||||
|
@ -209,13 +242,13 @@ layui.config({
|
|||
```
|
||||
/**
|
||||
* index.js 首页业务模块
|
||||
*/
|
||||
*/
|
||||
layui.define(['layer', 'form'], function(exports){
|
||||
var layer = layui.layer;
|
||||
var form = layui.form;
|
||||
|
||||
|
||||
layer.msg('Hello Index');
|
||||
|
||||
|
||||
exports('index', {}); // 输出模块名需和 use 和 extend 时的模块名一致
|
||||
});
|
||||
```
|
||||
|
@ -230,16 +263,16 @@ layui.define('layer', function(exports){
|
|||
// …
|
||||
exports('mod1', {});
|
||||
});
|
||||
|
||||
|
||||
// mod2.js,假设依赖 mod1 和 form
|
||||
layui.define(['mod1', 'form'], function(exports){
|
||||
// …
|
||||
exports('mod2', {});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
// mod3.js
|
||||
// …
|
||||
|
||||
// …
|
||||
|
||||
// index.js 主入口模块
|
||||
layui.define('mod2', function(exports){
|
||||
// …
|
||||
|
@ -258,7 +291,7 @@ layui.define('mod2', function(exports){
|
|||
> 综上: Layui 轻量级模块系统,无非就是:定义模块、使用模块、弃用模块、扩展模块的相互呼应,翻译成 API 即:
|
||||
> - `layui.define();`
|
||||
> - `layui.use();`
|
||||
> - `layui.disuse();`
|
||||
> - `layui.disuse();`
|
||||
> - `layui.extend();`
|
||||
> ---
|
||||
> 熟练运用,可让您的项目更利于维护。
|
||||
|
|
|
@ -1,30 +1,56 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
|
||||
<title>自定义模块 - layui</title>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
|
||||
<title>扩展模块 - Layui</title>
|
||||
<link rel="stylesheet" href="../src/css/layui.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="layui-text layui-padding-3">
|
||||
<h3>打开浏览器控制台查看测试结果</h3>
|
||||
</div>
|
||||
<script src="../src/layui.js"></script>
|
||||
<script>
|
||||
layui.config({
|
||||
base: 'extends/' // 用于扩展模块的基础路径
|
||||
});
|
||||
|
||||
<link rel="stylesheet" href="../src/css/layui.css">
|
||||
// 扩展模块
|
||||
layui.extend({
|
||||
// 扩展遵循 Layui 规范的模块
|
||||
index: 'index', // 会前置追加 base 基础路径
|
||||
test1: 'test/test1', // 会前置追加 base 基础路径
|
||||
test2: '{/}extends/test/test2', // 不会前置追加 base 基础路径,即单独路径
|
||||
// 扩展任意外部模块
|
||||
markdownit: {
|
||||
src: 'https://cdnjs.cloudflare.com/ajax/libs/markdown-it/13.0.2/markdown-it.min.js', // 模块路径
|
||||
api: 'markdownit' // 接口名称
|
||||
},
|
||||
Prism: {
|
||||
src: 'https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/prism.min.js',
|
||||
api: 'Prism'
|
||||
}
|
||||
});
|
||||
|
||||
<style>
|
||||
// 加载模块
|
||||
layui.use(['all', 'index', 'test1', 'test2', 'markdownit', 'Prism'], function() {
|
||||
console.log('Layui 内置模块 loaded: ', layui);
|
||||
console.log('遵循 Layui 规范的扩展模块 loaded: ', layui.index, layui.test1, layui.test2);
|
||||
console.log('任意外部模块 loaded: ')
|
||||
console.log(' > markdownit: ', layui.markdownit);
|
||||
console.log(' > Prism: ', layui.Prism);
|
||||
});
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
// 直接加载 base 目录下的模块
|
||||
layui.use(['test'], function() {
|
||||
console.log('直接加载 base 目录下的模块 loaded: ', layui.test);
|
||||
});
|
||||
|
||||
|
||||
|
||||
<script src="../src/layui.js"></script>
|
||||
<script>
|
||||
layui.config({
|
||||
base: './js/'
|
||||
}).extend({
|
||||
index: 'index'
|
||||
,test: 'child/test'
|
||||
}).use('test');
|
||||
|
||||
layui.use('index')
|
||||
</script>
|
||||
</body>
|
||||
// 加载外部样式
|
||||
layui.link('https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism.min.css', function(link) {
|
||||
console.log('prism.min.css loaded');
|
||||
}, 'prism');
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
|
||||
|
||||
layui.define(function(exports){
|
||||
|
||||
|
||||
exports('index', {
|
||||
title: '模块入口'
|
||||
title: 'index 扩展模块'
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,143 +0,0 @@
|
|||
/**
|
||||
|
||||
@Name:layui.modDemo XX组件
|
||||
@Author:贤心
|
||||
@License:MIT
|
||||
|
||||
*/
|
||||
|
||||
layui.define(['laytpl'], function(exports){
|
||||
"use strict";
|
||||
|
||||
var $ = layui.$
|
||||
,laytpl = layui.laytpl
|
||||
|
||||
//模块名
|
||||
,MOD_NAME = 'modDemo'
|
||||
|
||||
//外部接口
|
||||
,modeDemo = {
|
||||
config: {}
|
||||
,index: layui[MOD_NAME] ? (layui[MOD_NAME].index + 10000) : 0
|
||||
|
||||
//设置全局项
|
||||
,set: function(options){
|
||||
var that = this;
|
||||
that.config = $.extend({}, that.config, options);
|
||||
return that;
|
||||
}
|
||||
|
||||
//事件监听
|
||||
,on: function(events, callback){
|
||||
return layui.onevent.call(this, MOD_NAME, events, callback);
|
||||
}
|
||||
}
|
||||
|
||||
//操作当前实例
|
||||
,thisModule = function(){
|
||||
var that = this
|
||||
,options = that.config
|
||||
,id = options.id || that.index;
|
||||
|
||||
thisModule.that[id] = that; //记录当前实例对象
|
||||
thisModule.config[id] = options; //记录当前实例配置项
|
||||
|
||||
return {
|
||||
config: options
|
||||
//重置实例
|
||||
,reload: function(options){
|
||||
that.reload.call(that, options);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//获取当前实例配置项
|
||||
,getThisModuleConfig = function(id){
|
||||
var config = thisModule.config[id];
|
||||
if(!config) hint.error('The ID option was not found in the '+ MOD_NAME +' instance');
|
||||
return config || null;
|
||||
}
|
||||
|
||||
//字符常量
|
||||
,ELEM = 'layui-modeDemo'
|
||||
|
||||
|
||||
//主模板
|
||||
,TPL_MAIN = ['<div class="ayui-border-box">'
|
||||
|
||||
,'</div>'].join('')
|
||||
|
||||
//构造器
|
||||
,Class = function(options){
|
||||
var that = this;
|
||||
that.index = ++transfer.index;
|
||||
that.config = $.extend({}, that.config, transfer.config, options);
|
||||
that.render();
|
||||
};
|
||||
|
||||
//默认配置
|
||||
Class.prototype.config = {
|
||||
|
||||
};
|
||||
|
||||
//重载实例
|
||||
Class.prototype.reload = function(options){
|
||||
var that = this;
|
||||
|
||||
layui.each(options, function(key, item){
|
||||
if(item.constructor === Array) delete that.config[key];
|
||||
});
|
||||
|
||||
that.config = $.extend(true, {}, that.config, options);
|
||||
that.render();
|
||||
};
|
||||
|
||||
//渲染
|
||||
Class.prototype.render = function(){
|
||||
var that = this
|
||||
,options = that.config;
|
||||
|
||||
//解析模板
|
||||
that.elem = $(TPL_MAIN);
|
||||
|
||||
var othis = options.elem = $(options.elem);
|
||||
if(!othis[0]) return;
|
||||
|
||||
//索引
|
||||
that.key = options.id || that.index;
|
||||
|
||||
//插入组件结构
|
||||
othis.html(that.elem);
|
||||
|
||||
that.events(); //事件
|
||||
};
|
||||
|
||||
//事件
|
||||
Class.prototype.events = function(){
|
||||
var that = this;
|
||||
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
//记录所有实例
|
||||
thisModule.that = {}; //记录所有实例对象
|
||||
thisModule.config = {}; //记录所有实例配置项
|
||||
|
||||
//重载实例
|
||||
modeDemo.reload = function(id, options){
|
||||
var that = thisModule.that[id];
|
||||
that.reload(options);
|
||||
|
||||
return thisModule.call(that);
|
||||
};
|
||||
|
||||
//核心入口
|
||||
modeDemo.render = function(options){
|
||||
var inst = new Class(options);
|
||||
return thisTransfer.call(inst);
|
||||
};
|
||||
|
||||
exports(MOD_NAME, modeDemo);
|
||||
});
|
|
@ -1,8 +1,9 @@
|
|||
|
||||
/**
|
||||
* test
|
||||
*/
|
||||
|
||||
layui.define(function(exports){
|
||||
|
||||
exports('test', {
|
||||
title: '子目录模块加载'
|
||||
title: 'test 扩展模块'
|
||||
})
|
||||
});
|
||||
});
|
|
@ -0,0 +1,9 @@
|
|||
/**
|
||||
* test1
|
||||
*/
|
||||
|
||||
layui.define(function(exports){
|
||||
exports('test1', {
|
||||
title: 'test1 扩展模块'
|
||||
})
|
||||
});
|
|
@ -0,0 +1,9 @@
|
|||
/**
|
||||
* test2
|
||||
*/
|
||||
|
||||
layui.define(function(exports){
|
||||
exports('test2', {
|
||||
title: 'test2 扩展模块'
|
||||
})
|
||||
});
|
|
@ -1,74 +1,45 @@
|
|||
<!DOCTYPE html>
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>视图模板引擎 - layui</title>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>模板引擎 - Layui</title>
|
||||
<link rel="stylesheet" href="../src/css/layui.css">
|
||||
<style>
|
||||
.laytpl-demo{border: 1px solid #eee;}
|
||||
.laytpl-demo:first-child{border-right: none;}
|
||||
.laytpl-demo>textarea{position: relative; display: block; width:100%; height: 300px; padding: 11px; border: 0; box-sizing: border-box; resize: none; background-color: #fff; font-family: Courier New; font-size: 13px;}
|
||||
.laytpl-demo>div:first-child{height: 32px; line-height: 32px; padding: 6px 11px; border-bottom: 1px solid #eee; background-color: #F8F9FA;}
|
||||
.laytpl-demo .layui-tabs{top: -1px;}
|
||||
|
||||
<link rel="stylesheet" href="../src/css/layui.css">
|
||||
<style>
|
||||
.laytpl-demo{border: 1px solid #EBEBEB;}
|
||||
.laytpl-demo>textarea{position: relative; display: block; width:100%; height: 300px; padding: 11px; border: 0; box-sizing: border-box; resize: none; background-color: #fff; font-family: Courier New; font-size: 13px;}
|
||||
.laytpl-demo>div:first-child{height: 32px; line-height: 32px; padding: 6px 11px; border-bottom: 1px solid #EBEBEB; background-color: #F8F9FA;}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div>
|
||||
|
||||
<div class="layui-row">
|
||||
<div class="layui-col-xs6 laytpl-demo">
|
||||
<div>模板</div>
|
||||
<textarea id="demoTPL1"><h1>{{ d.title }}</h1>
|
||||
|
||||
<p>转义输出(HTML):{{ d.desc }}</p>
|
||||
<p>转义输出(HTML):{{= d.desc }}</p>
|
||||
<p>原始输出(HTML):{{- d.desc }}</p>
|
||||
{{#}}
|
||||
|
||||
<div class="layui-section">
|
||||
<hr>
|
||||
<ul>
|
||||
{{# var str = "a b c";
|
||||
layui.each(d.items, function(index, item){ }}
|
||||
<li class="{{ index > 0 ? 'list' : '' }}">
|
||||
<strong>{{ item.title }}</strong>
|
||||
{{# if(item.content){ }}
|
||||
<span>{{ item.content }}</span>
|
||||
{{# } }}
|
||||
<span>{{ item.time || '' }}</span>
|
||||
{{ str }}
|
||||
|
||||
|
||||
</li>
|
||||
{{# }); if(d.items.length === 0){ }}
|
||||
无数据
|
||||
{{# } }}
|
||||
</ul>
|
||||
<hr>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
{{ d.content || '' }}
|
||||
{{ }} {{}}
|
||||
{{ }}
|
||||
|
||||
\a
|
||||
'12'"""""
|
||||
"哈''哈"
|
||||
</div>
|
||||
|
||||
<p>渲染时间:{{ layui.util.toDateString(new Date()) }}</p></textarea>
|
||||
</div>
|
||||
|
||||
<div class="layui-col-xs6 laytpl-demo">
|
||||
#ID-tpl-view-body {
|
||||
height: calc(100vh - 430px); overflow: auto; clear: both;
|
||||
}
|
||||
#ID-tpl-view-body > div {
|
||||
display: none;
|
||||
}
|
||||
.laytpl-demo pre {
|
||||
padding: 16px; background-color: #20222A; color: #F8F9FA; font-family: 'Courier New',Consolas, monospace;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="layui-padding-3">
|
||||
<div class="layui-row">
|
||||
<div class="layui-col-xs3">数据</div>
|
||||
<div class="layui-col-xs9" style="text-align: right">
|
||||
<button class="layui-btn layui-btn-sm layui-btn-primary" lay-on="createData">生成数据</button>
|
||||
<div class="layui-col-xs6 laytpl-demo">
|
||||
<div>
|
||||
<a href="javascript:;" id="ID-tpl-src-title">
|
||||
<cite><strong>模板</strong></cite>
|
||||
<i class="layui-icon layui-icon-down layui-font-12"></i>
|
||||
</a>
|
||||
</div>
|
||||
<textarea id="ID-tpl-src"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<textarea id="demoData1">
|
||||
<div class="layui-col-xs6 laytpl-demo">
|
||||
<div class="layui-row">
|
||||
<div><strong>数据</strong></div>
|
||||
</div>
|
||||
<textarea id="ID-tpl-data">
|
||||
{
|
||||
"title": "标题",
|
||||
"desc": "<a href=\"\" style=\"color:blue;\">一段描述</a>",
|
||||
|
@ -91,156 +62,316 @@
|
|||
{"title": "list 3"}
|
||||
]
|
||||
}</textarea>
|
||||
</div>
|
||||
<div class="layui-col-xs12 laytpl-demo">
|
||||
<div class="layui-row">
|
||||
<div class="layui-col-xs4">视图</div>
|
||||
<div class="layui-col-xs4" style="text-align: center">
|
||||
<button class="layui-btn layui-btn-sm layui-btn-primary" lay-on="test1">性能测试</button>
|
||||
</div>
|
||||
<div class="layui-col-xs4" style="text-align: right">
|
||||
<span id="demoViewTime"></span>
|
||||
<div class="layui-col-xs12 laytpl-demo" style="border-top: none;">
|
||||
<div class="layui-row">
|
||||
<div class="layui-col-xs4 layui-tabs" id="ID-tpl-view-header">
|
||||
<ul class="layui-tabs-header">
|
||||
<li><strong>渲染结果</strong></li>
|
||||
<li><strong>源码</strong></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="layui-col-xs4" style="text-align: center">
|
||||
<button class="layui-btn layui-btn-sm layui-btn-border" lay-on="test">性能测试</button>
|
||||
</div>
|
||||
<div class="layui-col-xs4" style="text-align: right">
|
||||
<span class="layui-badge" id="ID-tpl-view-time"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div id="ID-tpl-view-body">
|
||||
<div class="layui-show layui-padding-3 layui-text" id="ID-tpl-view"></div>
|
||||
<div><pre id="ID-tpl-view-code"></pre></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-padding-sm" id="demoView1"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<script type="type/html" template id="demoTplCommon">
|
||||
公共模板 - {{ d.title }}
|
||||
</script>
|
||||
|
||||
<script type="type/html" template id="demoTplList">
|
||||
{{# if(d.items && d.items.length > 0){ }}
|
||||
<ul>
|
||||
{{# layui.each(d.items, function(index, item){ }}
|
||||
<li><strong>{{ item.title }}</strong>{{ laytpl.include('demoTplList', {items: item.child}) }}</li>
|
||||
{{# }); }}
|
||||
</ul>
|
||||
{{# } }}
|
||||
</script>
|
||||
</div>
|
||||
|
||||
<script type="type/html" template id="laytplTestTpl">
|
||||
{{# for(var i = 0; i < d.items.length; i++){ }}
|
||||
第{{= d.items[i].index }}个,Name: {{- d.items[i].name }} Number: {{= d.items[i].number }}
|
||||
{{# } }}
|
||||
</script>
|
||||
|
||||
<script type="text/html" id="ID-tpl-template-modern">
|
||||
<h2>
|
||||
{{= d.title }} - {{= d.title ? '#' : '' }}
|
||||
{{ if(true){ }}AAAA{{='A'}}{{ } }}
|
||||
</h2>
|
||||
{{- include('ID-tpl-template-common', {title: '头部'}) }}
|
||||
|
||||
<hr>
|
||||
|
||||
<p>转义输出:{{= d.desc }}</p>
|
||||
<p>原文输出:{{- d.desc }}</p>
|
||||
|
||||
{{# 这是一段注释。仅在模板中显示,不在视图中输出 }}
|
||||
|
||||
{{!
|
||||
这是一段不进行模板解析的区域,可显示原始标签:
|
||||
{{ let a = 0; }}、{{= escape }}、{{- source }}、{{# comments }}、{{! ignore !}}
|
||||
!}}
|
||||
|
||||
{{# 空主体测试 }}
|
||||
{{}} {{ }} {{ }} {{= }} {{=}} {{= }}
|
||||
|
||||
<div>
|
||||
{{- !0 ? '<br>循环输出:' : '' }}
|
||||
<hr>
|
||||
<ul>
|
||||
{{
|
||||
var str = "一级列表 a \\ b c";
|
||||
d.items.forEach(function(value, index) {
|
||||
}}
|
||||
<li class="{{= index > 0 ? 'list' : '' }}">
|
||||
<strong>{{= value.title }}</strong>
|
||||
{{ if(value.content){ }}
|
||||
<span>{{= value.content }}</span>
|
||||
{{ } }}
|
||||
<span>{{= value.time || '' }}</span>
|
||||
{{= str }}
|
||||
{{- include('ID-tpl-template-list', { items: value.child }) }}
|
||||
</li>
|
||||
{{ }); }}
|
||||
{{ if(d.items.length === 0){ }}
|
||||
无数据
|
||||
{{ } }}
|
||||
</ul>
|
||||
<hr>
|
||||
</div>
|
||||
|
||||
<script src="../src/layui.js"></script>
|
||||
<script>
|
||||
layui.use(['laytpl', 'util'], function(){
|
||||
var laytpl = layui.laytpl;
|
||||
var util = layui.util;
|
||||
var $ = layui.$;
|
||||
|
||||
// 获取模板和数据
|
||||
var get = function(type){
|
||||
return {
|
||||
template: $('#demoTPL1').val(), // 获取模板
|
||||
data: function(){ // 获取数据
|
||||
try {
|
||||
return JSON.parse($('#demoData1').val());
|
||||
} catch(e){
|
||||
$('#demoView1').html(e);
|
||||
}
|
||||
}()
|
||||
};
|
||||
};
|
||||
|
||||
var data = get();
|
||||
|
||||
// 耗时计算
|
||||
var startTime = new Date().getTime(), timer = function(startTime, title){
|
||||
var endTime = new Date().getTime();
|
||||
$('#demoViewTime').html((title || '模板解析耗时:')+ (endTime - startTime) + 'ms');
|
||||
};
|
||||
<div>
|
||||
{{= d.content || '' }}
|
||||
\反斜杠 | '单引号' "双引号" ""''"" | "左双右单' | '左单右双"
|
||||
</div>
|
||||
|
||||
// 全局设置
|
||||
/*laytpl.config({
|
||||
open: '<%',
|
||||
close: '%>'
|
||||
});*/
|
||||
<p>渲染时间:{{= layui.util.toDateString(new Date()) }}</p>
|
||||
</script>
|
||||
|
||||
// 渲染模板
|
||||
var thisTpl = laytpl(data.template);
|
||||
<script type="text/html" id="ID-tpl-template-common">
|
||||
公共模板 - {{= d.title }}
|
||||
</script>
|
||||
|
||||
// 执行渲染
|
||||
thisTpl.render(data.data, function(view){
|
||||
timer(startTime);
|
||||
$('#demoView1').html(view);
|
||||
});
|
||||
|
||||
// 编辑
|
||||
$('.laytpl-demo textarea').on('input', function(){
|
||||
var data = get();
|
||||
if(!data.data) return;
|
||||
|
||||
// 计算模板渲染耗时
|
||||
var startTime = new Date().getTime();
|
||||
|
||||
// 若模板有变化,则重新解析模板;若模板没变,数据有变化,则从模板缓存中直接渲染(效率大增)
|
||||
if(this.id === 'demoTPL1'){
|
||||
thisTpl.parse(data.template, data.data); // 解析模板
|
||||
}
|
||||
|
||||
// 执行渲染
|
||||
thisTpl.render(data.data, function(view){
|
||||
timer(startTime);
|
||||
$('#demoView1').html(view);
|
||||
});
|
||||
});
|
||||
|
||||
// 事件
|
||||
util.on({
|
||||
// 性能测试
|
||||
test1: function(){
|
||||
var dataLen = 1000 // 数据量
|
||||
var renderTimes = 1000; // 渲染次数
|
||||
|
||||
// 初始化数据
|
||||
var data = {
|
||||
title: '性能测试',
|
||||
items: function(items){
|
||||
for(var i = 0; i < dataLen; i++){
|
||||
items.push({
|
||||
index: i
|
||||
,name: '<strong style="color: red;">张三</strong>'
|
||||
,number: 100+i
|
||||
<script type="text/html" id="ID-tpl-template-list">
|
||||
{{ if(d.items && d.items.length > 0){ }}
|
||||
<ul>
|
||||
{{ layui.each(d.items, function(index, item){ }}
|
||||
<li>
|
||||
<strong>{{= item.title }}</strong>
|
||||
{{- include('ID-tpl-template-list', {items: item.child}) }}
|
||||
</li>
|
||||
{{ }); }}
|
||||
</ul>
|
||||
{{ } }}
|
||||
</script>
|
||||
|
||||
<script type="text/html" id="ID-tpl-template-test">
|
||||
{{ for (var i = 0; i < d.items.length; i++) { }}
|
||||
第 {{= d.items[i].index }} 个,Name: {{- d.items[i].name }} Number: {{= d.items[i].number }}
|
||||
{{ } }}
|
||||
</script>
|
||||
|
||||
<!-- 旧版本模板 -->
|
||||
<script type="text/html" id="ID-tpl-template-legacy">
|
||||
<h2>
|
||||
{{= d.title }} - {{= d.title ? '#' : '' }}
|
||||
{{# if(true){ }}AAAA{{='A'}}{{# } }}
|
||||
</h2>
|
||||
|
||||
<hr>
|
||||
|
||||
<p>转义输出:{{ d.desc }}</p>
|
||||
<p>转义输出:{{= d.desc }}</p>
|
||||
<p>原文输出:{{- d.desc }}</p>
|
||||
|
||||
{{}} {{ }} {{ }} {{= }} {{=}} {{= }}
|
||||
|
||||
<div>
|
||||
<ul>
|
||||
{{# var str = "a b c"; }}
|
||||
{{# layui.each(d.items, function(index, item) { }}
|
||||
<li class="{{= index > 0 ? 'list' : '' }}">
|
||||
<strong>{{= item.title }}</strong>
|
||||
{{# if(item.content){ }}
|
||||
<span>{{= item.content }}</span>
|
||||
{{# } }}
|
||||
<span>{{= item.time || '' }}</span>
|
||||
{{ str }}
|
||||
</li>
|
||||
{{# }); }}
|
||||
{{# if (d.items.length === 0) { }}
|
||||
无数据
|
||||
{{# } }}
|
||||
</ul>
|
||||
<hr>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
{{= d.content || '' }}
|
||||
\反斜杠 | '单引号' "双引号" ""''"" | "左双右单' | '左单右双"
|
||||
</div>
|
||||
|
||||
<p>渲染时间:{{ layui.util.toDateString(new Date()) }}</p>
|
||||
</script>
|
||||
</div>
|
||||
|
||||
<script src="../src/layui.js"></script>
|
||||
<script>
|
||||
layui.use(['laytpl', 'util', 'tabs', 'dropdown'], function() {
|
||||
var laytpl = layui.laytpl;
|
||||
var util = layui.util;
|
||||
var tabs = layui.tabs;
|
||||
var dropdown = layui.dropdown;
|
||||
var $ = layui.$;
|
||||
|
||||
// 默认设置
|
||||
laytpl.config({
|
||||
tagStyle: 'modern' // 初始采用新版标签风格
|
||||
})
|
||||
|
||||
// 获取模板和数据
|
||||
var getData = function(type) {
|
||||
return {
|
||||
template: $('#ID-tpl-src').val(), // 获取模板
|
||||
data: function(){ // 获取数据
|
||||
try {
|
||||
return JSON.parse($('#ID-tpl-data').val());
|
||||
} catch(e) {
|
||||
$('#ID-tpl-view').html(e);
|
||||
}
|
||||
}()
|
||||
};
|
||||
};
|
||||
|
||||
// 视图渲染
|
||||
var renderView = function(html, startTime) {
|
||||
timer(startTime);
|
||||
$('#ID-tpl-view').html(html);
|
||||
$('#ID-tpl-view-code').html(util.escape(html));
|
||||
};
|
||||
|
||||
// 生成模板
|
||||
var createTemplate = function(opts) {
|
||||
opts = $.extend({
|
||||
tagStyle: 'modern'
|
||||
}, opts);
|
||||
|
||||
// 初始化模板
|
||||
var elem = $('#ID-tpl-template-'+ opts.tagStyle);
|
||||
$('#ID-tpl-src').val(elem.html());
|
||||
|
||||
return opts;
|
||||
};
|
||||
var tplConfig = createTemplate();
|
||||
var data = getData();
|
||||
|
||||
// 耗时计算
|
||||
var timer = function(startTime, title) {
|
||||
var endTime = new Date();
|
||||
$('#ID-tpl-view-time').html((title || '模板解析耗时:')+ (endTime - startTime) + 'ms');
|
||||
};
|
||||
var startTime = new Date();
|
||||
|
||||
// 创建一个模板实例
|
||||
var templateInst = laytpl(data.template, {
|
||||
condense: false, // 不处理连续空白符,即保留模板原始结构
|
||||
tagStyle: tplConfig.tagStyle
|
||||
});
|
||||
|
||||
// 初始渲染
|
||||
templateInst.render(data.data, function(html) {
|
||||
renderView(html, startTime);
|
||||
});
|
||||
|
||||
// 编辑
|
||||
$('.laytpl-demo textarea').on('input', function() {
|
||||
var data = getData();
|
||||
var startTime = new Date();
|
||||
|
||||
// 若模板有变化,则重新编译模板
|
||||
if (this.id === 'ID-tpl-src') {
|
||||
templateInst.compile(data.template);
|
||||
}
|
||||
|
||||
// 若模板没变,数据有变化,则从模板缓存中直接渲染数据(效率大增)
|
||||
templateInst.render(data.data, function(html) {
|
||||
renderView(html, startTime);
|
||||
});
|
||||
});
|
||||
|
||||
// 事件
|
||||
util.on({
|
||||
// 性能测试
|
||||
test: function() {
|
||||
var dataLen = 1000 // 数据量
|
||||
var renderTimes = 1000; // 渲染次数
|
||||
|
||||
// 初始化数据
|
||||
var data = {
|
||||
title: '性能测试',
|
||||
items: function(items) {
|
||||
for (var i = 0; i < dataLen; i++) {
|
||||
items.push({
|
||||
index: i,
|
||||
name: '<strong style="color: red;">张三</strong>',
|
||||
number: 100+i
|
||||
});
|
||||
}
|
||||
return items;
|
||||
}([])
|
||||
};
|
||||
|
||||
// 模板
|
||||
var startTime = new Date();
|
||||
for (var j = 0; j < renderTimes; j++) {
|
||||
var template = document.getElementById('ID-tpl-template-test').innerHTML;
|
||||
var html = laytpl(template).render(data);
|
||||
}
|
||||
renderView(html, startTime);
|
||||
}
|
||||
});
|
||||
|
||||
// 局部自定义标签符
|
||||
laytpl(`
|
||||
<% var job = ["前端工程师"]; %>
|
||||
<%= d.name %>是一名<%= job[d.index] %>。
|
||||
`, {
|
||||
open: '<%',
|
||||
close: '%>'
|
||||
}).render({
|
||||
name: '张三',
|
||||
index: 0
|
||||
}, function(str) {
|
||||
console.log(str); // 张三是一名前端工程师。
|
||||
});
|
||||
|
||||
// 视图结果 tabs
|
||||
tabs.render({
|
||||
elem: '#ID-tpl-view-header',
|
||||
body: ['#ID-tpl-view-body', '>div']
|
||||
});
|
||||
|
||||
// 切换模板
|
||||
dropdown.render({
|
||||
elem: '#ID-tpl-src-title',
|
||||
data: [{
|
||||
title: '新版本模板',
|
||||
tagStyle: 'modern'
|
||||
}, {
|
||||
title: '旧版本模板',
|
||||
tagStyle: 'legacy'
|
||||
}],
|
||||
click: function(obj){
|
||||
createTemplate({
|
||||
tagStyle: obj.tagStyle
|
||||
});
|
||||
this.elem.children('cite').html(obj.title);
|
||||
|
||||
// 同步设置标签风格
|
||||
templateInst.config.tagStyle = obj.tagStyle;
|
||||
|
||||
var data = getData();
|
||||
var startTime = new Date();
|
||||
|
||||
// 重新渲染
|
||||
templateInst.compile(data.template).render(data.data, function(html) {
|
||||
renderView(html, startTime);
|
||||
});
|
||||
}
|
||||
return items;
|
||||
}([])
|
||||
};
|
||||
|
||||
// 模板
|
||||
var startTime = new Date();
|
||||
for(var j = 0; j < renderTimes; j++){
|
||||
var template = document.getElementById('laytplTestTpl').innerHTML;
|
||||
var html = laytpl(template).render(data);
|
||||
}
|
||||
timer(startTime, '本次测试耗时:');
|
||||
$('#demoView1').html(html);
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
// 自定义标签符
|
||||
laytpl(`
|
||||
<%# var job = ["前端工程师"]; %>
|
||||
<%= d.name %>是一名<%= job[d.type] %>。
|
||||
`, {
|
||||
open: '<%',
|
||||
close: '%>'
|
||||
}).render({
|
||||
name: '张三',
|
||||
type: 0
|
||||
}, function(str){
|
||||
console.log(str); // 张三是一名前端工程师。
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
})
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
849
src/layui.js
849
src/layui.js
File diff suppressed because it is too large
Load Diff
|
@ -1,162 +1,475 @@
|
|||
/**
|
||||
* laytpl 轻量模板引擎
|
||||
* laytpl
|
||||
* 轻量级通用模板引擎
|
||||
*/
|
||||
|
||||
layui.define(function(exports){
|
||||
"use strict";
|
||||
(function(global) {
|
||||
'use strict';
|
||||
|
||||
// 默认属性
|
||||
var config = {
|
||||
open: '{{', // 标签符前缀
|
||||
close: '}}' // 标签符后缀
|
||||
};
|
||||
var MOD_NAME = 'laytpl';
|
||||
|
||||
// 模板工具
|
||||
var tool = {
|
||||
escape: function(html){
|
||||
var exp = /[<"'>]|&(?=#[a-zA-Z0-9]+)/g;
|
||||
if(html === undefined || html === null) return '';
|
||||
|
||||
html += '';
|
||||
if(!exp.test(html)) return html;
|
||||
|
||||
return html.replace(/&(?!#?[a-zA-Z0-9]+;)/g, '&')
|
||||
.replace(/</g, '<').replace(/>/g, '>')
|
||||
.replace(/'/g, ''').replace(/"/g, '"');
|
||||
}
|
||||
};
|
||||
|
||||
// 内部方法
|
||||
var inner = {
|
||||
exp: function(str){
|
||||
return new RegExp(str, 'g');
|
||||
},
|
||||
// 错误提示
|
||||
error: function(e, source){
|
||||
var error = 'Laytpl Error: ';
|
||||
typeof console === 'object' && console.error(error + e + '\n'+ (source || ''));
|
||||
return error + e;
|
||||
}
|
||||
};
|
||||
|
||||
// constructor
|
||||
var Class = function(template, options){
|
||||
// 实例接口
|
||||
var thisModule = function() {
|
||||
var that = this;
|
||||
that.config = that.config || {};
|
||||
that.template = template;
|
||||
var options = that.config;
|
||||
return {
|
||||
config: options,
|
||||
|
||||
// 简单属性合并
|
||||
var extend = function(obj){
|
||||
for(var i in obj){
|
||||
that.config[i] = obj[i];
|
||||
/**
|
||||
* 渲染模板
|
||||
* @param {Object} data - 模板数据
|
||||
* @param {Function} callback - 回调函数
|
||||
* @returns {string} 渲染后的模板
|
||||
*/
|
||||
render: function(data, callback) {
|
||||
options.data = data
|
||||
var html = that.render();
|
||||
|
||||
// 如果传入目标元素选择器,则直接将模板渲染到目标元素中
|
||||
if (options.target) {
|
||||
var elem = document.querySelector(options.target);
|
||||
if (elem) {
|
||||
elem.innerHTML = html;
|
||||
}
|
||||
}
|
||||
|
||||
// 返回结果
|
||||
return typeof callback === 'function'
|
||||
? (callback(html), this)
|
||||
: html;
|
||||
},
|
||||
|
||||
/**
|
||||
* 编译新的模板
|
||||
* @param {string} template - 模板
|
||||
* @returns {this}
|
||||
*/
|
||||
compile: function(template) {
|
||||
options.template = template;
|
||||
delete that.compilerCache; // 清除模板缓存
|
||||
// that.compile(template);
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* 模板编译错误事件
|
||||
* @param {Function} callback
|
||||
* @returns {this}
|
||||
*/
|
||||
error: function(callback) {
|
||||
callback && (options.error = callback);
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* 以下为兼容旧版本相关方法
|
||||
*/
|
||||
|
||||
// 解析并渲染模板
|
||||
parse: function(template, data) {
|
||||
return this.compile(template).render(data);
|
||||
}
|
||||
};
|
||||
|
||||
extend(config);
|
||||
extend(options);
|
||||
};
|
||||
|
||||
// 标签正则
|
||||
Class.prototype.tagExp = function(type, _, __){
|
||||
var options = this.config;
|
||||
var types = [
|
||||
'#([\\s\\S])+?', // js 语句
|
||||
'([^{#}])*?' // 普通字段
|
||||
][type || 0];
|
||||
|
||||
return inner.exp((_||'') + options.open + types + options.close + (__||''));
|
||||
// 模板内部变量
|
||||
var vars = {
|
||||
// 字符转义
|
||||
escape: function(html) {
|
||||
var exp = /[<"'>]|&(?=#[a-zA-Z0-9]+)/g;
|
||||
if (html === undefined || html === null) return '';
|
||||
html = ''+ html;
|
||||
if (!exp.test(html)) return html;
|
||||
return html.replace(exp, function(str) {
|
||||
return '&#'+ str.charCodeAt(0) + ';';
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 模版解析
|
||||
Class.prototype.parse = function(template, data){
|
||||
// 组件工具类方法
|
||||
var tools = {
|
||||
regex: function(str, mod) {
|
||||
return new RegExp(str, mod || 'g');
|
||||
},
|
||||
|
||||
/**
|
||||
* 错误提示
|
||||
* @param {string} e - 原始错误信息
|
||||
* @param {Object} opts - 自定义选项
|
||||
* @param {Function} error - 错误回调
|
||||
* @returns {string} - 错误提示
|
||||
*/
|
||||
error: function(e, opts, error) {
|
||||
opts = opts || {};
|
||||
opts = Object.assign({
|
||||
debug: '',
|
||||
message: 'Laytpl '+ (opts.type || '') +'Error: ' + e
|
||||
}, opts);
|
||||
|
||||
// 向控制台输出错误信息
|
||||
typeof console === 'object' && console.error(opts.message, '\n', opts.debug, '\n', opts);
|
||||
typeof error === 'function' && error(opts); // 执行错误回调
|
||||
return opts.message; // 向视图返回错误提示
|
||||
}
|
||||
};
|
||||
|
||||
// 默认配置
|
||||
var config = {
|
||||
open: '{{', // 起始界定符
|
||||
close: '}}', // 结束界定符
|
||||
cache: true, // 是否开启模板缓存,以便下次渲染时不重新编译模板
|
||||
condense: true, // 是否压缩模板空白符,如:将多个连续的空白符压缩为单个空格
|
||||
tagStyle: '' // 标签风格。默认采用 < 2.11 的风格,设置 modern 则采用 2.11+ 风格
|
||||
};
|
||||
|
||||
// 构造器
|
||||
var Class = function(template, options) {
|
||||
var that = this;
|
||||
|
||||
// 选项合并
|
||||
options = that.config = Object.assign({
|
||||
template: template
|
||||
}, config, options);
|
||||
|
||||
// 当前实例的模板内工具
|
||||
that.vars = Object.assign({
|
||||
/**
|
||||
* 引用外部模板。若在 Node.js 环境,可通过重置该方法实现模板文件导入
|
||||
* @param {string} id - 模板 ID
|
||||
* @param {Object} data - 模板数据
|
||||
* @returns {string} 模板渲染后内容
|
||||
*/
|
||||
include: function(id, data) {
|
||||
var elem = document.getElementById(id);
|
||||
var template = elem ? elem.innerHTML : '';
|
||||
return template ? that.render(template, data) : '';
|
||||
}
|
||||
}, vars);
|
||||
|
||||
// 编译模板
|
||||
that.compile(options.template);
|
||||
};
|
||||
|
||||
/**
|
||||
* 渲染
|
||||
* @param {Object} template - 模板
|
||||
* @param {Object} data - 数据
|
||||
* @returns {string} 渲染后的模板内容
|
||||
*/
|
||||
Class.prototype.render = function(template, data) {
|
||||
var that = this;
|
||||
var options = that.config;
|
||||
|
||||
// 获得模板渲染函数
|
||||
var compiler = template ? that.compile(template) : (
|
||||
that.compilerCache || that.compile(options.template)
|
||||
);
|
||||
|
||||
// 获取渲染后的字符
|
||||
var html = function() {
|
||||
data = data || options.data || {};
|
||||
try {
|
||||
return compiler(data);
|
||||
} catch(e) {
|
||||
template = template || options.template;
|
||||
return tools.error(e, {
|
||||
debug: that.checkErrorArea(template, data),
|
||||
template: template,
|
||||
type: 'Render'
|
||||
}, options.error);
|
||||
}
|
||||
}();
|
||||
|
||||
// 缓存编译器
|
||||
if (options.cache && !template) {
|
||||
that.compilerCache = compiler;
|
||||
}
|
||||
|
||||
return html; // 返回渲染后的字符
|
||||
};
|
||||
|
||||
/**
|
||||
* 编译模板
|
||||
* @param {string} template - 原始模板
|
||||
* @returns {Function} 模板编译器,用于后续数据渲染
|
||||
*/
|
||||
Class.prototype.compile = function(template) {
|
||||
var that = this;
|
||||
var options = that.config;
|
||||
var source = template;
|
||||
var jss = inner.exp('^'+ options.open +'#', '');
|
||||
var jsse = inner.exp(options.close +'$', '');
|
||||
var openDelimiter = options.open;
|
||||
var closeDelimiter = options.close;
|
||||
var condense = options.condense;
|
||||
var regex = tools.regex;
|
||||
const placeholder = '\u2028'; // Unicode 行分隔符
|
||||
|
||||
// 模板必须为 string 类型
|
||||
if(typeof template !== 'string') return template;
|
||||
// console.log('compile');
|
||||
|
||||
// 正则解析
|
||||
template = template.replace(/\s+|\r|\t|\n/g, ' ')
|
||||
.replace(inner.exp(options.open +'#'), options.open +'# ')
|
||||
.replace(inner.exp(options.close +'}'), '} '+ options.close).replace(/\\/g, '\\\\')
|
||||
|
||||
// 不匹配指定区域的内容
|
||||
.replace(inner.exp(options.open + '!(.+?)!' + options.close), function(str){
|
||||
str = str.replace(inner.exp('^'+ options.open + '!'), '')
|
||||
.replace(inner.exp('!'+ options.close), '')
|
||||
.replace(inner.exp(options.open + '|' + options.close), function(tag){
|
||||
return tag.replace(/(.)/g, '\\$1')
|
||||
});
|
||||
return str
|
||||
})
|
||||
|
||||
// 匹配 JS 语法
|
||||
.replace(/(?="|')/g, '\\').replace(that.tagExp(), function(str){
|
||||
str = str.replace(jss, '').replace(jsse, '');
|
||||
return '";' + str.replace(/\\(.)/g, '$1') + ';view+="';
|
||||
})
|
||||
|
||||
// 匹配普通输出语句
|
||||
.replace(that.tagExp(1), function(str){
|
||||
var start = '"+laytpl.escape(';
|
||||
if(str.replace(/\s/g, '') === options.open + options.close){
|
||||
// 模板必须为 string 类型,且不能为空
|
||||
if (typeof template !== 'string' || !template) {
|
||||
return function() {
|
||||
return '';
|
||||
}
|
||||
str = str.replace(inner.exp(options.open + '|' + options.close), '');
|
||||
if(/^=/.test(str)){
|
||||
str = str.replace(/^=/, '');
|
||||
} else if(/^-/.test(str)){
|
||||
str = str.replace(/^-/, '');
|
||||
start = '"+(';
|
||||
}
|
||||
return start + str.replace(/\\(.)/g, '$1') + ')+"';
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
template = '"use strict";var view = "' + template + '";return view;';
|
||||
/**
|
||||
* 完整标签正则
|
||||
* @param {string[]} cores - 标签内部核心表达式,含:前置、主体、后置
|
||||
* @param {Object} sides - 标签两侧外部表达式
|
||||
* @returns {RegExp}
|
||||
*/
|
||||
var tagRegex = function(cores, sides) {
|
||||
var arr = [
|
||||
'(?:'+ openDelimiter + (cores[0] || '') +'\\s*)', // 界定符前置
|
||||
'('+ (cores[1] || '[\\s\\S]') +'*?)', // 标签主体
|
||||
'(?:\\s*'+ (cores[2] || '') + closeDelimiter +')' // 界定符后置
|
||||
];
|
||||
sides = sides || {};
|
||||
sides.before && arr.unshift(sides.before); // 标签前面的表达式
|
||||
sides.after && arr.push(sides.after); // 标签后面的表达式
|
||||
return regex(arr.join(''));
|
||||
};
|
||||
|
||||
// 匹配非输出类型标签两侧的换行符和空白符,避免渲染后占用一行
|
||||
var sidesRegex = condense ? ['', ''] : ['(?:(?:\\n)*\\s*)', '(?:\\s*?)'];
|
||||
var delimSides = {
|
||||
before: sidesRegex[0],
|
||||
after: sidesRegex[1]
|
||||
};
|
||||
|
||||
/**
|
||||
* 清理多余符号
|
||||
* @param {string} body - 标签主体字符
|
||||
* @param {boolean} nowrap - 是否强制不换行
|
||||
* @returns {string} 清理后的字符
|
||||
*/
|
||||
var clear = function(body, nowrap) {
|
||||
if (!condense) {
|
||||
// 还原语句中的 Unicode 行分隔符
|
||||
body = body.replace(regex(placeholder), nowrap ? '' : '\n');
|
||||
}
|
||||
body = body.replace(/\\(\\|")/g, '$1'); // 去除多余反斜杠
|
||||
return body;
|
||||
};
|
||||
|
||||
// 纠正标签结构
|
||||
var correct = function(tpl) {
|
||||
return tpl.replace(regex('([}\\]])'+ closeDelimiter), '$1 '+ closeDelimiter);
|
||||
};
|
||||
|
||||
// 模板解析
|
||||
var parse = that.parse = function(tpl) {
|
||||
tpl = tpl || '';
|
||||
if (!tpl) return tpl;
|
||||
|
||||
// 压缩连续空白符
|
||||
if (condense) {
|
||||
tpl = tpl.replace(/\t/g, ' ').replace(/\s+/g, ' ');
|
||||
}
|
||||
|
||||
// 初始整理
|
||||
tpl = correct(tpl) // 纠正标签
|
||||
.replace(/(?=\\|")/g, '\\') // 转义反斜杠和双引号
|
||||
.replace(/\r?\n/g, condense ? '' : placeholder); // 整理换行符
|
||||
|
||||
// 忽略标签 - 即区域中的内容不进行标签解析
|
||||
tpl = tpl.replace(tagRegex(['!', '', '!'], delimSides), function(str, body) {
|
||||
body = body.replace(regex(openDelimiter + '|' + closeDelimiter), function(tag) {
|
||||
return tag.replace(/(?=.)/g, '\\');
|
||||
});
|
||||
return body;
|
||||
});
|
||||
|
||||
// 模板字符拼接
|
||||
var strConcatenation = function(body) {
|
||||
// 通过对 20k+ 行的模板进行编译测试, 发现 Chrome `+=` 性能竟优于 `push`
|
||||
// 1k 次循环 + 1k 行数据量进行模板编译+渲染,发现 `+=` 性能仍然优于 `push`
|
||||
// 考虑可能是 V8 做了 Ropes 结构优化? 或跟模板采用「静态拼接」的实现有关(可能性更大)
|
||||
return ['";', body, '__laytpl__+="'].join('\n');
|
||||
// return ['");', body, '__laytpl__.push("'].join('\n');
|
||||
};
|
||||
|
||||
// 解析输出标签
|
||||
var output = function(str, delimiter, body) {
|
||||
var _escape;
|
||||
|
||||
if (!body) return '';
|
||||
body = clear(body, true);
|
||||
|
||||
// 输出方式
|
||||
if (delimiter === '-') { // 原文输出,即不对 HTML 原文进行转义
|
||||
_escape = '';
|
||||
} else { // 转义输出
|
||||
_escape = '_escape';
|
||||
}
|
||||
|
||||
return body ? strConcatenation(
|
||||
'__laytpl__+='+ _escape +'('+ body +');'
|
||||
// '__laytpl__.push('+ _escape +'('+ body +'));'
|
||||
) : '';
|
||||
};
|
||||
|
||||
// 解析 Scriptlet
|
||||
var statement = function(str, body) {
|
||||
if (!body) return '';
|
||||
body = clear(body);
|
||||
return strConcatenation(body);
|
||||
};
|
||||
|
||||
// 标签风格
|
||||
if (options.tagStyle === 'modern') { // 2.11+ 版本风格
|
||||
// 注释标签 - 仅在模板中显示,不进行解析,也不在视图中输出
|
||||
tpl = tpl.replace(tagRegex(['#'], delimSides), '');
|
||||
// 输出标签
|
||||
tpl = tpl.replace(tagRegex(['(=|-)']), output);
|
||||
// Scriptlet 标签
|
||||
tpl = tpl.replace(tagRegex([], delimSides), statement);
|
||||
} else { // < 2.11 版本风格
|
||||
// Scriptlet 标签
|
||||
tpl = tpl.replace(tagRegex(['#'], delimSides), statement);
|
||||
// 输出标签
|
||||
tpl = tpl.replace(tagRegex(['(=|-)*']), output);
|
||||
}
|
||||
|
||||
// 恢复换行符
|
||||
if (!condense) {
|
||||
tpl = tpl.replace(regex(placeholder), '\\n');
|
||||
}
|
||||
|
||||
return tpl;
|
||||
};
|
||||
|
||||
// 创建模板编译器
|
||||
var createCompiler = that.createCompiler = function(template) {
|
||||
var codeBuilder = [
|
||||
'function(d){',
|
||||
'"use strict";',
|
||||
'var __laytpl__="",'+
|
||||
function() { // 内部变量
|
||||
// 内部方法
|
||||
var arr = [];
|
||||
for (var key in that.vars) {
|
||||
arr.push(((key === 'escape' ? '_' : '') + key) +'=laytpl.'+ key);
|
||||
}
|
||||
return arr.join(',');
|
||||
}() + ';',
|
||||
'__laytpl__="'+ parse(template) +'";',
|
||||
'return __laytpl__;',
|
||||
// '__laytpl__.push("'+ parse(template) +'");',
|
||||
// 'return __laytpl__.join("");',
|
||||
'};'
|
||||
].join('\n');
|
||||
// console.log(codeBuilder);
|
||||
|
||||
try {
|
||||
/**
|
||||
* 请注意: 开发者在使用模板语法时,需确保模板中的 JS 语句不来自于页面用户输入。
|
||||
* 即模板中的 JS 语句必须在页面开发者自身的可控范围内,否则请避免使用该模板解析。
|
||||
*/
|
||||
that.cache = template = new Function('d, laytpl', template);
|
||||
return template(data, tool);
|
||||
return new Function('laytpl', 'return '+ codeBuilder)(that.vars);
|
||||
};
|
||||
|
||||
try {
|
||||
return createCompiler(template); // 返回编译器
|
||||
} catch(e) {
|
||||
delete that.cache;
|
||||
return inner.error(e, source);
|
||||
delete that.compilerCache;
|
||||
return function() {
|
||||
return tools.error(e, {
|
||||
debug: that.checkErrorArea(source),
|
||||
template: source,
|
||||
type: 'Compile'
|
||||
}, options.error);
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
// 数据渲染
|
||||
Class.prototype.render = function(data, callback){
|
||||
data = data || {};
|
||||
|
||||
/**
|
||||
* 校验出错区域
|
||||
* @param {string} source - 原始模板
|
||||
* @param {Object} data - 数据
|
||||
* @returns {string} 出错区域的模板碎片
|
||||
*/
|
||||
Class.prototype.checkErrorArea = function(source, data) {
|
||||
var that = this;
|
||||
var result = that.cache ? that.cache(data, tool) : that.parse(that.template, data);
|
||||
var srcs = source.split(/\n/g);
|
||||
var validLine = -1; // 有效行
|
||||
|
||||
// 返回渲染结果
|
||||
typeof callback === 'function' && callback(result);
|
||||
return result;
|
||||
};
|
||||
|
||||
// 创建实例
|
||||
var laytpl = function(template, options){
|
||||
return new Class(template, options);
|
||||
};
|
||||
|
||||
// 配置全局属性
|
||||
laytpl.config = function(options){
|
||||
options = options || {};
|
||||
for(var i in options){
|
||||
config[i] = options[i];
|
||||
// 逐行查找
|
||||
var i = 0;
|
||||
var str = '';
|
||||
var len = srcs.length;
|
||||
for (; i < len; i++) {
|
||||
str += srcs[i];
|
||||
try {
|
||||
data
|
||||
? that.createCompiler(str)(data)
|
||||
: new Function('"use strict";_laytpl__="'+ that.parse(str) +'";');
|
||||
validLine = i;
|
||||
} catch(e) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// 呈现模板出错大致区域
|
||||
var errorArea = function(errLine) {
|
||||
var arr = [];
|
||||
var addLine = 3; // 错误行上下延伸的行数
|
||||
var i = 0;
|
||||
var len = srcs.length;
|
||||
|
||||
if (errLine < 0) errLine = 0;
|
||||
if (errLine > len - 1) errLine = len - 1;
|
||||
|
||||
i = errLine - addLine;
|
||||
if (i < 0) i = 0;
|
||||
|
||||
for (; i < len; i++) {
|
||||
arr.push((i == errLine ? '? ' : ' ') +(i + 1)+ '| '+ srcs[i]);
|
||||
if (i >= errLine + addLine) break;
|
||||
}
|
||||
|
||||
return '\n'+ arr.join('\n');
|
||||
};
|
||||
|
||||
return errorArea(validLine + 1); // 有效行的下一行即为出错行
|
||||
};
|
||||
|
||||
laytpl.v = '2.0.0';
|
||||
/**
|
||||
* 创建实例
|
||||
* @param {string} template - 模板
|
||||
* @param {Object} options - 选项
|
||||
* @returns
|
||||
*/
|
||||
var laytpl = function(template, options) {
|
||||
var inst = new Class(template, options);
|
||||
return thisModule.call(inst);
|
||||
};
|
||||
|
||||
// export
|
||||
exports('laytpl', laytpl);
|
||||
});
|
||||
/**
|
||||
* 扩展模板内部变量
|
||||
* @param {Object} variables - 扩展内部变量,变量值通常为函数
|
||||
*/
|
||||
laytpl.extendVars = function(variables) {
|
||||
Object.assign(vars, variables);
|
||||
};
|
||||
|
||||
/**
|
||||
* 设置默认配置
|
||||
* @param {Object} options - 选项
|
||||
*/
|
||||
laytpl.config = laytpl.set = function(options) {
|
||||
Object.assign(config, options);
|
||||
};
|
||||
|
||||
// 输出接口
|
||||
typeof module === 'object' && typeof exports === 'object'
|
||||
? module.exports = laytpl // CommonJS
|
||||
: ( // 浏览器
|
||||
typeof layui === 'object' ? layui.define(function(exports) { // Layui
|
||||
exports(MOD_NAME, laytpl);
|
||||
}) : (
|
||||
typeof define === 'function' && define.amd ? define(function() { // RequireJS
|
||||
return laytpl;
|
||||
}) : global.laytpl = laytpl // 单独引入
|
||||
)
|
||||
);
|
||||
})(this);
|
Loading…
Reference in New Issue