You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
617 lines
14 KiB
JavaScript
617 lines
14 KiB
JavaScript
/*!
|
|
* artTemplate - Template Engine
|
|
* https://github.com/aui/artTemplate
|
|
* Released under the MIT, BSD, and GPL Licenses
|
|
*/
|
|
|
|
|
|
/**
|
|
* 模板引擎路由函数
|
|
* 若第二个参数类型为 Object 则执行 render 方法, 否则 compile 方法
|
|
* @name template
|
|
* @param {String} 模板ID (可选)
|
|
* @param {Object, String} 数据或者模板字符串
|
|
* @return {String, Function} 渲染好的HTML字符串或者渲染方法
|
|
*/
|
|
var template = function (id, content) {
|
|
return template[
|
|
typeof content === 'object' ? 'render' : 'compile'
|
|
].apply(template, arguments);
|
|
};
|
|
|
|
|
|
|
|
|
|
(function (exports, global) {
|
|
|
|
|
|
'use strict';
|
|
exports.version = '2.0.1';
|
|
exports.openTag = '<%'; // 设置逻辑语法开始标签
|
|
exports.closeTag = '%>'; // 设置逻辑语法结束标签
|
|
exports.isEscape = true; // HTML字符编码输出开关
|
|
exports.isCompress = false; // 剔除渲染后HTML多余的空白开关
|
|
exports.parser = null; // 自定义语法插件接口
|
|
|
|
|
|
|
|
/**
|
|
* 渲染模板
|
|
* @name template.render
|
|
* @param {String} 模板ID
|
|
* @param {Object} 数据
|
|
* @return {String} 渲染好的HTML字符串
|
|
*/
|
|
exports.render = function (id, data) {
|
|
|
|
var cache = _getCache(id);
|
|
|
|
if (cache === undefined) {
|
|
|
|
return _debug({
|
|
id: id,
|
|
name: 'Render Error',
|
|
message: 'No Template'
|
|
});
|
|
|
|
}
|
|
|
|
return cache(data);
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
* 编译模板
|
|
* 2012-6-6:
|
|
* define 方法名改为 compile,
|
|
* 与 Node Express 保持一致,
|
|
* 感谢 TooBug 提供帮助!
|
|
* @name template.compile
|
|
* @param {String} 模板ID (可选)
|
|
* @param {String} 模板字符串
|
|
* @return {Function} 渲染方法
|
|
*/
|
|
exports.compile = function (id, source) {
|
|
|
|
var params = arguments;
|
|
var isDebug = params[2];
|
|
var anonymous = 'anonymous';
|
|
|
|
if (typeof source !== 'string') {
|
|
isDebug = params[1];
|
|
source = params[0];
|
|
id = anonymous;
|
|
}
|
|
|
|
|
|
try {
|
|
|
|
var Render = _compile(source, isDebug);
|
|
|
|
} catch (e) {
|
|
|
|
e.id = id || source;
|
|
e.name = 'Syntax Error';
|
|
|
|
return _debug(e);
|
|
|
|
}
|
|
|
|
|
|
function render (data) {
|
|
|
|
try {
|
|
|
|
return new Render(data) + '';
|
|
|
|
} catch (e) {
|
|
|
|
if (!isDebug) {
|
|
return exports.compile(id, source, true)(data);
|
|
}
|
|
|
|
e.id = id || source;
|
|
e.name = 'Render Error';
|
|
e.source = source;
|
|
|
|
return _debug(e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
render.prototype = Render.prototype;
|
|
render.toString = function () {
|
|
return Render.toString();
|
|
};
|
|
|
|
|
|
if (id !== anonymous) {
|
|
_cache[id] = render;
|
|
}
|
|
|
|
|
|
return render;
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
* 添加模板辅助方法
|
|
* @name template.helper
|
|
* @param {String} 名称
|
|
* @param {Function} 方法
|
|
*/
|
|
exports.helper = function (name, helper) {
|
|
exports.prototype[name] = helper;
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
* 模板错误事件
|
|
* @name template.onerror
|
|
* @event
|
|
*/
|
|
exports.onerror = function (e) {
|
|
var content = '[template]:\n'
|
|
+ e.id
|
|
+ '\n\n[name]:\n'
|
|
+ e.name;
|
|
|
|
if (e.message) {
|
|
content += '\n\n[message]:\n'
|
|
+ e.message;
|
|
}
|
|
|
|
if (e.line) {
|
|
content += '\n\n[line]:\n'
|
|
+ e.line;
|
|
content += '\n\n[source]:\n'
|
|
+ e.source.split(/\n/)[e.line - 1].replace(/^[\s\t]+/, '');
|
|
}
|
|
|
|
if (e.temp) {
|
|
content += '\n\n[temp]:\n'
|
|
+ e.temp;
|
|
}
|
|
|
|
if (global.console) {
|
|
console.error(content);
|
|
}
|
|
};
|
|
|
|
|
|
|
|
// 编译好的函数缓存
|
|
var _cache = {};
|
|
|
|
|
|
|
|
// 获取模板缓存
|
|
var _getCache = function (id) {
|
|
|
|
var cache = _cache[id];
|
|
|
|
if (cache === undefined && 'document' in global) {
|
|
var elem = document.getElementById(id);
|
|
|
|
if (elem) {
|
|
var source = elem.value || elem.innerHTML;
|
|
return exports.compile(id, source.replace(/^\s*|\s*$/g, ''));
|
|
}
|
|
|
|
} else if (_cache.hasOwnProperty(id)) {
|
|
|
|
return cache;
|
|
}
|
|
};
|
|
|
|
|
|
|
|
// 模板调试器
|
|
var _debug = function (e) {
|
|
|
|
exports.onerror(e);
|
|
|
|
function error () {
|
|
return error + '';
|
|
}
|
|
|
|
error.toString = function () {
|
|
return '{Template Error}';
|
|
};
|
|
|
|
return error;
|
|
};
|
|
|
|
|
|
|
|
// 模板编译器
|
|
var _compile = (function () {
|
|
|
|
|
|
// 辅助方法集合
|
|
exports.prototype = {
|
|
$render: exports.render,
|
|
$escape: function (content) {
|
|
|
|
return typeof content === 'string'
|
|
? content.replace(/&(?![\w#]+;)|[<>"']/g, function (s) {
|
|
return {
|
|
"<": "<",
|
|
">": ">",
|
|
'"': """,
|
|
"'": "'",
|
|
"&": "&"
|
|
}[s];
|
|
})
|
|
: content;
|
|
},
|
|
$string: function (value) {
|
|
|
|
if (typeof value === 'string' || typeof value === 'number') {
|
|
|
|
return value;
|
|
|
|
} else if (typeof value === 'function') {
|
|
|
|
return value();
|
|
|
|
} else {
|
|
|
|
return '';
|
|
|
|
}
|
|
|
|
}
|
|
};
|
|
|
|
|
|
var arrayforEach = Array.prototype.forEach || function (block, thisObject) {
|
|
var len = this.length >>> 0;
|
|
|
|
for (var i = 0; i < len; i++) {
|
|
if (i in this) {
|
|
block.call(thisObject, this[i], i, this);
|
|
}
|
|
}
|
|
|
|
};
|
|
|
|
|
|
// 数组迭代
|
|
var forEach = function (array, callback) {
|
|
arrayforEach.call(array, callback);
|
|
};
|
|
|
|
|
|
// 静态分析模板变量
|
|
var KEYWORDS =
|
|
// 关键字
|
|
'break,case,catch,continue,debugger,default,delete,do,else,false'
|
|
+ ',finally,for,function,if,in,instanceof,new,null,return,switch,this'
|
|
+ ',throw,true,try,typeof,var,void,while,with'
|
|
|
|
// 保留字
|
|
+ ',abstract,boolean,byte,char,class,const,double,enum,export,extends'
|
|
+ ',final,float,goto,implements,import,int,interface,long,native'
|
|
+ ',package,private,protected,public,short,static,super,synchronized'
|
|
+ ',throws,transient,volatile'
|
|
|
|
// ECMA 5 - use strict
|
|
+ ',arguments,let,yield'
|
|
|
|
+ ',undefined';
|
|
var REMOVE_RE = /\/\*(?:.|\n)*?\*\/|\/\/[^\n]*\n|\/\/[^\n]*$|'[^']*'|"[^"]*"|[\s\t\n]*\.[\s\t\n]*[$\w\.]+/g;
|
|
var SPLIT_RE = /[^\w$]+/g;
|
|
var KEYWORDS_RE = new RegExp(["\\b" + KEYWORDS.replace(/,/g, '\\b|\\b') + "\\b"].join('|'), 'g');
|
|
var NUMBER_RE = /\b\d[^,]*/g;
|
|
var BOUNDARY_RE = /^,+|,+$/g;
|
|
var getVariable = function (code) {
|
|
|
|
code = code
|
|
.replace(REMOVE_RE, '')
|
|
.replace(SPLIT_RE, ',')
|
|
.replace(KEYWORDS_RE, '')
|
|
.replace(NUMBER_RE, '')
|
|
.replace(BOUNDARY_RE, '');
|
|
|
|
code = code ? code.split(/,+/) : [];
|
|
|
|
return code;
|
|
};
|
|
|
|
|
|
return function (source, isDebug) {
|
|
|
|
var openTag = exports.openTag;
|
|
var closeTag = exports.closeTag;
|
|
var parser = exports.parser;
|
|
|
|
|
|
var code = source;
|
|
var tempCode = '';
|
|
var line = 1;
|
|
var uniq = {$data:true,$helpers:true,$out:true,$line:true};
|
|
var helpers = exports.prototype;
|
|
var prototype = {};
|
|
|
|
|
|
var variables = "var $helpers=this,"
|
|
+ (isDebug ? "$line=0," : "");
|
|
|
|
var isNewEngine = ''.trim;// '__proto__' in {}
|
|
var replaces = isNewEngine
|
|
? ["$out='';", "$out+=", ";", "$out"]
|
|
: ["$out=[];", "$out.push(", ");", "$out.join('')"];
|
|
|
|
var concat = isNewEngine
|
|
? "if(content!==undefined){$out+=content;return content}"
|
|
: "$out.push(content);";
|
|
|
|
var print = "function(content){" + concat + "}";
|
|
|
|
var include = "function(id,data){"
|
|
+ "if(data===undefined){data=$data}"
|
|
+ "var content=$helpers.$render(id,data);"
|
|
+ concat
|
|
+ "}";
|
|
|
|
|
|
// html与逻辑语法分离
|
|
forEach(code.split(openTag), function (code, i) {
|
|
code = code.split(closeTag);
|
|
|
|
var $0 = code[0];
|
|
var $1 = code[1];
|
|
|
|
// code: [html]
|
|
if (code.length === 1) {
|
|
|
|
tempCode += html($0);
|
|
|
|
// code: [logic, html]
|
|
} else {
|
|
|
|
tempCode += logic($0);
|
|
|
|
if ($1) {
|
|
tempCode += html($1);
|
|
}
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
code = tempCode;
|
|
|
|
|
|
// 调试语句
|
|
if (isDebug) {
|
|
code = 'try{' + code + '}catch(e){'
|
|
+ 'e.line=$line;'
|
|
+ 'throw e'
|
|
+ '}';
|
|
}
|
|
|
|
|
|
code = "'use strict';"
|
|
+ variables + replaces[0] + code
|
|
+ 'return new String(' + replaces[3] + ')';
|
|
|
|
|
|
try {
|
|
|
|
var Render = new Function('$data', code);
|
|
Render.prototype = prototype;
|
|
|
|
return Render;
|
|
|
|
} catch (e) {
|
|
e.temp = 'function anonymous($data) {' + code + '}';
|
|
throw e;
|
|
}
|
|
|
|
|
|
|
|
|
|
// 处理 HTML 语句
|
|
function html (code) {
|
|
|
|
// 记录行号
|
|
line += code.split(/\n/).length - 1;
|
|
|
|
if (exports.isCompress) {
|
|
code = code.replace(/[\n\r\t\s]+/g, ' ');
|
|
}
|
|
|
|
code = code
|
|
// 单引号与反斜杠转义(因为编译后的函数默认使用单引号,因此双引号无需转义)
|
|
.replace(/('|\\)/g, '\\$1')
|
|
// 换行符转义(windows + linux)
|
|
.replace(/\r/g, '\\r')
|
|
.replace(/\n/g, '\\n');
|
|
|
|
code = replaces[1] + "'" + code + "'" + replaces[2];
|
|
|
|
return code + '\n';
|
|
}
|
|
|
|
|
|
// 处理逻辑语句
|
|
function logic (code) {
|
|
|
|
var thisLine = line;
|
|
|
|
if (parser) {
|
|
|
|
// 语法转换插件钩子
|
|
code = parser(code);
|
|
|
|
} else if (isDebug) {
|
|
|
|
// 记录行号
|
|
code = code.replace(/\n/g, function () {
|
|
line ++;
|
|
return '$line=' + line + ';';
|
|
});
|
|
|
|
}
|
|
|
|
|
|
// 输出语句. 转义: <%=value%> 不转义:<%==value%>
|
|
if (code.indexOf('=') === 0) {
|
|
|
|
var isEscape = code.indexOf('==') !== 0;
|
|
|
|
code = code.replace(/^=*|[\s;]*$/g, '');
|
|
|
|
if (isEscape && exports.isEscape) {
|
|
|
|
// 转义处理,但排除辅助方法
|
|
var name = code.replace(/\s*\([^\)]+\)/, '');
|
|
if (
|
|
!helpers.hasOwnProperty(name)
|
|
&& !/^(include|print)$/.test(name)
|
|
) {
|
|
code = '$escape($string(' + code + '))';
|
|
}
|
|
|
|
} else {
|
|
code = '$string(' + code + ')';
|
|
}
|
|
|
|
|
|
code = replaces[1] + code + replaces[2];
|
|
|
|
}
|
|
|
|
if (isDebug) {
|
|
code = '$line=' + thisLine + ';' + code;
|
|
}
|
|
|
|
getKey(code);
|
|
|
|
return code + '\n';
|
|
}
|
|
|
|
|
|
// 提取模板中的变量名
|
|
function getKey (code) {
|
|
|
|
code = getVariable(code);
|
|
|
|
// 分词
|
|
forEach(code, function (name) {
|
|
|
|
// 除重
|
|
if (!uniq.hasOwnProperty(name)) {
|
|
setValue(name);
|
|
uniq[name] = true;
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
// 声明模板变量
|
|
// 赋值优先级:
|
|
// 内置特权方法(include, print) > 私有模板辅助方法 > 数据 > 公用模板辅助方法
|
|
function setValue (name) {
|
|
|
|
var value;
|
|
|
|
if (name === 'print') {
|
|
|
|
value = print;
|
|
|
|
} else if (name === 'include') {
|
|
|
|
prototype['$render'] = helpers['$render'];
|
|
value = include;
|
|
|
|
} else {
|
|
|
|
value = '$data.' + name;
|
|
|
|
if (helpers.hasOwnProperty(name)) {
|
|
|
|
prototype[name] = helpers[name];
|
|
|
|
if (name.indexOf('$') === 0) {
|
|
value = '$helpers.' + name;
|
|
} else {
|
|
value = value
|
|
+ '===undefined?$helpers.' + name + ':' + value;
|
|
}
|
|
}
|
|
|
|
|
|
}
|
|
|
|
variables += name + '=' + value + ',';
|
|
}
|
|
|
|
|
|
};
|
|
})();
|
|
|
|
|
|
|
|
|
|
})(template, this);
|
|
|
|
|
|
// RequireJS || SeaJS
|
|
if (typeof define === 'function') {
|
|
define(function(require, exports, module) {
|
|
module.exports = template;
|
|
});
|
|
// NodeJS
|
|
} else if (typeof exports !== 'undefined') {
|
|
module.exports = template;
|
|
}
|
|
|
|
Cute.template = function(tplname, data, extend){
|
|
tplname = "_" + tplname.toUpperCase() + "_TPL_";
|
|
template.openTag = '<#';
|
|
template.closeTag = '#>';
|
|
template.helper('$', $);
|
|
template.helper('Cute', Cute);
|
|
template.helper('window', window);
|
|
|
|
$.each(extend || {}, function(i,item){
|
|
template.helper(i, item);
|
|
});
|
|
return template.render(tplname,data);
|
|
};
|
|
Cute.template.helper = template.helper;
|
|
// function(tplname, data, isCached) { //模板
|
|
// if (!this._templateCache) this._templateCache = {};
|
|
// tplname = "_" + tplname.toUpperCase() + "_TPL_";
|
|
// var func = this._templateCache[tplname];
|
|
// null == data && (data = {});
|
|
// if (!func) {
|
|
// var tpl = $("#" + tplname).html();//.replace(/</g, "<").replace(/>/g, ">");
|
|
// func = new Function("obj", "var _=[];with(obj){_.push('" +
|
|
// tpl.replace(/[\r\t\n]/g, " ")
|
|
// .replace(/'(?=[^#]*#>)/g, "\t")
|
|
// .split("'").join("\\'")
|
|
// .split("\t").join("'")
|
|
// .replace(/<#=(.+?)#>/g, "',$1,'")
|
|
// .split("<#").join("');")
|
|
// .split("#>").join("_.push('")
|
|
// + "');}return _.join('');");
|
|
// (null == isCached || true === isCached) && (this._templateCache[tplname] = func);
|
|
// }
|
|
// return func(data);
|
|
// }
|