sodajs
Version:
Light weight but powerful template engine for JavaScript.
421 lines (320 loc) • 11.6 kB
JavaScript
import {
IDENTOR_REG,
STRING_REG,
NUMBER_REG,
OBJECT_REG,
OBJECT_REG_NG,
ATTR_REG,
ATTR_REG_NG,
ATTR_REG_DOT,
NOT_ATTR_REG,
OR_REG,
OR_REPLACE,
CONST_PRIFIX,
CONST_REG,
CONST_REGG,
VALUE_OUT_REG,
ONLY_VALUE_OUT_REG
} from './const';
import {
getAttrVarKey,
getRandom,
exist,
nodes2Arr
} from './util';
var doc = typeof document !== 'undefined' ? document : {};
export default class Soda{
static sodaDirectives = [];
static sodaFilterMap = {};
static template = {};
constructor(prefix = 'soda-'){
this._prefix = prefix;
}
setDocument(_doc){
doc = _doc;
}
run(str, data){
// 解析模板DOM
var div = doc.createElement("div");
// 必须加入到body中去,不然自定义标签不生效
if (doc.documentMode < 9) {
div.style.display = 'none';
doc.body.appendChild(div);
}
div.innerHTML = str;
nodes2Arr(div.childNodes).map(child => {
this.compileNode(child, data);
});
var innerHTML = div.innerHTML;
if (doc.documentMode < 9) {
doc.body.removeChild(div);
}
return innerHTML;
}
prefix(prefix){
this._prefix = prefix;
}
_getPrefixReg(){
return new RegExp('^' + this._prefix);
}
_getPrefixedDirectiveMap(){
var map = {};
Soda.sodaDirectives.map(item => {
var prefixedName = this._prefix + item.name;
map[prefixedName] = item;
});
return map;
}
_removeSodaMark(node, name){
node.removeAttribute(name);
}
compileNode(node, scope){
let prefixReg = this._getPrefixReg();
let {
sodaDirectives
} = Soda;
let prefixedDirectiveMap = this._getPrefixedDirectiveMap();
let compile = (node, scope) => {
// 如果只是文本
// parseTextNode
if (node.nodeType === (node.TEXT_NODE || 3)) {
node.nodeValue = node.nodeValue.replace(VALUE_OUT_REG, (item, $1) => {
var value = this.parseSodaExpression($1, scope);
if (typeof value === "object") {
value = JSON.stringify(value, null, 2)
}
return value;
});
}
// parse Attributes
if (node.attributes && node.attributes.length) {
// 指令优先处理
sodaDirectives.map(item => {
let {
name,
opt
} = item;
let prefixedName = this._prefix + name;
// 这里移除了对parentNode的判断
// 允许使用无值的指令
if (exist(node.getAttribute(prefixedName))) {
let expression = node.getAttribute(prefixedName);
opt.link.bind(this)({
expression,
scope,
el: node,
parseSodaExpression: this.parseSodaExpression.bind(this),
getValue: this.getValue.bind(this),
compileNode: this.compileNode.bind(this),
document: doc
});
// 移除标签
this._removeSodaMark(node, prefixedName);
}
});
// 处理输出 包含 prefix-*
nodes2Arr(node.attributes)
// 过滤掉指令里包含的属性
.filter(
attr =>
! prefixedDirectiveMap[attr.name]
)
.map(attr => {
if (prefixReg.test(attr.name)) {
var attrName = attr.name.replace(prefixReg, '');
if (attrName && exist(attr.value)) {
var attrValue = this.parseComplexExpression(attr.value, scope);
if(attrValue !== false && exist(attrValue)){
node.setAttribute(attrName, attrValue);
}
this._removeSodaMark(node, attr.name);
}
// 对其他属性里含expr 处理
} else {
if (exist(attr.value)) {
attr.value = this.parseComplexExpression(attr.value, scope);
}
}
});
}
// parse childNodes
nodes2Arr(node.childNodes).map(child => {
compile(child, scope);
});
};
compile(node, scope);
}
getEvalFunc(expr){
var evalFunc = new Function("getValue", "sodaFilterMap", "return function sodaExp(scope){ return " + expr + "}")(this.getValue, Soda.sodaFilterMap);
return evalFunc;
}
getValue(_data, _attrStr) {
CONST_REGG.lastIndex = 0;
var realAttrStr = _attrStr.replace(CONST_REGG, function(r) {
if (typeof _data[r] === "undefined") {
return r;
} else {
return _data[r];
}
});
if (_attrStr === 'true') {
return true;
}
if (_attrStr === 'false') {
return false;
}
var _getValue = function(data, attrStr) {
var dotIndex = attrStr.indexOf(".");
if (dotIndex > -1) {
var attr = attrStr.substr(0, dotIndex);
attrStr = attrStr.substr(dotIndex + 1);
// 检查attrStr是否属于变量并转换
if (typeof _data[attr] !== "undefined" && CONST_REG.test(attr)) {
attr = _data[attr];
}
if (typeof data[attr] !== "undefined" && data[attr] !== null) {
return _getValue(data[attr], attrStr);
} else {
var eventData = {
name: realAttrStr,
data: _data
};
// 如果还有
return "";
}
} else {
attrStr = attrStr.trim();
// 检查attrStr是否属于变量并转换
if (typeof _data[attrStr] !== "undefined" && CONST_REG.test(attrStr)) {
attrStr = _data[attrStr];
}
var rValue;
if (typeof data[attrStr] !== "undefined") {
rValue = data[attrStr];
} else {
var eventData = {
name: realAttrStr,
data: _data
};
rValue = "";
}
return rValue;
}
};
return _getValue(_data, _attrStr);
}
// 解析混合表达式
parseComplexExpression(str, scope){
var onlyResult = ONLY_VALUE_OUT_REG.exec(str);
if(onlyResult){
var sodaExp = onlyResult[1];
return this.parseSodaExpression(sodaExp, scope);
}
return str.replace(VALUE_OUT_REG, (item, $1) => {
return this.parseSodaExpression($1, scope);
});
}
parseSodaExpression(str, scope) {
// 将字符常量保存下来
str = str.replace(STRING_REG, function(r, $1, $2) {
var key = getRandom();
scope[key] = $1 || $2;
return key;
});
// 对filter进行处理
str = str.replace(OR_REG, OR_REPLACE).split("|");
for (var i = 0; i < str.length; i++) {
str[i] = (str[i].replace(new RegExp(OR_REPLACE, 'g'), "||") || '').trim();
}
var expr = str[0] || "";
var filters = str.slice(1);
while (ATTR_REG_NG.test(expr)) {
ATTR_REG.lastIndex = 0;
//对expr预处理
expr = expr.replace(ATTR_REG, (r, $1) => {
var key = getAttrVarKey();
// 属性名称为字符常量
var attrName = this.parseSodaExpression($1, scope);
// 给一个特殊的前缀 表示是属性变量
scope[key] = attrName;
return "." + key;
});
}
expr = expr.replace(OBJECT_REG, function(value) {
return "getValue(scope,'" + value.trim() + "')";
});
expr = this.parseFilter(filters, expr);
return this.getEvalFunc(expr)(scope);
}
parseFilter(filters, expr) {
let {sodaFilterMap} = Soda;
var parse = () => {
var filterExpr = filters.shift();
if (!filterExpr) {
return;
}
var filterExpr = filterExpr.split(":");
var args = filterExpr.slice(1) || [];
var name = (filterExpr[0] || "").trim();
for (var i = 0; i < args.length; i++) {
//这里根据类型进行判断
if (OBJECT_REG_NG.test(args[i])) {
args[i] = "getValue(scope,'" + args[i] + "')";
} else {}
}
if (sodaFilterMap[name]) {
args.unshift(expr);
args = args.join(",");
expr = "sodaFilterMap['" + name + "'](" + args + ")";
}
parse();
}
parse();
return expr;
}
static filter(name, func) {
this.sodaFilterMap[name] = func;
}
static getFilter(name){
return this.sodaFilterMap[name];
}
static directive(name, opt) {
// 按照顺序入
let {priority = 0} = opt;
let i;
for(i = 0; i < this.sodaDirectives.length; i ++){
let item = this.sodaDirectives[i];
let {priority: itemPriority = 0} = item.opt;
// 比他小 继续比下一个
if(priority < itemPriority){
// 发现比它大或者相等 就插大他前面
}else if(priority >= itemPriority){
break;
}
}
this.sodaDirectives.splice(i, 0, {
name,
opt
});
}
static discribe(name, funcOrStr, option = { compile: true}){
this.template[name] = {
funcOrStr,
option
};
}
static getTmpl(name, args){
let template = this.template[name];
let { funcOrStr, option = {} } = template;
let result;
if(typeof funcOrStr === 'function'){
result = funcOrStr.apply(null, args);
}else{
result = funcOrStr;
}
return {
template: result,
option
}
}
}