1 /** 2 * @fileOverview KISSY Template Engine 3 * @author yyfrankyy@gmail.com 4 */ 5 KISSY.add('template', function (S) { 6 7 var // Template Cache 8 templateCache = {}, 9 10 // start/end tag mark 11 tagStartEnd = { 12 '#':'start', 13 '@':'start', 14 '/':'end' 15 }, 16 17 // static string 18 KS_TEMPL_STAT_PARAM = 'KS_TEMPL_STAT_PARAM', 19 KS_TEMPL_STAT_PARAM_REG = new RegExp(KS_TEMPL_STAT_PARAM, "g"), 20 KS_TEMPL = 'KS_TEMPL', 21 KS_DATA = 'KS_DATA_', 22 KS_AS = 'as', 23 24 // note : double quote for generated code 25 PREFIX = '");', 26 SUFFIX = KS_TEMPL + '.push("', 27 28 PARSER_SYNTAX_ERROR = 'KISSY.Template: Syntax Error. ', 29 PARSER_RENDER_ERROR = 'KISSY.Template: Render Error. ', 30 31 PARSER_PREFIX = 'var ' + KS_TEMPL + '=[],' + 32 KS_TEMPL_STAT_PARAM + '=false;with(', 33 34 PARSER_MIDDLE = '||{}){try{' + KS_TEMPL + '.push("', 35 36 PARSER_SUFFIX = '");}catch(e){' + KS_TEMPL + '=["' + 37 PARSER_RENDER_ERROR + '" + e.message]}};return ' + 38 KS_TEMPL + '.join("");', 39 40 // restore double quote in logic template variable 41 restoreQuote = function (str) { 42 return str.replace(/\\"/g, '"'); 43 }, 44 45 // escape double quote in template 46 escapeQuote = function (str) { 47 return str.replace(/"/g, '\\"'); 48 }, 49 50 trim = S.trim, 51 52 // build a static parser 53 buildParser = function (tpl) { 54 var _parser, 55 _empty_index; 56 return escapeQuote(trim(tpl) 57 .replace(/[\r\t\n]/g, ' ') 58 // escape escape ... . in case \ is consumed when run tpl parser function 59 // '{{y}}\\x{{/y}}' =>tmpl.push('\x'); => tmpl.push('\\x'); 60 .replace(/\\/g, '\\\\')) 61 .replace(/\{\{([#/@]?)(?!\}\})([^}]*)\}\}/g, 62 function (all, expr, body) { 63 _parser = ""; 64 // must restore quote , if str is used as code directly 65 body = restoreQuote(trim(body)); 66 // is an expression 67 if (expr) { 68 _empty_index = body.indexOf(' '); 69 body = _empty_index === -1 ? 70 [ body, '' ] : 71 [ 72 body.substring(0, _empty_index), 73 body.substring(_empty_index) 74 ]; 75 76 var operator = body[0], 77 fn, 78 args = trim(body[1]), 79 opStatement = Statements[operator]; 80 81 if (opStatement && tagStartEnd[expr]) { 82 // get expression definition function/string 83 fn = opStatement[tagStartEnd[expr]]; 84 _parser = String(S.isFunction(fn) ? 85 fn.apply(this, args.split(/\s+/)) : 86 fn.replace(KS_TEMPL_STAT_PARAM_REG, args)); 87 } 88 } 89 // return array directly 90 else { 91 _parser = KS_TEMPL + 92 '.push(' + 93 // prevent variable undefined error when look up in with, simple variable substitution 94 // with({}){alert(x);} => ReferenceError: x is not defined 95 'typeof (' + body + ') ==="undefined"?"":' + body + 96 ');'; 97 } 98 return PREFIX + _parser + SUFFIX; 99 100 }); 101 }, 102 103 // expression 104 Statements = { 105 'if':{ 106 start:'if(typeof (' + KS_TEMPL_STAT_PARAM + ') !=="undefined" && ' + KS_TEMPL_STAT_PARAM + '){', 107 end:'}' 108 }, 109 110 'else':{ 111 start:'}else{' 112 }, 113 114 'elseif':{ 115 start:'}else if(' + KS_TEMPL_STAT_PARAM + '){' 116 }, 117 118 // KISSY.each function wrap 119 'each':{ 120 start:function (obj, as, v, k) { 121 var _ks_value = '_ks_value', 122 _ks_index = '_ks_index'; 123 if (as === KS_AS && v) { 124 _ks_value = v || _ks_value; 125 _ks_index = k || _ks_index; 126 } 127 return 'KISSY.each(' + obj + 128 ', function(' + _ks_value + 129 ', ' + _ks_index + '){'; 130 }, 131 end:'});' 132 }, 133 134 // comments 135 '!':{ 136 start:'/*' + KS_TEMPL_STAT_PARAM + '*/' 137 } 138 }; 139 140 /** 141 * Template 142 * @param {String} tpl template to be rendered. 143 * @return {Object} return this for chain. 144 */ 145 function Template(tpl) { 146 if (!(templateCache[tpl])) { 147 var _ks_data = S.guid(KS_DATA), 148 func, 149 o, 150 _parser = [ 151 PARSER_PREFIX, 152 _ks_data, 153 PARSER_MIDDLE, 154 o = buildParser(tpl), 155 PARSER_SUFFIX 156 ]; 157 158 try { 159 func = new Function(_ks_data, _parser.join("")); 160 } catch (e) { 161 _parser[3] = PREFIX + SUFFIX + 162 PARSER_SYNTAX_ERROR + ',' + 163 e.message + PREFIX + SUFFIX; 164 func = new Function(_ks_data, _parser.join("")); 165 } 166 167 templateCache[tpl] = { 168 name:_ks_data, 169 o:o, 170 parser:_parser.join(""), 171 render:func 172 }; 173 } 174 return templateCache[tpl]; 175 } 176 177 S.mix(Template, { 178 /** 179 * Logging Compiled Template Codes 180 * @param {String} tpl template string. 181 */ 182 log:function (tpl) { 183 if (tpl in templateCache) { 184 // if ('js_beautify' in window) { 185 // S.log(js_beautify(templateCache[tpl].parser, { 186 // indent_size: 4, 187 // indent_char: ' ', 188 // preserve_newlines: true, 189 // braces_on_own_line: false, 190 // keep_array_indentation: false, 191 // space_after_anon_function: true 192 // }), 'info'); 193 // } else { 194 S.log(templateCache[tpl].parser, 'info'); 195 // } 196 } else { 197 Template(tpl); 198 this.log(tpl); 199 } 200 }, 201 202 /** 203 * add statement for extending template tags 204 * @param {String} statement tag name. 205 * @param {String} o extent tag object. 206 */ 207 addStatement:function (statement, o) { 208 if (S.isString(statement)) { 209 Statements[statement] = o; 210 } else { 211 S.mix(Statements, statement); 212 } 213 } 214 215 }); 216 217 return Template; 218 219 }); 220 /** 221 * TODO: check spec 222 * - http://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/templates/index.html 223 * 224 * 2012-04-24 yiminghe@gmail.com 225 * - support {{@if test}}t{{/if}} to prevent collision with velocity template engine 226 * 227 * 228 * 2011-09-20 note by yiminghe : 229 * - code style change 230 * - remove reg cache , ugly to see 231 * - fix escape by escape 232 * - expect(T('{{#if a=="a"}}{{b}}\\"{{/if}}').render({a:"a",b:"b"})).toBe('b\\"'); 233 */ 234