/** * xtemplate runtime * @author yiminghe@gmail.com * @ignore */ KISSY.add('xtemplate/runtime', function (S, commands, undefined) { var escapeHtml = S.escapeHtml; var logger = S.getLogger('s/xtemplate'); function info(s) { logger.info(s); } function findCommand(commands, name) { var parts = name.split('.'); var cmd = commands; var len = parts.length; for (var i = 0; i < len; i++) { cmd = cmd[parts[i]]; if (!cmd) { break; } } return cmd; } function getProperty(parts, scopes, depth) { // this refer to current scope object if (parts === '.') { parts = 'this'; } parts = parts.split('.'); var len = parts.length, i, j = depth || 0, v, p, valid, sl = scopes.length; // root keyword for root scope if (parts[0] == 'root') { j = sl - 1; parts.shift(); len--; } for (; j < sl; j++) { v = scopes[j]; valid = 1; for (i = 0; i < len; i++) { p = parts[i]; if (p === 'this') { continue; } // may not be object at all else if (typeof v != 'object' || !(p in v)) { valid = 0; break; } v = v[p]; } if (valid) { // support property function return value as property value if (typeof v == 'function') { v = v.call(scopes[0]); } return [v]; } } return false; } var utils = { 'runBlockCommand': function (engine, scopes, options, name, line) { var config = engine.config; var logFn = config.silent ? info : S.error; var commands = config.commands; var command = findCommand(commands, name); if (!command) { if (!options.params && !options.hash) { var property = getProperty(name, scopes); if (property === false) { logFn("can not find property: '" + name + "' at line " + line); property = ''; } else { property = property[0]; } command = commands['if']; if (S.isArray(property)) { command = commands.each; } else if (typeof property == 'object') { command = commands['with']; } options.params = [property]; } else { S.error("can not find command module: " + name + "' at line " + line); return ''; } } var ret = ''; try { ret = command.call(engine, scopes, options); } catch (e) { S.error(e.message + ": '" + name + "' at line " + line); } if (ret === undefined) { ret = ''; } return ret; }, 'getExpression': function (exp, escaped) { if (exp === undefined) { exp = ''; } return escaped && exp ? escapeHtml(exp) : exp; }, 'getPropertyOrRunCommand': function (engine, scopes, options, name, depth, line, escape, preserveUndefined) { var id0; var config = engine.config; var commands = config.commands; var command1 = findCommand(commands, name); var logFn = config.silent ? info : S.error; if (command1) { try { id0 = command1.call(engine, scopes, options); } catch (e) { S.error(e.message + ": '" + name + "' at line " + line); return ''; } } else { var tmp2 = getProperty(name, scopes, depth); if (tmp2 === false) { logFn("can not find property: '" + name + "' at line " + line, "warn"); // undefined for expression // {{n+2}} return preserveUndefined ? undefined : ''; } else { id0 = tmp2[0]; } } if (!preserveUndefined && id0 === undefined) { id0 = ''; } return escape && id0 ? escapeHtml(id0) : id0; } }, defaultConfig = { /** * whether throw exception when template variable is not found in data * * or * * command is not found * * * '{{title}}'.render({t2:0}) * * * @cfg {Boolean} silent * @member KISSY.XTemplate.Runtime */ silent: true, /** * template file name for chrome debug * * @cfg {Boolean} name * @member KISSY.XTemplate.Runtime */ name: 'unspecified', /** * tpl loader to load sub tpl by name * @cfg {Function} loader * @member KISSY.XTemplate.Runtime */ loader: function (subTplName) { var tpl = S.require(subTplName); if (!tpl) { S.error('template "' + subTplName + '" does not exist, ' + 'need to be required or used first!'); } return tpl; } }; /** * XTemplate runtime. only accept tpl as function. * * * @example * KISSY.use('xtemplate/runtime',function(S, XTemplateRunTime){ * document.writeln( * new XTemplateRunTime(function(scopes){ return scopes[0].title;}).render({title:2}) * ); * }); * * @class KISSY.XTemplate.Runtime */ function XTemplateRuntime(tpl, config) { var self = this; self.tpl = tpl; config = S.merge(defaultConfig, config); config.commands = S.merge(config.commands, commands); config.utils = utils; config.macros = config.macros || {}; this.config = config; } S.mix(XTemplateRuntime, { commands: commands, utils: utils, /** * add command to all template * @method * @static * @param {String} commandName * @param {Function} fn * @member KISSY.XTemplate.Runtime */ addCommand: function (commandName, fn) { commands[commandName] = fn; }, /** * remove command from all template by name * @method * @static * @param {String} commandName * @member KISSY.XTemplate.Runtime */ removeCommand: function (commandName) { delete commands[commandName]; } }); XTemplateRuntime.prototype = { constructor: XTemplateRuntime, // allow str sub template invokeEngine: function (tpl, scopes, config) { return new this.constructor(tpl, config).render(scopes, true); }, /** * remove command by name * @param commandName */ 'removeCommand': function (commandName) { delete this.config.commands[commandName]; }, /** * add command definition to current template * @param commandName * @param {Function} fn command definition */ addCommand: function (commandName, fn) { this.config.commands[commandName] = fn; }, /** * get result by merge data with template * @param data * @return {String} * @param {Boolean} [keepDataFormat] for internal use */ render: function (data, keepDataFormat) { if (!keepDataFormat) { data = [data]; } return this.tpl(data, S); } }; return XTemplateRuntime; }, { requires: [ './runtime/commands'] }); /** * @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 语法 * */