1 | (function(){ |
---|
2 | |
---|
3 | |
---|
4 | var rsplit = function(string, regex) { |
---|
5 | var result = regex.exec(string),retArr = new Array(), first_idx, last_idx, first_bit; |
---|
6 | while (result != null) |
---|
7 | { |
---|
8 | first_idx = result.index; last_idx = regex.lastIndex; |
---|
9 | if ((first_idx) != 0) |
---|
10 | { |
---|
11 | first_bit = string.substring(0,first_idx); |
---|
12 | retArr.push(string.substring(0,first_idx)); |
---|
13 | string = string.slice(first_idx); |
---|
14 | } |
---|
15 | retArr.push(result[0]); |
---|
16 | string = string.slice(result[0].length); |
---|
17 | result = regex.exec(string); |
---|
18 | } |
---|
19 | if (! string == '') |
---|
20 | { |
---|
21 | retArr.push(string); |
---|
22 | } |
---|
23 | return retArr; |
---|
24 | }, |
---|
25 | chop = function(string){ |
---|
26 | return string.substr(0, string.length - 1); |
---|
27 | }, |
---|
28 | extend = function(d, s){ |
---|
29 | for(var n in s){ |
---|
30 | if(s.hasOwnProperty(n)) d[n] = s[n] |
---|
31 | } |
---|
32 | } |
---|
33 | |
---|
34 | |
---|
35 | EJS = function( options ){ |
---|
36 | options = typeof options == "string" ? {view: options} : options |
---|
37 | this.set_options(options); |
---|
38 | if(options.precompiled){ |
---|
39 | this.template = {}; |
---|
40 | this.template.process = options.precompiled; |
---|
41 | EJS.update(this.name, this); |
---|
42 | return; |
---|
43 | } |
---|
44 | if(options.element) |
---|
45 | { |
---|
46 | if(typeof options.element == 'string'){ |
---|
47 | var name = options.element |
---|
48 | options.element = document.getElementById( options.element ) |
---|
49 | if(options.element == null) throw name+'does not exist!' |
---|
50 | } |
---|
51 | if(options.element.value){ |
---|
52 | this.text = options.element.value |
---|
53 | }else{ |
---|
54 | this.text = options.element.innerHTML |
---|
55 | } |
---|
56 | this.name = options.element.id |
---|
57 | this.type = '[' |
---|
58 | }else if(options.url){ |
---|
59 | options.url = EJS.endExt(options.url, this.extMatch); |
---|
60 | this.name = this.name ? this.name : options.url; |
---|
61 | var url = options.url |
---|
62 | //options.view = options.absolute_url || options.view || options.; |
---|
63 | var template = EJS.get(this.name /*url*/, this.cache); |
---|
64 | if (template) return template; |
---|
65 | if (template == EJS.INVALID_PATH) return null; |
---|
66 | try{ |
---|
67 | this.text = EJS.request( url+(this.cache ? '' : '?'+Math.random() )); |
---|
68 | }catch(e){} |
---|
69 | |
---|
70 | if(this.text == null){ |
---|
71 | throw( {type: 'EJS', message: 'There is no template at '+url} ); |
---|
72 | } |
---|
73 | //this.name = url; |
---|
74 | } |
---|
75 | var template = new EJS.Compiler(this.text, this.type); |
---|
76 | |
---|
77 | template.compile(options, this.name); |
---|
78 | |
---|
79 | |
---|
80 | EJS.update(this.name, this); |
---|
81 | this.template = template; |
---|
82 | }; |
---|
83 | /* @Prototype*/ |
---|
84 | EJS.prototype = { |
---|
85 | /** |
---|
86 | * Renders an object with extra view helpers attached to the view. |
---|
87 | * @param {Object} object data to be rendered |
---|
88 | * @param {Object} extra_helpers an object with additonal view helpers |
---|
89 | * @return {String} returns the result of the string |
---|
90 | */ |
---|
91 | render : function(object, extra_helpers){ |
---|
92 | object = object || {}; |
---|
93 | this._extra_helpers = extra_helpers; |
---|
94 | var v = new EJS.Helpers(object, extra_helpers || {}); |
---|
95 | return this.template.process.call(object, object,v); |
---|
96 | }, |
---|
97 | update : function(element, options){ |
---|
98 | if(typeof element == 'string'){ |
---|
99 | element = document.getElementById(element) |
---|
100 | } |
---|
101 | if(options == null){ |
---|
102 | _template = this; |
---|
103 | return function(object){ |
---|
104 | EJS.prototype.update.call(_template, element, object) |
---|
105 | } |
---|
106 | } |
---|
107 | if(typeof options == 'string'){ |
---|
108 | params = {} |
---|
109 | params.url = options |
---|
110 | _template = this; |
---|
111 | params.onComplete = function(request){ |
---|
112 | var object = eval( request.responseText ) |
---|
113 | EJS.prototype.update.call(_template, element, object) |
---|
114 | } |
---|
115 | EJS.ajax_request(params) |
---|
116 | }else |
---|
117 | { |
---|
118 | element.innerHTML = this.render(options) |
---|
119 | } |
---|
120 | }, |
---|
121 | out : function(){ |
---|
122 | return this.template.out; |
---|
123 | }, |
---|
124 | /** |
---|
125 | * Sets options on this view to be rendered with. |
---|
126 | * @param {Object} options |
---|
127 | */ |
---|
128 | set_options : function(options){ |
---|
129 | this.type = options.type || EJS.type; |
---|
130 | this.cache = options.cache != null ? options.cache : EJS.cache; |
---|
131 | this.text = options.text || null; |
---|
132 | this.name = options.name || null; |
---|
133 | this.ext = options.ext || EJS.ext; |
---|
134 | this.extMatch = new RegExp(this.ext.replace(/\./, '\.')); |
---|
135 | } |
---|
136 | }; |
---|
137 | EJS.endExt = function(path, match){ |
---|
138 | if(!path) return null; |
---|
139 | match.lastIndex = 0 |
---|
140 | return path+ (match.test(path) ? '' : this.ext ) |
---|
141 | } |
---|
142 | |
---|
143 | |
---|
144 | |
---|
145 | |
---|
146 | /* @Static*/ |
---|
147 | EJS.Scanner = function(source, left, right) { |
---|
148 | |
---|
149 | extend(this, |
---|
150 | {left_delimiter: left +'%', |
---|
151 | right_delimiter: '%'+right, |
---|
152 | double_left: left+'%%', |
---|
153 | double_right: '%%'+right, |
---|
154 | left_equal: left+'%=', |
---|
155 | left_comment: left+'%#'}) |
---|
156 | |
---|
157 | this.SplitRegexp = left=='[' ? /(\[%%)|(%%\])|(\[%=)|(\[%#)|(\[%)|(%\]\n)|(%\])|(\n)/ : new RegExp('('+this.double_left+')|(%%'+this.double_right+')|('+this.left_equal+')|('+this.left_comment+')|('+this.left_delimiter+')|('+this.right_delimiter+'\n)|('+this.right_delimiter+')|(\n)') ; |
---|
158 | |
---|
159 | this.source = source; |
---|
160 | this.stag = null; |
---|
161 | this.lines = 0; |
---|
162 | }; |
---|
163 | |
---|
164 | EJS.Scanner.to_text = function(input){ |
---|
165 | if(input == null || input === undefined) |
---|
166 | return ''; |
---|
167 | if(input instanceof Date) |
---|
168 | return input.toDateString(); |
---|
169 | if(input.toString) |
---|
170 | return input.toString(); |
---|
171 | return ''; |
---|
172 | }; |
---|
173 | |
---|
174 | EJS.Scanner.prototype = { |
---|
175 | scan: function(block) { |
---|
176 | scanline = this.scanline; |
---|
177 | regex = this.SplitRegexp; |
---|
178 | if (! this.source == '') |
---|
179 | { |
---|
180 | var source_split = rsplit(this.source, /\n/); |
---|
181 | for(var i=0; i<source_split.length; i++) { |
---|
182 | var item = source_split[i]; |
---|
183 | this.scanline(item, regex, block); |
---|
184 | } |
---|
185 | } |
---|
186 | }, |
---|
187 | scanline: function(line, regex, block) { |
---|
188 | this.lines++; |
---|
189 | var line_split = rsplit(line, regex); |
---|
190 | for(var i=0; i<line_split.length; i++) { |
---|
191 | var token = line_split[i]; |
---|
192 | if (token != null) { |
---|
193 | try{ |
---|
194 | block(token, this); |
---|
195 | }catch(e){ |
---|
196 | throw {type: 'EJS.Scanner', line: this.lines}; |
---|
197 | } |
---|
198 | } |
---|
199 | } |
---|
200 | } |
---|
201 | }; |
---|
202 | |
---|
203 | |
---|
204 | EJS.Buffer = function(pre_cmd, post_cmd) { |
---|
205 | this.line = new Array(); |
---|
206 | this.script = ""; |
---|
207 | this.pre_cmd = pre_cmd; |
---|
208 | this.post_cmd = post_cmd; |
---|
209 | for (var i=0; i<this.pre_cmd.length; i++) |
---|
210 | { |
---|
211 | this.push(pre_cmd[i]); |
---|
212 | } |
---|
213 | }; |
---|
214 | EJS.Buffer.prototype = { |
---|
215 | |
---|
216 | push: function(cmd) { |
---|
217 | this.line.push(cmd); |
---|
218 | }, |
---|
219 | |
---|
220 | cr: function() { |
---|
221 | this.script = this.script + this.line.join('; '); |
---|
222 | this.line = new Array(); |
---|
223 | this.script = this.script + "\n"; |
---|
224 | }, |
---|
225 | |
---|
226 | close: function() { |
---|
227 | if (this.line.length > 0) |
---|
228 | { |
---|
229 | for (var i=0; i<this.post_cmd.length; i++){ |
---|
230 | this.push(pre_cmd[i]); |
---|
231 | } |
---|
232 | this.script = this.script + this.line.join('; '); |
---|
233 | line = null; |
---|
234 | } |
---|
235 | } |
---|
236 | |
---|
237 | }; |
---|
238 | |
---|
239 | |
---|
240 | EJS.Compiler = function(source, left) { |
---|
241 | this.pre_cmd = ['var ___ViewO = [];']; |
---|
242 | this.post_cmd = new Array(); |
---|
243 | this.source = ' '; |
---|
244 | if (source != null) |
---|
245 | { |
---|
246 | if (typeof source == 'string') |
---|
247 | { |
---|
248 | source = source.replace(/\r\n/g, "\n"); |
---|
249 | source = source.replace(/\r/g, "\n"); |
---|
250 | this.source = source; |
---|
251 | }else if (source.innerHTML){ |
---|
252 | this.source = source.innerHTML; |
---|
253 | } |
---|
254 | if (typeof this.source != 'string'){ |
---|
255 | this.source = ""; |
---|
256 | } |
---|
257 | } |
---|
258 | left = left || '<'; |
---|
259 | var right = '>'; |
---|
260 | switch(left) { |
---|
261 | case '[': |
---|
262 | right = ']'; |
---|
263 | break; |
---|
264 | case '<': |
---|
265 | break; |
---|
266 | default: |
---|
267 | throw left+' is not a supported deliminator'; |
---|
268 | break; |
---|
269 | } |
---|
270 | this.scanner = new EJS.Scanner(this.source, left, right); |
---|
271 | this.out = ''; |
---|
272 | }; |
---|
273 | EJS.Compiler.prototype = { |
---|
274 | compile: function(options, name) { |
---|
275 | options = options || {}; |
---|
276 | this.out = ''; |
---|
277 | var put_cmd = "___ViewO.push("; |
---|
278 | var insert_cmd = put_cmd; |
---|
279 | var buff = new EJS.Buffer(this.pre_cmd, this.post_cmd); |
---|
280 | var content = ''; |
---|
281 | var clean = function(content) |
---|
282 | { |
---|
283 | content = content.replace(/\\/g, '\\\\'); |
---|
284 | content = content.replace(/\n/g, '\\n'); |
---|
285 | content = content.replace(/"/g, '\\"'); |
---|
286 | return content; |
---|
287 | }; |
---|
288 | this.scanner.scan(function(token, scanner) { |
---|
289 | if (scanner.stag == null) |
---|
290 | { |
---|
291 | switch(token) { |
---|
292 | case '\n': |
---|
293 | content = content + "\n"; |
---|
294 | buff.push(put_cmd + '"' + clean(content) + '");'); |
---|
295 | buff.cr(); |
---|
296 | content = ''; |
---|
297 | break; |
---|
298 | case scanner.left_delimiter: |
---|
299 | case scanner.left_equal: |
---|
300 | case scanner.left_comment: |
---|
301 | scanner.stag = token; |
---|
302 | if (content.length > 0) |
---|
303 | { |
---|
304 | buff.push(put_cmd + '"' + clean(content) + '")'); |
---|
305 | } |
---|
306 | content = ''; |
---|
307 | break; |
---|
308 | case scanner.double_left: |
---|
309 | content = content + scanner.left_delimiter; |
---|
310 | break; |
---|
311 | default: |
---|
312 | content = content + token; |
---|
313 | break; |
---|
314 | } |
---|
315 | } |
---|
316 | else { |
---|
317 | switch(token) { |
---|
318 | case scanner.right_delimiter: |
---|
319 | switch(scanner.stag) { |
---|
320 | case scanner.left_delimiter: |
---|
321 | if (content[content.length - 1] == '\n') |
---|
322 | { |
---|
323 | content = chop(content); |
---|
324 | buff.push(content); |
---|
325 | buff.cr(); |
---|
326 | } |
---|
327 | else { |
---|
328 | buff.push(content); |
---|
329 | } |
---|
330 | break; |
---|
331 | case scanner.left_equal: |
---|
332 | buff.push(insert_cmd + "(EJS.Scanner.to_text(" + content + ")))"); |
---|
333 | break; |
---|
334 | } |
---|
335 | scanner.stag = null; |
---|
336 | content = ''; |
---|
337 | break; |
---|
338 | case scanner.double_right: |
---|
339 | content = content + scanner.right_delimiter; |
---|
340 | break; |
---|
341 | default: |
---|
342 | content = content + token; |
---|
343 | break; |
---|
344 | } |
---|
345 | } |
---|
346 | }); |
---|
347 | if (content.length > 0) |
---|
348 | { |
---|
349 | // Chould be content.dump in Ruby |
---|
350 | buff.push(put_cmd + '"' + clean(content) + '")'); |
---|
351 | } |
---|
352 | buff.close(); |
---|
353 | this.out = buff.script + ";"; |
---|
354 | var to_be_evaled = '/*'+name+'*/this.process = function(_CONTEXT,_VIEW) { try { with(_VIEW) { with (_CONTEXT) {'+this.out+" return ___ViewO.join('');}}}catch(e){e.lineNumber=null;throw e;}};"; |
---|
355 | |
---|
356 | try{ |
---|
357 | eval(to_be_evaled); |
---|
358 | }catch(e){ |
---|
359 | if(typeof JSLINT != 'undefined'){ |
---|
360 | JSLINT(this.out); |
---|
361 | for(var i = 0; i < JSLINT.errors.length; i++){ |
---|
362 | var error = JSLINT.errors[i]; |
---|
363 | if(error.reason != "Unnecessary semicolon."){ |
---|
364 | error.line++; |
---|
365 | var e = new Error(); |
---|
366 | e.lineNumber = error.line; |
---|
367 | e.message = error.reason; |
---|
368 | if(options.view) |
---|
369 | e.fileName = options.view; |
---|
370 | throw e; |
---|
371 | } |
---|
372 | } |
---|
373 | }else{ |
---|
374 | throw e; |
---|
375 | } |
---|
376 | } |
---|
377 | } |
---|
378 | }; |
---|
379 | |
---|
380 | |
---|
381 | //type, cache, folder |
---|
382 | /** |
---|
383 | * Sets default options for all views |
---|
384 | * @param {Object} options Set view with the following options |
---|
385 | * <table class="options"> |
---|
386 | <tbody><tr><th>Option</th><th>Default</th><th>Description</th></tr> |
---|
387 | <tr> |
---|
388 | <td>type</td> |
---|
389 | <td>'<'</td> |
---|
390 | <td>type of magic tags. Options are '<' or '[' |
---|
391 | </td> |
---|
392 | </tr> |
---|
393 | <tr> |
---|
394 | <td>cache</td> |
---|
395 | <td>true in production mode, false in other modes</td> |
---|
396 | <td>true to cache template. |
---|
397 | </td> |
---|
398 | </tr> |
---|
399 | </tbody></table> |
---|
400 | * |
---|
401 | */ |
---|
402 | EJS.config = function(options){ |
---|
403 | EJS.cache = options.cache != null ? options.cache : EJS.cache; |
---|
404 | EJS.type = options.type != null ? options.type : EJS.type; |
---|
405 | EJS.ext = options.ext != null ? options.ext : EJS.ext; |
---|
406 | |
---|
407 | var templates_directory = EJS.templates_directory || {}; //nice and private container |
---|
408 | EJS.templates_directory = templates_directory; |
---|
409 | EJS.get = function(path, cache){ |
---|
410 | if(cache == false) return null; |
---|
411 | if(templates_directory[path]) return templates_directory[path]; |
---|
412 | return null; |
---|
413 | }; |
---|
414 | |
---|
415 | EJS.update = function(path, template) { |
---|
416 | if(path == null) return; |
---|
417 | templates_directory[path] = template ; |
---|
418 | }; |
---|
419 | |
---|
420 | EJS.INVALID_PATH = -1; |
---|
421 | }; |
---|
422 | EJS.config( {cache: true, type: '<', ext: '.ejs' } ); |
---|
423 | |
---|
424 | |
---|
425 | |
---|
426 | /** |
---|
427 | * @constructor |
---|
428 | * By adding functions to EJS.Helpers.prototype, those functions will be available in the |
---|
429 | * views. |
---|
430 | * @init Creates a view helper. This function is called internally. You should never call it. |
---|
431 | * @param {Object} data The data passed to the view. Helpers have access to it through this._data |
---|
432 | */ |
---|
433 | EJS.Helpers = function(data, extras){ |
---|
434 | this._data = data; |
---|
435 | this._extras = extras; |
---|
436 | extend(this, extras ); |
---|
437 | }; |
---|
438 | /* @prototype*/ |
---|
439 | EJS.Helpers.prototype = { |
---|
440 | /** |
---|
441 | * Renders a new view. If data is passed in, uses that to render the view. |
---|
442 | * @param {Object} options standard options passed to a new view. |
---|
443 | * @param {optional:Object} data |
---|
444 | * @return {String} |
---|
445 | */ |
---|
446 | view: function(options, data, helpers){ |
---|
447 | if(!helpers) helpers = this._extras |
---|
448 | if(!data) data = this._data; |
---|
449 | return new EJS(options).render(data, helpers); |
---|
450 | }, |
---|
451 | /** |
---|
452 | * For a given value, tries to create a human representation. |
---|
453 | * @param {Object} input the value being converted. |
---|
454 | * @param {Object} null_text what text should be present if input == null or undefined, defaults to '' |
---|
455 | * @return {String} |
---|
456 | */ |
---|
457 | to_text: function(input, null_text) { |
---|
458 | if(input == null || input === undefined) return null_text || ''; |
---|
459 | if(input instanceof Date) return input.toDateString(); |
---|
460 | if(input.toString) return input.toString().replace(/\n/g, '<br />').replace(/''/g, "'"); |
---|
461 | return ''; |
---|
462 | } |
---|
463 | }; |
---|
464 | EJS.newRequest = function(){ |
---|
465 | var factories = [function() { return new ActiveXObject("Msxml2.XMLHTTP"); },function() { return new XMLHttpRequest(); },function() { return new ActiveXObject("Microsoft.XMLHTTP"); }]; |
---|
466 | for(var i = 0; i < factories.length; i++) { |
---|
467 | try { |
---|
468 | var request = factories[i](); |
---|
469 | if (request != null) return request; |
---|
470 | } |
---|
471 | catch(e) { continue;} |
---|
472 | } |
---|
473 | } |
---|
474 | |
---|
475 | EJS.request = function(path){ |
---|
476 | var request = new EJS.newRequest() |
---|
477 | request.open("GET", path, false); |
---|
478 | |
---|
479 | try{request.send(null);} |
---|
480 | catch(e){return null;} |
---|
481 | |
---|
482 | if ( request.status == 404 || request.status == 2 ||(request.status == 0 && request.responseText == '') ) return null; |
---|
483 | |
---|
484 | return request.responseText |
---|
485 | } |
---|
486 | EJS.ajax_request = function(params){ |
---|
487 | params.method = ( params.method ? params.method : 'GET') |
---|
488 | |
---|
489 | var request = new EJS.newRequest(); |
---|
490 | request.onreadystatechange = function(){ |
---|
491 | if(request.readyState == 4){ |
---|
492 | if(request.status == 200){ |
---|
493 | params.onComplete(request) |
---|
494 | }else |
---|
495 | { |
---|
496 | params.onComplete(request) |
---|
497 | } |
---|
498 | } |
---|
499 | } |
---|
500 | request.open(params.method, params.url) |
---|
501 | request.send(null) |
---|
502 | } |
---|
503 | |
---|
504 | |
---|
505 | })(); |
---|