Created
August 7, 2012 03:36
-
-
Save kwatch/3281188 to your computer and use it in GitHub Desktop.
Shotenjin
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* $Release: $ | |
* $Copyright: copyright(c) 2007-2011 kuwata-lab.com all rights reserved. $ | |
* $License: MIT License $ | |
*/ | |
/** | |
* client-side template engine | |
* | |
* usage: | |
* <script src="/js/jquery.js"></script> <!-- not necessary, but recommended --> | |
* <script src="/js/shotenjin.js"></script> | |
* | |
* <div id="template1" style="display:none"> | |
* <ul> | |
* <?js for (var i = 0, n = items.length; i < n; i++) { ?> | |
* <li>#{i}: ${items[i]}</li> | |
* <?js } ?> | |
* </ul> | |
* </div> | |
* <div id="placeholder1"></div> | |
* | |
* <script> | |
* var context = { | |
* items: ["Haruhi", "Mikuru","Yuki"] | |
* }; | |
* | |
* /// | |
* /// render with jQuery | |
* /// | |
* var html = $('#template1').renderWith(context, true); // return html string | |
* $('#template1').renderWith(context, '#placeholder1'); // replace '#placeholder1' content by html | |
* $('#template1').renderWith(context).appendTo('#placeholder1'); // append html into into #placeholder1 | |
* | |
* /// | |
* /// render without jQuery | |
* /// | |
* var tmpl = document.getElementById('template1').innerHTML; | |
* var html = Shotenjin.render(tmpl, context); | |
* document.getElementById('placeholder1').innerHTML = html; | |
* </script> | |
* | |
*/ | |
/// | |
/// namespace | |
/// | |
var Shotenjin = {}; | |
if (typeof exports !== 'undefined') Shotenjin = exports; // for node.js | |
/// | |
/// helpers | |
/// | |
Shotenjin._escape_table = { '&': '&', '<': '<', '>': '>', '"': '"' }; | |
Shotenjin._escape_func = function _escape_func(m) { | |
return Shotenjin._escape_table[m]; | |
}; | |
Shotenjin.escapeHtml = function escapeHtml(s) { | |
if (s == null) return ''; | |
if (typeof s !== 'string') return s; | |
return s.replace(/[&<>"]/g, Shotenjin._escape_func); | |
}; | |
Shotenjin.toStr = function toStr(s) { | |
if (s == null) return ''; | |
return s; | |
}; | |
Shotenjin.strip = function strip(s) { | |
if (! s) return s; | |
//return s.replace(/^\s+|\s+$/g, ''); | |
return s.replace(/^\s+/, '').replace(/\s+$/, ''); | |
}; | |
// ex. {x: 10, y: 'foo'} | |
// => "var x = _context['x'];\nvar y = _conntext['y'];\n" | |
Shotenjin._setlocalvarscode = function _setlocalvarscode(obj) { | |
var sb = "", p; | |
for (p in obj) sb += "var " + p + " = _context['" + p + "'];\n"; | |
return sb; | |
}; | |
var escapeHtml = Shotenjin.escapeHtml; | |
var toStr = Shotenjin.toStr; | |
/// | |
/// template class | |
/// | |
Shotenjin.Template = function Template(input, properties) { | |
if (typeof input === 'object' && ! properties) { | |
input = null; | |
properties = input; | |
} | |
if (properties) { | |
if (properties.tostrfunc) this.tostrfunc = properties.tostrfunc; | |
if (properties.escapefunc) this.escapefunc = properties.escapefunc; | |
} | |
if (input) this.convert(input); | |
}; | |
(function(def) { | |
def.tostrfunc = 'toStr'; | |
def.escapefunc = 'escapeHtml'; | |
def.script = null; | |
def.preamble = "var _buf = ''; "; | |
def.postamble = "_buf\n"; | |
def.convert = function convert(input) { | |
this.args = null; | |
input = input.replace(/<!--\?js/g, '<?js').replace(/\?-->/g, '?>'); // for Chrome | |
return this.script = this.preamble + this.parseStatements(input) + this.postamble; | |
}; | |
def.parseStatements = function parseStatements(input) { | |
var sb = '', | |
pos = 0, | |
regexp = /(^[ \t]*)?<\?js(\s(?:.|\n)*?) ?\?>([ \t]*\r?\n)?/mg, | |
ended_with_nl = true, | |
remained = null, | |
m, lspace, stmt, rspace, is_bol, text, rest; | |
while ((m = regexp.exec(input)) != null) { | |
lspace = m[1]; stmt = m[2]; rspace = m[3]; | |
is_bol = lspace || ended_with_nl; | |
text = input.substring(pos, m.index); | |
pos = m.index + m[0].length; | |
if (remained) { | |
text = remained + text; | |
remained = null; | |
} | |
if (is_bol && rspace) { | |
stmt = (lspace || '') + stmt + rspace; | |
} | |
else { | |
if (lspace) text += lspace; | |
remained = rspace; | |
} | |
if (text) sb += this.parseExpressions(text); | |
stmt = this._parseArgs(stmt); | |
sb += stmt; | |
} | |
rest = pos === 0 ? input : input.substring(pos); | |
sb += this.parseExpressions(rest); | |
return sb; | |
}; | |
def.args = null; | |
def._parseArgs = function _parseArgs(stmt) { | |
var m, sb, arr, args, i, n, arg; | |
if (this.args !== null) return stmt; | |
m = stmt.match(/^(\s*)\/\/@ARGS:?[ \t]+(.*?)(\r?\n)?$/); | |
if (! m) return stmt; | |
sb = m[1]; | |
arr = m[2].split(/,/); | |
args = []; | |
for (i = 0, n = arr.length; i < n; i++) { | |
arg = arr[i].replace(/^\s+/, '').replace(/\s+$/, ''); | |
args.push(arg); | |
sb += " var " + arg + "=_context." + arg + ";"; | |
} | |
sb += m[3]; | |
this.args = args; | |
return sb; | |
}; | |
def.parseExpressions = function parseExpressions(input) { | |
var sb, regexp, pos, m, text, s, indicator, expr, funcname, rest, is_newline; | |
if (! input) return ''; | |
sb = " _buf += "; | |
regexp = /([$#])\{(.*?)\}/g; | |
pos = 0; | |
while ((m = regexp.exec(input)) != null) { | |
text = input.substring(pos, m.index); | |
s = m[0]; | |
pos = m.index + s.length; | |
indicator = m[1]; | |
expr = m[2]; | |
funcname = indicator === "$" ? this.escapefunc : this.tostrfunc; | |
sb += "'" + this._escapeText(text) + "' + " + funcname + "(" + expr + ") + "; | |
} | |
rest = pos === 0 ? input : input.substring(pos); | |
is_newline = input.charAt(input.length-1) === "\n"; | |
sb += "'" + this._escapeText(rest, true) + (is_newline ? "';\n" : "';"); | |
return sb; | |
}; | |
def._escapeText = function _escapeText(text, eol) { | |
if (! text) return ""; | |
text = text.replace(/[\'\\]/g, '\\$&').replace(/\r?\n/g, '\\n\\\n'); | |
if (eol) text = text.replace(/\\n\\\n$/, "\\n"); | |
return text; | |
}; | |
def.render = function _render(_context) { | |
if (! _context) { | |
_context = {}; | |
} | |
else if (this.args === null) { | |
eval(Shotenjin._setlocalvarscode(_context)); | |
} | |
return eval(this.script); | |
}; | |
})(Shotenjin.Template.prototype); | |
/* | |
* convenient function | |
*/ | |
Shotenjin.render = function(template_str, context) { | |
var template, output, lastRendered = Shotenjin._lastRendered; | |
if (lastRendered[0] === template_str) { | |
template = lastRendered[1]; | |
} | |
else { | |
template = new Shotenjin.Template(template_str); | |
Shotenjin._lastRendered = [template_str, template]; | |
} | |
output = template.render(context); | |
return output; | |
}; | |
Shotenjin._lastRendered = [null, null]; | |
/* | |
* jQuery plugin | |
*/ | |
if (typeof jQuery !== 'undefined') { | |
jQuery.fn.extend({ | |
renderWith: function renderWith(context, option) { | |
var tmpl, html; | |
tmpl = this.html(); | |
if (tmpl == null) throw new Error("renderWith(): html template is null (maybe jQuery selector is wrong)"); | |
tmpl = tmpl.replace(/^\s*\<\!\-\-\?js/, '<'+'?js').replace(/\?\-\-\>\s*$/, '?>'); | |
//html = Shotenjin.render(tmpl, context); | |
if (! this._shotenjinTemplate) { | |
this._shotenjinTemplate = new Shotenjin.Template(tmpl); | |
} | |
html = this._shotenjinTemplate.render(context); | |
if (option === true) return html; | |
if (option) return jQuery(option).html(html); | |
return jQuery(html); | |
} | |
}); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment