Skip to content

Instantly share code, notes, and snippets.

@kwatch
Created August 7, 2012 03:36
Show Gist options
  • Save kwatch/3281188 to your computer and use it in GitHub Desktop.
Save kwatch/3281188 to your computer and use it in GitHub Desktop.
Shotenjin
/*
* $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 = { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;' };
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