1 /* 2 * Geddy JavaScript Web development framework 3 * Copyright 2112 Matthew Eernisse (mde@fleegix.org) 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 * 17 */ 18 19 var ejs = {}; 20 21 ejs.Template = function (p) { 22 var UNDEF; 23 var params = p || {}; 24 25 this.mode = null; 26 this.templateText = params.text || 27 // If you don't want to use Fleegix.js, 28 // override getTemplateTextFromNode to use 29 // textarea node value for template text 30 this.getTemplateTextFromNode(params.node); 31 this.afterLoaded = params.afterLoaded; 32 this.source = ''; 33 this.markup = UNDEF; 34 // Try to get from URL 35 if (typeof this.templateText == 'undefined') { 36 // If you don't want to use Fleegix.js, 37 // override getTemplateTextFromUrl to use 38 // files for template text 39 this.getTemplateTextFromUrl(params); 40 } 41 }; 42 43 ejs.Template.prototype = new function () { 44 var _REGEX = /(<%%)|(%%>)|(<%=)|(<%#)|(<%)|(%>\n)|(%>)|(\n)/; 45 this.modes = { 46 EVAL: 'eval', 47 OUTPUT: 'output', 48 APPEND: 'append', 49 COMMENT: 'comment', 50 LITERAL: 'literal' 51 }; 52 this.getTemplateTextFromNode = function (node) { 53 // Requires the fleegix.xhr module 54 if (typeof fleegix.string == 'undefined') { 55 throw('Requires fleegix.string module.'); } 56 var ret; 57 if (node) { 58 ret = node.value; 59 ret = fleegix.string.unescapeXML(ret); 60 ret = fleegix.string.trim(ret); 61 } 62 return ret; 63 }; 64 this.getTemplateTextFromUrl = function (params) { 65 // Requires the fleegix.xhr module 66 if (typeof fleegix.xhr == 'undefined') { 67 throw('Requires fleegix.xhr module.'); } 68 var _this = this; 69 var url = params.url; 70 var noCache = params.preventCache || false; 71 // Found text in cache, and caching is turned on 72 if (text && !noCache) { 73 this.templateText = text; 74 } 75 // Otherwise go grab the text 76 else { 77 // Callback for setting templateText and caching -- 78 // used for both sync and async loading 79 var callback = function (s) { 80 _this.templateText = s; 81 ejs.templateTextCache[url] = s; 82 // Use afterLoaded hook if set 83 if (typeof _this.afterLoaded == 'function') { 84 _this.afterLoaded(); 85 } 86 }; 87 var opts; 88 if (params.async) { 89 opts = { 90 url: url, 91 method: 'GET', 92 preventCache: noCache, 93 async: true, 94 handleSuccess: callback 95 }; 96 // Get templ text asynchronously, wait for 97 // loading to exec the callback 98 fleegix.xhr.send(opts); 99 } 100 else { 101 opts = { 102 url: url, 103 method: 'GET', 104 preventCache: noCache, 105 async: false 106 }; 107 // Get the templ text inline and pass directly to 108 // the callback 109 text = fleegix.xhr.send(opts); 110 callback(text); 111 } 112 } 113 }; 114 this.process = function (p) { 115 var params = p || {}; 116 this.data = params.data || {}; 117 var domNode = params.node; 118 // Cache/reuse the generated template source for speed 119 this.source = this.source || ''; 120 if (!this.source) { this.generateSource(); } 121 122 // Eval the template with the passed data 123 // Use 'with' to give local scoping to data obj props 124 // ======================== 125 var _output = ''; // Inner scope var for eval output 126 with (this.data) { 127 eval(this.source); 128 } 129 this.markup = _output; 130 131 if (domNode) { 132 domNode.innerHTML = this.markup; 133 } 134 return this.markup; 135 }; 136 this.generateSource = function () { 137 var line = ''; 138 var matches = this.parseTemplateText(); 139 if (matches) { 140 for (var i = 0; i < matches.length; i++) { 141 line = matches[i]; 142 if (line) { 143 this.scanLine(line); 144 } 145 } 146 } 147 }; 148 this.parseTemplateText = function() { 149 var str = this.templateText; 150 var pat = _REGEX; 151 var result = pat.exec(str); 152 var arr = []; 153 while (result) { 154 var firstPos = result.index; 155 var lastPos = pat.lastIndex; 156 if (firstPos !== 0) { 157 arr.push(str.substring(0, firstPos)); 158 str = str.slice(firstPos); 159 } 160 arr.push(result[0]); 161 str = str.slice(result[0].length); 162 result = pat.exec(str); 163 } 164 if (str !== '') { 165 arr.push(str); 166 } 167 return arr; 168 }; 169 this.scanLine = function (line) { 170 var _this = this; 171 var _addOutput = function () { 172 line = line.replace(/\n/, '\\n'); 173 174 line = line.replace(/"/g, '\\"'); 175 _this.source += '_output += "' + line + '";'; 176 }; 177 switch (line) { 178 case '<%': 179 this.mode = this.modes.EVAL; 180 break; 181 case '<%=': 182 this.mode = this.modes.OUTPUT; 183 break; 184 case '<%#': 185 this.mode = this.modes.COMMENT; 186 break; 187 case '<%%': 188 this.mode = this.modes.LITERAL; 189 this.source += '_output += "' + line + '";'; 190 break; 191 case '%>': 192 case '%>\n': 193 if (this.mode == this.modes.LITERAL) { 194 _addOutput(); 195 } 196 this.mode = null; 197 break; 198 default: 199 // In script mode, depends on type of tag 200 if (this.mode) { 201 switch (this.mode) { 202 // Just executing code 203 case this.modes.EVAL: 204 this.source += line; 205 break; 206 // Exec and output 207 case this.modes.OUTPUT: 208 // Add the exec'd result to the output 209 this.source += '_output += ' + line + ';'; 210 break; 211 case this.modes.COMMENT: 212 // Do nothing 213 break; 214 // Literal <%% mode, append as raw output 215 case this.modes.LITERAL: 216 _addOutput(); 217 break; 218 } 219 } 220 // In string mode, just add the output 221 else { 222 _addOutput(); 223 } 224 } 225 }; 226 }; 227 228 exports.Template = ejs.Template; 229