# Widget模式
Web Widget: Web Widget 指的是一块可以在任意页面中执行的代码块。
Widget 模式是指借用 Web Widget 思想,将页面分解成部件,针对部件开发,最终组合成完整的页面。
# 视图模块化
在Widget模式中,开发一个组件通常对应一个文件,而不是某个功能或者某个视图。
在这个组件文件中,我们要做两件事,第一创建视图,第二添加相应的功能。
# 模板引擎
创建视图要借用到简单模模式的思想,用服务端请求来的数据格式化我们视图模板,实现对视图的创建。
我们要封装一个模板功能组件,让他可以对页面元素模板,script 模板,表单模板,字符串模板格式化,甚至可以编译并执行 JavaScript 语句。
# 实现原理
要实现的效果如下所示
将模板
<a
href="#"
class="data-lang {% if(is_selected) { %} selected {% } %}"
value="{%=value%}"
>{%=text%}</a>
和数据
{
is_selected: true,
value: "zh",
text: "zh-text"
}
渲染输出结果
<a href="#" class="data-lang selected" value="zh">zh-text</a>
# 基本结构
- 处理数据
- 获取模板
- 处理模板
- 编译执行
定义
// lib/template.js
F.module("lib/template",function(){
const _TplEngine = function(){};
const _getTpl = function(){}
const _dealTpl = function(){}
const _compileTpl = function(){};
return _TplEngine;
})
使用
F.module(["lib/template"],function(template){
// do something
})
# 处理数据
/**
* 处理数据与编译模板入口
* @param {string} str 模板容器的id或模板字符串
* @param {Object} data 需要渲染的数据
* @returns
*/
const _TplEngine = function (str, data) {
if (data instanceof Array) {
let html = "";
let i = 0;
const len = data.length;
for (; i < len; i++) {
html += _getTpl(str)(data[i]);
}
} else {
return _getTpl(str)(data);
}
};
# 获取模板
/**
* 获取模板
* @param {string} str 模板容器的id或模板字符串
*/
const _getTpl = function (str) {
const ele = document.getElementById(str);
if (ele) {
// id
const html = /^(textarea|input)$/.test(ele.nodeName) ? ele.value : ele.innerHTML;
return _compileTpl(html);
} else {
// template
return _compileTpl(html);
}
};
# 处理模板
处理模板的流程比较复杂,举例说明一下
举例
需要处理的模板
<a>{%=test%}</a>
处理后的形式
templateList.push('<a>',typeof(test) === 'undefined' ? '': test,'</a>')
- 首先,将传入的内容转化为字符串
String(str)
- 将 html 标签内常用的的
<
和>
分别转义为<
和>
- 将三类空白符号过滤掉 回车符
\r
,制表符\t
,换行符\n
- 将插值表达式转化成
',typeof($1) === 'undefined' ? '' : $1,'
- 将
{%
替换为');
- 将
%}
替换成templateList.push('
/**
* 处理模板
* @param {*} str 模板字符串
* @returns
*/
const _dealTpl = function (str) {
return String(str)
.trim()
.replace(/$lt;/g, "<")
.replace(/$gt;/g, ">")
.replace(/[\r\t\n]/g, "")
.replace(/%}\s+{%/g, "")
.replace(/{%=(.*?)%}/g, `',typeof($1)==='undefined' ? '' : $1,'`)
.replace(/{%/g, `');\n`)
.replace(/%}/g, `\n\ttemplateList.push('`)
.replace(/push\('\s+/g, "push('")
.replace(/>\s+'\);/g, ">\\n');");
};
# 编译执行
/**
* 编译并执行模板字符串
* @param {*} str 模板字符串
* @returns
*/
const _compileTpl = function (str) {
const fnBody = `
var templateList = [];
var fn = (function (data) {
var templateKey = "";
for (key in data) {
templateKey += ('var ' + key + ' = data[\"' + key +'\"]');
}
eval(templateKey);
templateList.push('${_dealTpl(str)}');
templateKey = null;
})(templateData);
fn = null;
return templateList.join('');`;
const _compile = new Function("templateData", fnBody)
return _compile;
};
var templateList = [];
var fn = (function (data) {
var templateKey = "";
for (key in data) {
templateKey += ('var ' + key + ' = data[\"' + key +'\"]');
}
eval(templateKey);
templateList.push('${_dealTpl(str)}');
templateKey = null;
})(templateData);
fn = null;
return templateList.join('');
# 使用
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="./asyncModuleManager.js"></script>
<title>widgetMode</title>
</head>
<body>
<div id="app"></div>
<script type="text/template" id="demo_script">
<div id="tag_cloud">
{% for(var i = 0, len = tagCloud.length; i < len; i++) { %}
{% const ctx = tagCloud[i]; %}
<a href="#" class="tag_item {% if(ctx.is_selected){ %} selected {% } %}" title="{%=ctx.title%}" >{%=ctx.text%}</a>
{% } %}
</div>
</script>
<script>
const data = {
tagCloud: [
{ is_selected: true, title: "这是一本设计模式书", text: "设计模式" },
{ is_selected: false, title: "这是一本HTML书", text: "HTML" },
{ is_selected: false, title: "这是一本CSS书", text: "CSS" },
{ is_selected: true, title: "这是一本JavaScript书", text: "JavaScript" }
]
}
const fetchData = () => data
F.module(["lib/template", "lib/dom"], function (template, dom) {
const data = fetchData()
// 获取数据
const htmlStr = template("demo_script", data);
console.log(htmlStr)
dom.html("app", htmlStr)
// 其他逻辑
// ...
})
</script>
</body>
</html>
渲染结果
编译后的templaeData
函数如下所示:
ƒ anonymous(templateData) {
var templateList = [];
var fn = (function (data) {
var templateKey = "";
for (key in data) {
templateKey += ('var ' + key + ' = data["' + key +'"]');
}
eval(templateKey);
templateList.push('<div id="tag_cloud">\n');
for(var i = 0, len = tagCloud.length; i < len; i++) { const ctx = tagCloud[i];
templateList.push('<a href="#" class="tag_item ');
if(ctx.is_selected){
templateList.push('selected ');
}
templateList.push('" title="',typeof(ctx.title)==='undefined' ? '' : ctx.title,'" >',typeof(ctx.text)==='undefined' ? '' : ctx.text,'</a>\n');
}
templateList.push('</div>');
templateKey = null;
})(templateData);
fn = null;
return templateList.join('');
}
替换数据后的渲染出的HTML
字符串如下所示:
<div id="tag_cloud">
<a href="#" class="tag_item selected " title="这是一本设计模式书" >设计模式</a>
<a href="#" class="tag_item " title="这是一本HTML书" >HTML</a>
<a href="#" class="tag_item " title="这是一本CSS书" >CSS</a>
<a href="#" class="tag_item selected " title="这是一本JavaScript书" >JavaScript</a>
</div>
# 完整代码
DETAILS
// lib/template.js
F.module("lib/template", function () {
/**
* 处理数据与编译模板入口
* @param {string} str 模板容器的id或模板字符串
* @param {Object} data 需要渲染的数据
* @returns
*/
const _TplEngine = function (str, data) {
if (data instanceof Array) {
let html = "";
let i = 0;
const len = data.length;
for (; i < len; i++) {
html += _getTpl(str)(data[i]);
}
} else {
return _getTpl(str)(data);
}
};
/**
* 获取模板
* @param {string} str 模板容器的id或模板字符串
*/
const _getTpl = function (str) {
const ele = document.getElementById(str);
if (ele) {
// id
const html = /^(textarea|input)$/.test(ele.nodeName) ? ele.value : ele.innerHTML;
return _compileTpl(html);
} else {
// template
return _compileTpl(html);
}
};
/**
* 处理模板
* @param {*} str 模板字符串
* @returns
*/
const _dealTpl = function (str) {
return String(str)
.trim()
.replace(/$lt;/g, "<")
.replace(/$gt;/g, ">")
.replace(/[\r\t\n]/g, "")
.replace(/%}\s+{%/g, "")
.replace(/{%=(.*?)%}/g, `',typeof($1)==='undefined' ? '' : $1,'`)
.replace(/{%/g, `');\n`)
.replace(/%}/g, `\n\ttemplateList.push('`)
.replace(/push\('\s+/g, "push('")
.replace(/>\s+'\);/g, ">\\n');");
};
/**
* 编译并执行模板字符串
* @param {*} str 模板字符串
* @returns
*/
const _compileTpl = function (str) {
const fnBody = `
var templateList = [];
var fn = (function (data) {
var templateKey = "";
for (key in data) {
templateKey += ('var ' + key + ' = data[\"' + key +'\"]');
}
eval(templateKey);
templateList.push('${_dealTpl(str)}');
templateKey = null;
})(templateData);
fn = null;
return templateList.join('');`;
const _compile = new Function("templateData", fnBody)
return _compile;
};
return _TplEngine;
});