Created
November 17, 2011 14:17
-
-
Save fightbulc/1373235 to your computer and use it in GitHub Desktop.
icanhaz updated with mustache 0.4.0-dev
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
/*! | |
ICanHaz.js version 0.9 -- by @HenrikJoreteg | |
More info at: http://icanhazjs.com | |
*/ | |
(function($) { | |
/* | |
mustache.js — Logic-less templates in JavaScript | |
See http://mustache.github.com/ for more info. | |
*/ | |
var Mustache = function() { | |
var regexCache = {}; | |
var Renderer = function() {}; | |
Renderer.prototype = { | |
otag: "{{", | |
ctag: "}}", | |
pragmas: {}, | |
buffer: [], | |
pragmas_implemented: { | |
"IMPLICIT-ITERATOR": true | |
}, | |
context: {}, | |
render: function(template, context, partials, in_recursion) { | |
// reset buffer & set context | |
if (!in_recursion) { | |
this.context = context; | |
this.buffer = []; // TODO: make this non-lazy | |
} | |
// fail fast | |
if (!this.includes("", template)) { | |
if (in_recursion) { | |
return template; | |
} else { | |
this.send(template); | |
return; | |
} | |
} | |
// get the pragmas together | |
template = this.render_pragmas(template); | |
// render the template | |
var html = this.render_section(template, context, partials); | |
// render_section did not find any sections, we still need to render the tags | |
if (html === false) { | |
html = this.render_tags(template, context, partials, in_recursion); | |
} | |
if (in_recursion) { | |
return html; | |
} else { | |
this.sendLines(html); | |
} | |
}, | |
/* | |
Sends parsed lines | |
*/ | |
send: function(line) { | |
if (line !== "") { | |
this.buffer.push(line); | |
} | |
}, | |
sendLines: function(text) { | |
if (text) { | |
var lines = text.split("\n"); | |
for (var i = 0; i < lines.length; i++) { | |
this.send(lines[i]); | |
} | |
} | |
}, | |
/* | |
Looks for %PRAGMAS | |
*/ | |
render_pragmas: function(template) { | |
// no pragmas | |
if (!this.includes("%", template)) { | |
return template; | |
} | |
var that = this; | |
var regex = this.getCachedRegex("render_pragmas", function(otag, ctag) { | |
return new RegExp(otag + "%([\\w-]+) ?([\\w]+=[\\w]+)?" + ctag, "g"); | |
}); | |
return template.replace(regex, function(match, pragma, options) { | |
if (!that.pragmas_implemented[pragma]) { | |
throw ({ | |
message: "This implementation of mustache doesn't understand the '" + pragma + "' pragma" | |
}); | |
} | |
that.pragmas[pragma] = {}; | |
if (options) { | |
var opts = options.split("="); | |
that.pragmas[pragma][opts[0]] = opts[1]; | |
} | |
return ""; | |
// ignore unknown pragmas silently | |
}); | |
}, | |
/* | |
Tries to find a partial in the curent scope and render it | |
*/ | |
render_partial: function(name, context, partials) { | |
name = this.trim(name); | |
if (!partials || partials[name] === undefined) { | |
throw ({ | |
message: "unknown_partial '" + name + "'" | |
}); | |
} | |
if (typeof(context[name]) != "object") { | |
return this.render(partials[name], context, partials, true); | |
} | |
return this.render(partials[name], context[name], partials, true); | |
}, | |
/* | |
Renders inverted (^) and normal (#) sections | |
*/ | |
render_section: function(template, context, partials) { | |
if (!this.includes("#", template) && !this.includes("^", template)) { | |
// did not render anything, there were no sections | |
return false; | |
} | |
var that = this; | |
var regex = this.getCachedRegex("render_section", function(otag, ctag) { | |
// This regex matches _the first_ section ({{#foo}}{{/foo}}), and captures the remainder | |
return new RegExp("^([\\s\\S]*?)" + // all the crap at the beginning that is not {{*}} ($1) | |
otag + // {{ | |
"(\\^|\\#)\\s*(.+)\\s*" + // #foo (# == $2, foo == $3) | |
ctag + // }} | |
"\n*([\\s\\S]*?)" + // between the tag ($2). leading newlines are dropped | |
otag + // {{ | |
"\\/\\s*\\3\\s*" + // /foo (backreference to the opening tag). | |
ctag + // }} | |
"\\s*([\\s\\S]*)$", // everything else in the string ($4). leading whitespace is dropped. | |
"g"); | |
}); | |
// for each {{#foo}}{{/foo}} section do... | |
return template.replace(regex, function(match, before, type, name, content, after) { | |
// before contains only tags, no sections | |
var renderedBefore = before ? that.render_tags(before, context, partials, true) : "", | |
// after may contain both sections and tags, so use full rendering function | |
renderedAfter = after ? that.render(after, context, partials, true) : "", | |
// will be computed below | |
renderedContent, value = that.find(name, context); | |
if (type === "^") { // inverted section | |
if (!value || that.is_array(value) && value.length === 0) { | |
// false or empty list, render it | |
renderedContent = that.render(content, context, partials, true); | |
} else { | |
renderedContent = ""; | |
} | |
} else if (type === "#") { // normal section | |
if (that.is_array(value)) { // Enumerable, Let's loop! | |
renderedContent = that.map(value, function(row) { | |
return that.render(content, that.create_context(row), partials, true); | |
}).join(""); | |
} else if (that.is_object(value)) { // Object, Use it as subcontext! | |
renderedContent = that.render(content, that.create_context(value), partials, true); | |
} else if (typeof value === "function") { | |
// higher order section | |
renderedContent = value.call(context, content, function(text) { | |
return that.render(text, context, partials, true); | |
}); | |
} else if (value) { // boolean section | |
renderedContent = that.render(content, context, partials, true); | |
} else { | |
renderedContent = ""; | |
} | |
} | |
return renderedBefore + renderedContent + renderedAfter; | |
}); | |
}, | |
/* | |
Replace {{foo}} and friends with values from our view | |
*/ | |
render_tags: function(template, context, partials, in_recursion) { | |
// tit for tat | |
var that = this; | |
var new_regex = function() { | |
return that.getCachedRegex("render_tags", function(otag, ctag) { | |
return new RegExp(otag + "(=|!|>|\\{|%)?([^\\/#\\^]+?)\\1?" + ctag + "+", "g"); | |
}); | |
}; | |
var regex = new_regex(); | |
var tag_replace_callback = function(match, operator, name) { | |
switch (operator) { | |
case "!": | |
// ignore comments | |
return ""; | |
case "=": | |
// set new delimiters, rebuild the replace regexp | |
that.set_delimiters(name); | |
regex = new_regex(); | |
return ""; | |
case ">": | |
// render partial | |
return that.render_partial(name, context, partials); | |
case "{": | |
// the triple mustache is unescaped | |
return that.find(name, context); | |
default: | |
// escape the value | |
return that.escape(that.find(name, context)); | |
} | |
}; | |
var lines = template.split("\n"); | |
for (var i = 0; i < lines.length; i++) { | |
lines[i] = lines[i].replace(regex, tag_replace_callback, this); | |
if (!in_recursion) { | |
this.send(lines[i]); | |
} | |
} | |
if (in_recursion) { | |
return lines.join("\n"); | |
} | |
}, | |
set_delimiters: function(delimiters) { | |
var dels = delimiters.split(" "); | |
this.otag = this.escape_regex(dels[0]); | |
this.ctag = this.escape_regex(dels[1]); | |
}, | |
escape_regex: function(text) { | |
// thank you Simon Willison | |
if (!arguments.callee.sRE) { | |
var specials = ['/', '.', '*', '+', '?', '|', '(', ')', '[', ']', '{', '}', '\\']; | |
arguments.callee.sRE = new RegExp('(\\' + specials.join('|\\') + ')', 'g'); | |
} | |
return text.replace(arguments.callee.sRE, '\\$1'); | |
}, | |
/* | |
find `name` in current `context`. That is find me a value | |
from the view object | |
*/ | |
find: function(name, context) { | |
name = this.trim(name); | |
// Checks whether a value is thruthy or false or 0 | |
function is_kinda_truthy(bool) { | |
return bool === false || bool === 0 || bool; | |
} | |
var value; | |
// check for dot notation eg. foo.bar | |
if (name.match(/([a-z_]+)\./ig)) { | |
var childValue = this.walk_context(name, context); | |
if (is_kinda_truthy(childValue)) { | |
value = childValue; | |
} | |
} else { | |
if (is_kinda_truthy(context[name])) { | |
value = context[name]; | |
} else if (is_kinda_truthy(this.context[name])) { | |
value = this.context[name]; | |
} | |
} | |
if (typeof value === "function") { | |
return value.apply(context); | |
} | |
if (value !== undefined) { | |
return value; | |
} | |
// silently ignore unkown variables | |
return ""; | |
}, | |
walk_context: function(name, context) { | |
var path = name.split('.'); | |
// if the var doesn't exist in current context, check the top level context | |
var value_context = (context[path[0]] != undefined) ? context : this.context; | |
var value = value_context[path.shift()]; | |
while (value != undefined && path.length > 0) { | |
value_context = value; | |
value = value[path.shift()]; | |
} | |
// if the value is a function, call it, binding the correct context | |
if (typeof value === "function") { | |
return value.apply(value_context); | |
} | |
return value; | |
}, | |
// Utility methods | |
/* includes tag */ | |
includes: function(needle, haystack) { | |
return haystack.indexOf(this.otag + needle) != -1; | |
}, | |
/* | |
Does away with nasty characters | |
*/ | |
escape: function(s) { | |
s = String(s === null ? "" : s); | |
return s.replace(/&(?!\w+;)|["'<>\\]/g, function(s) { | |
switch (s) { | |
case "&": | |
return "&"; | |
case '"': | |
return '"'; | |
case "'": | |
return '''; | |
case "<": | |
return "<"; | |
case ">": | |
return ">"; | |
default: | |
return s; | |
} | |
}); | |
}, | |
// by @langalex, support for arrays of strings | |
create_context: function(_context) { | |
if (this.is_object(_context)) { | |
return _context; | |
} else { | |
var iterator = "."; | |
if (this.pragmas["IMPLICIT-ITERATOR"]) { | |
iterator = this.pragmas["IMPLICIT-ITERATOR"].iterator; | |
} | |
var ctx = {}; | |
ctx[iterator] = _context; | |
return ctx; | |
} | |
}, | |
is_object: function(a) { | |
return a && typeof a == "object"; | |
}, | |
is_array: function(a) { | |
return Object.prototype.toString.call(a) === '[object Array]'; | |
}, | |
/* | |
Gets rid of leading and trailing whitespace | |
*/ | |
trim: function(s) { | |
return s.replace(/^\s*|\s*$/g, ""); | |
}, | |
/* | |
Why, why, why? Because IE. Cry, cry cry. | |
*/ | |
map: function(array, fn) { | |
if (typeof array.map == "function") { | |
return array.map(fn); | |
} else { | |
var r = []; | |
var l = array.length; | |
for (var i = 0; i < l; i++) { | |
r.push(fn(array[i])); | |
} | |
return r; | |
} | |
}, | |
getCachedRegex: function(name, generator) { | |
var byOtag = regexCache[this.otag]; | |
if (!byOtag) { | |
byOtag = regexCache[this.otag] = {}; | |
} | |
var byCtag = byOtag[this.ctag]; | |
if (!byCtag) { | |
byCtag = byOtag[this.ctag] = {}; | |
} | |
var regex = byCtag[name]; | |
if (!regex) { | |
regex = byCtag[name] = generator(this.otag, this.ctag); | |
} | |
return regex; | |
} | |
}; | |
return ({ | |
name: "mustache.js", | |
version: "0.4.0-dev", | |
/* | |
Turns a template and view into HTML | |
*/ | |
to_html: function(template, view, partials, send_fun) { | |
var renderer = new Renderer(); | |
if (send_fun) { | |
renderer.send = send_fun; | |
} | |
renderer.render(template, view || {}, partials); | |
if (!send_fun) { | |
return renderer.buffer.join("\n"); | |
} | |
} | |
}); | |
}(); | |
/*! | |
ICanHaz.js -- by @HenrikJoreteg | |
*/ | |
/*global jQuery */ | |
function ICanHaz() { | |
var self = this; | |
self.VERSION = "0.9"; | |
self.templates = {}; | |
self.partials = {}; | |
// public function for adding templates | |
// We're enforcing uniqueness to avoid accidental template overwrites. | |
// If you want a different template, it should have a different name. | |
self.addTemplate = function(name, templateString) { | |
if (self[name]) throw "Invalid name: " + name + "."; | |
if (self.templates[name]) throw "Template \" + name + \" exists"; | |
self.templates[name] = templateString; | |
self[name] = function(data, raw) { | |
data = data || {}; | |
var result = Mustache.to_html(self.templates[name], data, self.partials); | |
return raw ? result : $(result); | |
}; | |
}; | |
// public function for adding partials | |
self.addPartial = function(name, templateString) { | |
if (self.partials[name]) { | |
throw "Partial \" + name + \" exists"; | |
} else { | |
self.partials[name] = templateString; | |
} | |
}; | |
// grabs templates from the DOM and caches them. | |
// Loop through and add templates. | |
// Whitespace at beginning and end of all templates inside <script> tags will | |
// be trimmed. If you want whitespace around a partial, add it in the parent, | |
// not the partial. Or do it explicitly using <br/> or | |
self.grabTemplates = function() { | |
$('script[type="text/html"]').each(function(a, b) { | |
var script = $((typeof a === 'number') ? b : a), | |
// Zepto doesn't bind this | |
text = (''.trim) ? script.html().trim() : $.trim(script.html()); | |
self[script.hasClass('partial') ? 'addPartial' : 'addTemplate'](script.attr('id'), text); | |
script.remove(); | |
}); | |
}; | |
// clears all retrieval functions and empties caches | |
self.clearAll = function() { | |
for (var key in self.templates) { | |
delete self[key]; | |
} | |
self.templates = {}; | |
self.partials = {}; | |
}; | |
self.refresh = function() { | |
self.clearAll(); | |
self.grabTemplates(); | |
}; | |
} | |
window.ich = new ICanHaz(); | |
// init itself on document ready | |
$(function() { | |
ich.grabTemplates(); | |
}); | |
})(window.jQuery || window.Zepto); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment