506 lines
13 KiB
JavaScript
506 lines
13 KiB
JavaScript
(function(){
|
|
|
|
|
|
var rsplit = function(string, regex) {
|
|
var result = regex.exec(string),retArr = new Array(), first_idx, last_idx, first_bit;
|
|
while (result != null)
|
|
{
|
|
first_idx = result.index; last_idx = regex.lastIndex;
|
|
if ((first_idx) != 0)
|
|
{
|
|
first_bit = string.substring(0,first_idx);
|
|
retArr.push(string.substring(0,first_idx));
|
|
string = string.slice(first_idx);
|
|
}
|
|
retArr.push(result[0]);
|
|
string = string.slice(result[0].length);
|
|
result = regex.exec(string);
|
|
}
|
|
if (! string == '')
|
|
{
|
|
retArr.push(string);
|
|
}
|
|
return retArr;
|
|
},
|
|
chop = function(string){
|
|
return string.substr(0, string.length - 1);
|
|
},
|
|
extend = function(d, s){
|
|
for(var n in s){
|
|
if(s.hasOwnProperty(n)) d[n] = s[n]
|
|
}
|
|
}
|
|
|
|
|
|
EJS = function( options ){
|
|
options = typeof options == "string" ? {view: options} : options
|
|
this.set_options(options);
|
|
if(options.precompiled){
|
|
this.template = {};
|
|
this.template.process = options.precompiled;
|
|
EJS.update(this.name, this);
|
|
return;
|
|
}
|
|
if(options.element)
|
|
{
|
|
if(typeof options.element == 'string'){
|
|
var name = options.element
|
|
options.element = document.getElementById( options.element )
|
|
if(options.element == null) throw name+'does not exist!'
|
|
}
|
|
if(options.element.value){
|
|
this.text = options.element.value
|
|
}else{
|
|
this.text = options.element.innerHTML
|
|
}
|
|
this.name = options.element.id
|
|
this.type = '['
|
|
}else if(options.url){
|
|
options.url = EJS.endExt(options.url, this.extMatch);
|
|
this.name = this.name ? this.name : options.url;
|
|
var url = options.url
|
|
//options.view = options.absolute_url || options.view || options.;
|
|
var template = EJS.get(this.name /*url*/, this.cache);
|
|
if (template) return template;
|
|
if (template == EJS.INVALID_PATH) return null;
|
|
try{
|
|
this.text = EJS.request( url+(this.cache ? '' : '?'+Math.random() ));
|
|
}catch(e){}
|
|
|
|
if(this.text == null){
|
|
throw( {type: 'EJS', message: 'There is no template at '+url} );
|
|
}
|
|
//this.name = url;
|
|
}
|
|
var template = new EJS.Compiler(this.text, this.type);
|
|
|
|
template.compile(options, this.name);
|
|
|
|
|
|
EJS.update(this.name, this);
|
|
this.template = template;
|
|
};
|
|
/* @Prototype*/
|
|
EJS.prototype = {
|
|
/**
|
|
* Renders an object with extra view helpers attached to the view.
|
|
* @param {Object} object data to be rendered
|
|
* @param {Object} extra_helpers an object with additonal view helpers
|
|
* @return {String} returns the result of the string
|
|
*/
|
|
render : function(object, extra_helpers){
|
|
object = object || {};
|
|
this._extra_helpers = extra_helpers;
|
|
var v = new EJS.Helpers(object, extra_helpers || {});
|
|
return this.template.process.call(object, object,v);
|
|
},
|
|
update : function(element, options){
|
|
if(typeof element == 'string'){
|
|
element = document.getElementById(element)
|
|
}
|
|
if(options == null){
|
|
_template = this;
|
|
return function(object){
|
|
EJS.prototype.update.call(_template, element, object)
|
|
}
|
|
}
|
|
if(typeof options == 'string'){
|
|
params = {}
|
|
params.url = options
|
|
_template = this;
|
|
params.onComplete = function(request){
|
|
var object = eval( request.responseText )
|
|
EJS.prototype.update.call(_template, element, object)
|
|
}
|
|
EJS.ajax_request(params)
|
|
}else
|
|
{
|
|
element.innerHTML = this.render(options)
|
|
}
|
|
},
|
|
out : function(){
|
|
return this.template.out;
|
|
},
|
|
/**
|
|
* Sets options on this view to be rendered with.
|
|
* @param {Object} options
|
|
*/
|
|
set_options : function(options){
|
|
this.type = options.type || EJS.type;
|
|
this.cache = options.cache != null ? options.cache : EJS.cache;
|
|
this.text = options.text || null;
|
|
this.name = options.name || null;
|
|
this.ext = options.ext || EJS.ext;
|
|
this.extMatch = new RegExp(this.ext.replace(/\./, '\.'));
|
|
}
|
|
};
|
|
EJS.endExt = function(path, match){
|
|
if(!path) return null;
|
|
match.lastIndex = 0
|
|
return path+ (match.test(path) ? '' : this.ext )
|
|
}
|
|
|
|
|
|
|
|
|
|
/* @Static*/
|
|
EJS.Scanner = function(source, left, right) {
|
|
|
|
extend(this,
|
|
{left_delimiter: left +'%',
|
|
right_delimiter: '%'+right,
|
|
double_left: left+'%%',
|
|
double_right: '%%'+right,
|
|
left_equal: left+'%=',
|
|
left_comment: left+'%#'})
|
|
|
|
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)') ;
|
|
|
|
this.source = source;
|
|
this.stag = null;
|
|
this.lines = 0;
|
|
};
|
|
|
|
EJS.Scanner.to_text = function(input){
|
|
if(input == null || input === undefined)
|
|
return '';
|
|
if(input instanceof Date)
|
|
return input.toDateString();
|
|
if(input.toString)
|
|
return input.toString();
|
|
return '';
|
|
};
|
|
|
|
EJS.Scanner.prototype = {
|
|
scan: function(block) {
|
|
scanline = this.scanline;
|
|
regex = this.SplitRegexp;
|
|
if (! this.source == '')
|
|
{
|
|
var source_split = rsplit(this.source, /\n/);
|
|
for(var i=0; i<source_split.length; i++) {
|
|
var item = source_split[i];
|
|
this.scanline(item, regex, block);
|
|
}
|
|
}
|
|
},
|
|
scanline: function(line, regex, block) {
|
|
this.lines++;
|
|
var line_split = rsplit(line, regex);
|
|
for(var i=0; i<line_split.length; i++) {
|
|
var token = line_split[i];
|
|
if (token != null) {
|
|
try{
|
|
block(token, this);
|
|
}catch(e){
|
|
throw {type: 'EJS.Scanner', line: this.lines};
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
|
|
EJS.Buffer = function(pre_cmd, post_cmd) {
|
|
this.line = new Array();
|
|
this.script = "";
|
|
this.pre_cmd = pre_cmd;
|
|
this.post_cmd = post_cmd;
|
|
for (var i=0; i<this.pre_cmd.length; i++)
|
|
{
|
|
this.push(pre_cmd[i]);
|
|
}
|
|
};
|
|
EJS.Buffer.prototype = {
|
|
|
|
push: function(cmd) {
|
|
this.line.push(cmd);
|
|
},
|
|
|
|
cr: function() {
|
|
this.script = this.script + this.line.join('; ');
|
|
this.line = new Array();
|
|
this.script = this.script + "\n";
|
|
},
|
|
|
|
close: function() {
|
|
if (this.line.length > 0)
|
|
{
|
|
for (var i=0; i<this.post_cmd.length; i++){
|
|
this.push(pre_cmd[i]);
|
|
}
|
|
this.script = this.script + this.line.join('; ');
|
|
line = null;
|
|
}
|
|
}
|
|
|
|
};
|
|
|
|
|
|
EJS.Compiler = function(source, left) {
|
|
this.pre_cmd = ['var ___ViewO = [];'];
|
|
this.post_cmd = new Array();
|
|
this.source = ' ';
|
|
if (source != null)
|
|
{
|
|
if (typeof source == 'string')
|
|
{
|
|
source = source.replace(/\r\n/g, "\n");
|
|
source = source.replace(/\r/g, "\n");
|
|
this.source = source;
|
|
}else if (source.innerHTML){
|
|
this.source = source.innerHTML;
|
|
}
|
|
if (typeof this.source != 'string'){
|
|
this.source = "";
|
|
}
|
|
}
|
|
left = left || '<';
|
|
var right = '>';
|
|
switch(left) {
|
|
case '[':
|
|
right = ']';
|
|
break;
|
|
case '<':
|
|
break;
|
|
default:
|
|
throw left+' is not a supported deliminator';
|
|
break;
|
|
}
|
|
this.scanner = new EJS.Scanner(this.source, left, right);
|
|
this.out = '';
|
|
};
|
|
EJS.Compiler.prototype = {
|
|
compile: function(options, name) {
|
|
options = options || {};
|
|
this.out = '';
|
|
var put_cmd = "___ViewO.push(";
|
|
var insert_cmd = put_cmd;
|
|
var buff = new EJS.Buffer(this.pre_cmd, this.post_cmd);
|
|
var content = '';
|
|
var clean = function(content)
|
|
{
|
|
content = content.replace(/\\/g, '\\\\');
|
|
content = content.replace(/\n/g, '\\n');
|
|
content = content.replace(/"/g, '\\"');
|
|
return content;
|
|
};
|
|
this.scanner.scan(function(token, scanner) {
|
|
if (scanner.stag == null)
|
|
{
|
|
switch(token) {
|
|
case '\n':
|
|
content = content + "\n";
|
|
buff.push(put_cmd + '"' + clean(content) + '");');
|
|
buff.cr();
|
|
content = '';
|
|
break;
|
|
case scanner.left_delimiter:
|
|
case scanner.left_equal:
|
|
case scanner.left_comment:
|
|
scanner.stag = token;
|
|
if (content.length > 0)
|
|
{
|
|
buff.push(put_cmd + '"' + clean(content) + '")');
|
|
}
|
|
content = '';
|
|
break;
|
|
case scanner.double_left:
|
|
content = content + scanner.left_delimiter;
|
|
break;
|
|
default:
|
|
content = content + token;
|
|
break;
|
|
}
|
|
}
|
|
else {
|
|
switch(token) {
|
|
case scanner.right_delimiter:
|
|
switch(scanner.stag) {
|
|
case scanner.left_delimiter:
|
|
if (content[content.length - 1] == '\n')
|
|
{
|
|
content = chop(content);
|
|
buff.push(content);
|
|
buff.cr();
|
|
}
|
|
else {
|
|
buff.push(content);
|
|
}
|
|
break;
|
|
case scanner.left_equal:
|
|
buff.push(insert_cmd + "(EJS.Scanner.to_text(" + content + ")))");
|
|
break;
|
|
}
|
|
scanner.stag = null;
|
|
content = '';
|
|
break;
|
|
case scanner.double_right:
|
|
content = content + scanner.right_delimiter;
|
|
break;
|
|
default:
|
|
content = content + token;
|
|
break;
|
|
}
|
|
}
|
|
});
|
|
if (content.length > 0)
|
|
{
|
|
// Chould be content.dump in Ruby
|
|
buff.push(put_cmd + '"' + clean(content) + '")');
|
|
}
|
|
buff.close();
|
|
this.out = buff.script + ";";
|
|
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;}};";
|
|
|
|
try{
|
|
eval(to_be_evaled);
|
|
}catch(e){
|
|
if(typeof JSLINT != 'undefined'){
|
|
JSLINT(this.out);
|
|
for(var i = 0; i < JSLINT.errors.length; i++){
|
|
var error = JSLINT.errors[i];
|
|
if(error.reason != "Unnecessary semicolon."){
|
|
error.line++;
|
|
var e = new Error();
|
|
e.lineNumber = error.line;
|
|
e.message = error.reason;
|
|
if(options.view)
|
|
e.fileName = options.view;
|
|
throw e;
|
|
}
|
|
}
|
|
}else{
|
|
throw e;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
|
|
//type, cache, folder
|
|
/**
|
|
* Sets default options for all views
|
|
* @param {Object} options Set view with the following options
|
|
* <table class="options">
|
|
<tbody><tr><th>Option</th><th>Default</th><th>Description</th></tr>
|
|
<tr>
|
|
<td>type</td>
|
|
<td>'<'</td>
|
|
<td>type of magic tags. Options are '<' or '['
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td>cache</td>
|
|
<td>true in production mode, false in other modes</td>
|
|
<td>true to cache template.
|
|
</td>
|
|
</tr>
|
|
</tbody></table>
|
|
*
|
|
*/
|
|
EJS.config = function(options){
|
|
EJS.cache = options.cache != null ? options.cache : EJS.cache;
|
|
EJS.type = options.type != null ? options.type : EJS.type;
|
|
EJS.ext = options.ext != null ? options.ext : EJS.ext;
|
|
|
|
var templates_directory = EJS.templates_directory || {}; //nice and private container
|
|
EJS.templates_directory = templates_directory;
|
|
EJS.get = function(path, cache){
|
|
if(cache == false) return null;
|
|
if(templates_directory[path]) return templates_directory[path];
|
|
return null;
|
|
};
|
|
|
|
EJS.update = function(path, template) {
|
|
if(path == null) return;
|
|
templates_directory[path] = template ;
|
|
};
|
|
|
|
EJS.INVALID_PATH = -1;
|
|
};
|
|
EJS.config( {cache: true, type: '<', ext: '.ejs' } );
|
|
|
|
|
|
|
|
/**
|
|
* @constructor
|
|
* By adding functions to EJS.Helpers.prototype, those functions will be available in the
|
|
* views.
|
|
* @init Creates a view helper. This function is called internally. You should never call it.
|
|
* @param {Object} data The data passed to the view. Helpers have access to it through this._data
|
|
*/
|
|
EJS.Helpers = function(data, extras){
|
|
this._data = data;
|
|
this._extras = extras;
|
|
extend(this, extras );
|
|
};
|
|
/* @prototype*/
|
|
EJS.Helpers.prototype = {
|
|
/**
|
|
* Renders a new view. If data is passed in, uses that to render the view.
|
|
* @param {Object} options standard options passed to a new view.
|
|
* @param {optional:Object} data
|
|
* @return {String}
|
|
*/
|
|
view: function(options, data, helpers){
|
|
if(!helpers) helpers = this._extras
|
|
if(!data) data = this._data;
|
|
return new EJS(options).render(data, helpers);
|
|
},
|
|
/**
|
|
* For a given value, tries to create a human representation.
|
|
* @param {Object} input the value being converted.
|
|
* @param {Object} null_text what text should be present if input == null or undefined, defaults to ''
|
|
* @return {String}
|
|
*/
|
|
to_text: function(input, null_text) {
|
|
if(input == null || input === undefined) return null_text || '';
|
|
if(input instanceof Date) return input.toDateString();
|
|
if(input.toString) return input.toString().replace(/\n/g, '<br />').replace(/''/g, "'");
|
|
return '';
|
|
}
|
|
};
|
|
EJS.newRequest = function(){
|
|
var factories = [function() { return new ActiveXObject("Msxml2.XMLHTTP"); },function() { return new XMLHttpRequest(); },function() { return new ActiveXObject("Microsoft.XMLHTTP"); }];
|
|
for(var i = 0; i < factories.length; i++) {
|
|
try {
|
|
var request = factories[i]();
|
|
if (request != null) return request;
|
|
}
|
|
catch(e) { continue;}
|
|
}
|
|
}
|
|
|
|
EJS.request = function(path){
|
|
var request = new EJS.newRequest()
|
|
request.open("GET", path, false);
|
|
|
|
try{request.send(null);}
|
|
catch(e){return null;}
|
|
|
|
if ( request.status == 404 || request.status == 2 ||(request.status == 0 && request.responseText == '') ) return null;
|
|
|
|
return request.responseText
|
|
}
|
|
EJS.ajax_request = function(params){
|
|
params.method = ( params.method ? params.method : 'GET')
|
|
|
|
var request = new EJS.newRequest();
|
|
request.onreadystatechange = function(){
|
|
if(request.readyState == 4){
|
|
if(request.status == 200){
|
|
params.onComplete(request)
|
|
}else
|
|
{
|
|
params.onComplete(request)
|
|
}
|
|
}
|
|
}
|
|
request.open(params.method, params.url)
|
|
request.send(null)
|
|
}
|
|
|
|
|
|
})();
|