UNPKG

xtemplate

Version:

eXtensible Template Engine lib on browser and nodejs. support async control, inheritance, include, logic expression, custom function and more.

331 lines (299 loc) 9.16 kB
/** * xtemplate runtime * @author yiminghe@gmail.com * @ignore */ var util = require('./runtime/util'); var nativeCommands = require('./runtime/commands'); var commands = {}; var Scope = require('./runtime/scope'); var LinkedBuffer = require('./runtime/linked-buffer'); function findCommand(runtimeCommands, instanceCommands, parts) { var name = parts[0]; var cmd = runtimeCommands && runtimeCommands[name] || instanceCommands && instanceCommands[name] || commands[name]; if (parts.length === 1) { return cmd; } if (cmd) { var len = parts.length; for (var i = 1; i < len; i++) { cmd = cmd[parts[i]]; if (!cmd) { break; } } } return cmd; } function getSubNameFromParentName(parentName, subName) { var parts = parentName.split('/'); var subParts = subName.split('/'); parts.pop(); for (var i = 0, l = subParts.length; i < l; i++) { var subPart = subParts[i]; if (subPart === '.') { } else if (subPart === '..') { parts.pop(); } else { parts.push(subPart); } } return parts.join('/'); } function renderTpl(tpl, scope, buffer) { buffer = tpl.fn(scope, buffer); var runtime = tpl.runtime; var extendTplName = runtime.extendTplName; // if has extend statement, only parse if (extendTplName) { runtime.extendTplName = null; buffer = tpl.root.include(extendTplName, tpl, scope, null, buffer); } return buffer.end(); } // depth: ../x.y() => 1 function callFn(tpl, scope, option, buffer, parts, depth) { var error, caller, fn, command1; if (!depth) { command1 = findCommand(tpl.runtime.commands, tpl.root.config.commands, parts); } if (command1) { return command1.call(tpl, scope, option, buffer); } else { error = 'in file: ' + tpl.name + ' can not call: ' + parts.join('.') + '" at line ' + tpl.pos.line + ', col ' + tpl.pos.col; } caller = scope.resolve(parts.slice(0, -1), depth); fn = caller[parts[parts.length - 1]]; if (fn) { return fn.apply(caller, option.params); } if (error) { throw new Error(error); } return buffer; } var utils = { callFn: callFn, callCommand: function (tpl, scope, option, buffer, parts) { return callFn(tpl, scope, option, buffer, parts); } }; var loader = { cache: {}, load: function (params, callback) { var name = params.name; var cache = this.cache; var cached = cache[name]; if (cached) { return callback(undefined, cached); } require([name], function (tpl) { cache[name] = tpl; callback(undefined, tpl); }, function () { var error = 'template "' + params.name + '" does not exist'; util.log(error, 'error'); callback(error); } ); } }; /** * template file name for chrome debug * * @cfg {Boolean} name * @member XTemplate.Runtime */ /** * XTemplate runtime. only accept tpl as function. * @class XTemplate.Runtime */ function XTemplateRuntime(fn, config) { var self = this; self.fn = fn; config = self.config = config || {}; config.loader = config.loader || XTemplateRuntime.loader; this.subNameResolveCache = {}; } util.mix(XTemplateRuntime, { loader: loader, version: '@VERSION@', nativeCommands: nativeCommands, utils: utils, util: util, /** * add command to all template * @method * @static * @param {String} commandName * @param {Function} fn * @member XTemplate.Runtime */ addCommand: function (commandName, fn) { commands[commandName] = fn; }, /** * remove command from all template by name * @method * @static * @param {String} commandName * @member XTemplate.Runtime */ removeCommand: function (commandName) { delete commands[commandName]; } }); XTemplateRuntime.prototype = { constructor: XTemplateRuntime, Scope: Scope, nativeCommands: nativeCommands, utils: utils, /** * remove command by name * @param commandName */ removeCommand: function (commandName) { var config = this.config; if (config.commands) { delete config.commands[commandName]; } }, /** * add command definition to current template * @param commandName * @param {Function} fn command definition */ addCommand: function (commandName, fn) { var config = this.config; config.commands = config.commands || {}; config.commands[commandName] = fn; }, resolve: function (subName, parentName) { if (subName.charAt(0) !== '.') { return subName; } if (!parentName) { var error = 'parent template does not have name' + ' for relative sub tpl name: ' + subName; throw new Error(error); } var key = parentName + '_ks_' + subName; var nameResolveCache = this.subNameResolveCache; var cached = nameResolveCache[key]; if (cached) { return cached; } subName = nameResolveCache[key] = getSubNameFromParentName(parentName, subName); return subName; }, include: function (subTplName, tpl, scope, option, buffer, directAccess) { var self = this; var parentName = tpl.name; var resolvedSubTplName = self.resolve(subTplName, parentName); return buffer.async(function (newBuffer) { self.config.loader.load({ root: self, parentName: parentName, originalName: subTplName, name: resolvedSubTplName, scope: scope, option: option }, function (error, tplFn) { if (typeof tplFn === 'function') { renderTpl({ directAccess: directAccess, root: tpl.root, fn: tplFn, name: resolvedSubTplName, runtime: tpl.runtime }, scope, newBuffer); } else if (error) { newBuffer.error(error); } else { if (option && option.escaped) { newBuffer.writeEscaped(tplFn); } else { newBuffer.append(tplFn); } newBuffer.end(); } }); }); }, /** * get result by merge data with template * @param data * @param option * @param callback function called * @return {String} */ render: function (data, option, callback) { var html = ''; var self = this; var fn = self.fn; if (typeof option === 'function') { callback = option; option = null; } option = option || {}; callback = callback || function (error, ret) { if (error) { if (!(error instanceof Error)) { error = new Error(error); } throw error; } html = ret; }; var name = self.config.name; if (!name && fn.TPL_NAME) { name = fn.TPL_NAME; } var scope = new Scope(data); var buffer = new XTemplateRuntime.LinkedBuffer(callback, self.config).head; renderTpl({ name: name, fn: fn, runtime: { commands: option.commands }, root: self, directAccess: true }, scope, buffer); return html; } }; XTemplateRuntime.Scope = Scope; XTemplateRuntime.LinkedBuffer = LinkedBuffer; module.exports = XTemplateRuntime; /** * @ignore * * 2012-09-12 yiminghe@gmail.com * - 参考 velocity, 扩充 ast * - Expression/ConditionalOrExpression * - EqualityExpression/RelationalExpression... * * 2012-09-11 yiminghe@gmail.com * - 初步完成,添加 tc * * 对比 template * * 优势 * - 不会莫名其妙报错(with) * - 更多出错信息,直接给出行号 * - 更容易扩展 command, sub-tpl * - 支持子模板 * - 支持作用域链: ..\x ..\..\y * - 内置 escapeHtml 支持 * - 支持预编译 * - 支持简单表达式 +-/%* () * - 支持简单比较 === !=== * - 支持类似函数的嵌套命令 * 劣势 * - 不支持完整 js 语法 */