Created
August 6, 2011 05:34
-
-
Save d4tocchini/1129047 to your computer and use it in GitHub Desktop.
This file contains 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
$ = jQuery | |
# Wrap in Closure to avoid global variables. | |
$ -> | |
### | |
ICanHaz.js version 0.9 -- by @HenrikJoreteg | |
More info at: http://icanhazjs.com | |
#8b d88 88 | |
#88b d888 ,d 88 | |
#8`8b d8'88 88 88 | |
#8 `8b d8' 88 88 88 ,adPPYba, MM88MMM ,adPPYYba, ,adPPYba, 88,dPPYba, ,adPPYba, | |
#8 `8b d8' 88 88 88 I8[ "" 88 "" `Y8 a8" "" 88P' "8a a8P_____88 | |
#8 `8b d8' 88 88 88 `"Y8ba, 88 ,adPPPPP88 8b 88 88 8PP""""""" | |
#8 `888' 88 "8a, ,a88 aa ]8I 88, 88, ,88 "8a, ,aa 88 88 "8b, ,aa | |
#8 `8' 88 `"YbbdP'Y8 `"YbbdP"' "Y888 `"8bbdP"Y8 `"Ybbd8"' 88 88 `"Ybbd8"' | |
mustache.js -- Logic-less templates in JavaScript | |
=========================================================== | |
by @janl (MIT Licensed, https://github.com/janl/mustache.js/blob/master/LICENSE). | |
See http://mustache.github.com/ for more info. | |
D4 customizations | |
======================================================= | |
Dependencies added | |
---------------------------- | |
- underscore | |
- underscore string! | |
- I MADE MUSTACHE REFERENCE ich global OBJECT!!!!! bad bad bad | |
Features added | |
------------------------------ | |
- iCanHaz onrender | |
----------------------------------------------------- | |
onrender="console.log(el);alert('{{#isNew}}isnt_ready2publish{{/isNew}} {{^isNew}}is_ready2publish{{/isNew}}');" | |
- allows for dynamic partials from the context variable | |
----------------------------------------------------- | |
context = {type:video} | |
{{>[type]_partial}} | |
- pass arguments to partials, as defaults or extends | |
----------------------------------------------------- | |
that = self = context | |
{{>[type]_partial(title:'newTitle')}} | |
{{>[type]_partial(title:'defaultTitle', as_defaults:true, reset_context:...)}} | |
{{>input_title( type:'largeTitle', sss:that.title, required:true, allowreturn:false )}} | |
as_defaults: false | |
reset_context: false | |
- makes a deep copy of the context that only effects the partial's context, not the partials and tags that are at the sibling level | |
- define partial's defaults | |
----------------------------------------------------- | |
ich[partial_name].defaults = { | |
} | |
- dot syntax added | |
----------------------------------------------------- | |
{{#Collection.item.attributes}} {{/Collection.item.attributes}} | |
instead of the horrendous: {{#Collection}} {{#item}} {{#attributes}} {{/attributes}} {{/item}} {{/Collection}} | |
- All I did was add the getValueFromContext() method | |
- Does it mess up the context though? hmm.... Not from the looks of it! | |
- code evaluation API | |
----------------------------------------------------- | |
- context | |
- template | |
- that = renderer | |
- el if ** | |
- {{*}} executes code before template is rendered | |
- {{**}} executes code after template is rendered???? | |
{{*(function(){return "<span>test</span>";})()}} | |
TBD: | |
- bind context to code execution! | |
- make all template available as partials! | |
### | |
class MustacheRenderer | |
otag: "{{" | |
ctag: "}}" | |
pragmas: {} | |
buffer: [] | |
pragmas_implemented: | |
"IMPLICIT-ITERATOR": true | |
context: {} | |
# D4: gets value from context from key that can include periods | |
# Allows for {{#Collection.item.attributes}} {{/Collection.item.attributes}} | |
valueFromContext: (key, context) -> | |
### From https://github.com/janl/mustache.js/commit/5890cbf9949e82833fef879424aec2968765ddaa | |
if(key is "." && context[key] != undefined) | |
return context[key] | |
Do I need this? | |
### | |
# D4 HACK NEEDS FIXING | |
# keys with brackets... {{RW.Tiles.current.att_meta[type]}} | |
if ( _.includes(key, ']') ) | |
dynamicValueKey = _(_(key).strRight('[')).strLeft(']') | |
# RECURSIVE POTENTIAL! | |
dynamicValue = context[dynamicValueKey] | |
if _.isString dynamicValue or _.isNumber dynamicValue | |
key = key.replace('[', '.') | |
key = key.replace(dynamicValueKey+']', dynamicValue) | |
else | |
LOG 'iCanHaz // valuefromcontext / ERROR! dynamic value isnt string' | |
LOG '*******************************************************************' | |
key = key.replace('['+dynamicValueKey+')', '') | |
# resolve nested perioud separated attributes from string | |
if ( _.includes(key, '.') ) | |
keyArray = key.split('.') | |
value = {} | |
_.each keyArray, (k) -> | |
value = context[k] | |
# some values may be a function that returns an object RW() | |
if _.isFunction(value) then value = value() | |
#alert(_.isObject(value)); | |
#if(_.isObject(value)){ | |
context = value | |
return value | |
# else | |
return context[key] | |
render: (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 | |
# this.checkForOnRenderFlags(template); | |
template = this.render_code(template, context); | |
template = this.render_pragmas(template); | |
html = this.render_section(template, context, partials); | |
if(in_recursion) | |
return this.render_tags(html, context, partials, in_recursion) | |
this.render_tags(html, context, partials, in_recursion); | |
# Sends parsed lines | |
send: (line) -> | |
if(line != "") | |
this.buffer.push(line); | |
# Executes code sections {{* }} | |
render_code: (template, context) -> | |
# no | |
if(!this.includes("*", template)) | |
return template; | |
### does it need to execute after template is rendered? | |
if(_(template).startsWith("**")) { | |
alert(template); | |
template = _.ltrim(template,'*'); | |
alert(template); | |
} | |
### | |
# find ALL code sections and replace executed code | |
that = this; | |
regex = new RegExp(this.otag + "\\*\\s*(.+?)\\s*" + this.ctag, "mg") | |
return template.replace regex, (match, code, options) -> | |
# is there another *, if so, | |
# render the code section after the template is rendered | |
if(_(code).startsWith("*")) | |
code = _.ltrim(code,'*') | |
#alert(that.hasOnRenderFlags); | |
#alert(code); | |
#stuff = "<span class=''>"+code+"</span>" | |
#eval(code); | |
return "" | |
else | |
return eval(code); | |
# Looks for %PRAGMAS | |
render_pragmas: (template) -> | |
# no pragmas | |
if(!this.includes("%", template)) | |
return template | |
that = this; | |
regex = new RegExp(this.otag + "%([\\w-]+) ?([\\w]+=[\\w]+)?" + this.ctag) | |
return template.replace regex, (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) | |
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: (name, context, partials) -> | |
name = this.trim(name) | |
context || context = {} | |
that = context | |
self = context | |
renderredValue = "" | |
alteredContext = {} | |
# D4: parse dynamic named partials inside [] | |
if (_.includes(name, '[', ']')) | |
propKey = _(_(name).strRight('[')).strLeft(']'); | |
# update dynamic name | |
name = name.replace('['+propKey+']', this.valueFromContext( propKey, context) ); | |
# D4: parse options in partial inside () | |
if _(name).endsWith(')') | |
# get options string | |
options = _(_(name).strRight('(')).strLeft(')') | |
# remove options from name | |
name = name.replace('('+options+')', '') | |
# if options aren't isn't wrapped in {}, then wrap it | |
if ( !_(options).endsWith('}') ) | |
options = '{'+options+'}' | |
eval('options = '+options) | |
options || options = {} | |
if (!_.isEmpty(options)) | |
if(options.as_defaults?) | |
alteredContext = _.defaults(alteredContext, options, context) | |
else | |
alteredContext = _.extend(alteredContext, context, options) # deep copy | |
if(!partials || !partials[name]?) | |
alert("unknown_partial '" + name + "'") | |
return "" | |
# should I use the altered context or the original? | |
ctx = if _.isEmpty(alteredContext) then context else alteredContext | |
# Apply Defaults to the context, if specified | |
# entangling ICH here! bad practice | |
# D4: HACK | |
ctxDefaults = ich?.contextDefaults[name] || false | |
if ctxDefaults | |
ctx = _.defaults ctx, ctxDefaults | |
# D4 separated rendered values so an altered context wouldn't effect the context for renders at the same sibling level | |
if( typeof(this.valueFromContext( name, context )) isnt "object") | |
renderredValue = this.render(partials[name], ctx, partials, true) | |
else | |
renderredValue = this.render(partials[name], this.valueFromContext( ctx ), partials, true) | |
return renderredValue | |
# Renders inverted (^) and normal (#) sections | |
render_section: (template, context, partials) -> | |
if (!this.includes("#", template) && !this.includes("^", template)) | |
return template | |
that = this | |
# CSW - Added "+?" so it finds the tighest bound, not the widest | |
regex = new RegExp(this.otag + "(\\^|\\#)\\s*(.*)\\s*" + this.ctag + "\n*([\\s\\S]+?)" + this.otag + "\\/\\s*\\2\\s*" + this.ctag + "\\s*", "mg") | |
# for each {{#foo}}{{/foo}} section do... | |
return template.replace regex, (match, type, name, content) -> | |
value = that.find(name, context) | |
# inverted section | |
if(type == "^") | |
if(!value || that.is_array(value) && value.length is 0) | |
# false or empty list, render it | |
return that.render(content, context, partials, true) | |
else | |
return "" | |
# normal section | |
else if(type is "#") | |
# Enumerable, Let's loop! | |
if(that.is_array(value)) | |
return `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! | |
return that.render(content, that.create_context(value), partials, true) | |
else if(typeof value is "function") | |
# higher order section | |
return value.call context, content, (text) -> | |
return that.render(text, context, partials, true) | |
else if(value) # boolean section | |
return that.render(content, context, partials, true) | |
else | |
return "" | |
# Replace {{foo}} and friends with values from our view | |
render_tags:(template, context, partials, in_recursion) -> | |
# tit for tat | |
that = this | |
new_regex = () -> | |
return new RegExp(that.otag + "(=|!|>|\\{|%)?([^\\/#\\^]+?)\\1?" + that.ctag + "+", "g") | |
regex = new_regex() | |
tag_replace_callback = (match, operator, name) -> | |
switch(operator) | |
# ignore comments | |
when "!" then return "" | |
# set new delimiters, rebuild the replace regexp | |
when "=" | |
that.set_delimiters(name) | |
regex = new_regex() | |
return "" | |
# render partial | |
when ">" | |
return that.render_partial(name, context, partials) | |
# the triple mustache is unescaped | |
when "{" | |
return that.find(name, context); | |
# escape the value | |
else | |
return that.escape(that.find(name, context)) | |
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: (delimiters) -> | |
dels = delimiters.split(" ") | |
this.otag = this.escape_regex(dels[0]) | |
this.ctag = this.escape_regex(dels[1]) | |
escape_regex: (text) -> | |
# thank you Simon Willison | |
if(!arguments.callee.sRE) | |
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: (name, context) -> | |
name = this.trim(name) | |
localContext = context | |
if (name is "") | |
return context | |
# Checks whether a value is thruthy or false or 0 | |
is_kinda_truthy = (bool) -> | |
return bool is false || bool is 0 || bool | |
if( is_kinda_truthy(this.valueFromContext( name, context )) ) | |
value = this.valueFromContext( name, context ) | |
else | |
globalContext = this.context | |
if(is_kinda_truthy(this.valueFromContext( name, globalContext ))) | |
value = this.valueFromContext( name, globalContext ) | |
if(typeof value is "function") | |
return value.apply(context) | |
if(value isnt undefined) | |
return value | |
# else silently ignore unkown variables | |
return "" | |
# Utility methods | |
# includes tag | |
includes: (needle, haystack) -> | |
return haystack.indexOf(this.otag + needle) isnt -1 | |
# Does away with nasty characters | |
escape: (s) -> | |
s = String( if s is null then "" else s) | |
return s.replace /&(?!\w+;)|["<>\\]/g, (s) -> | |
switch(s) | |
when "&" | |
return "&" | |
when "\\" | |
return "\\\\" | |
when '"' | |
return '\"' | |
when "<" | |
return "<" | |
when ">" | |
return ">" | |
else | |
return s | |
# by @langalex, support for arrays of strings | |
create_context: (_context) -> | |
if(this.is_object(_context)) | |
return _context | |
else | |
iterator = "." | |
if(this.pragmas["IMPLICIT-ITERATOR"]) | |
iterator = this.pragmas["IMPLICIT-ITERATOR"].iterator | |
ctx = {} | |
ctx[iterator] = _context | |
return ctx | |
is_object: (a) -> | |
return a && typeof a is "object" | |
is_array: (a) -> | |
return Object.prototype.toString.call(a) is '[object Array]' | |
# Gets rid of leading and trailing whitespace | |
trim: (s) -> | |
return s.replace(/^\s*|\s*$/g, "") | |
# Why, why, why? Because IE. Cry, cry cry. | |
map: (array, fn) -> | |
if (typeof array.map is "function") | |
return array.map(fn) | |
else | |
r = [] | |
l = array.length | |
` | |
for(var i = 0; i < l; i++) { | |
r.push(fn(array[i])); | |
} | |
` | |
return r | |
root.Mustache = | |
name: "mustache.js" | |
version: "0.3.0" | |
renderer: new MustacheRenderer() | |
# Turns a template and view into HTML | |
to_html: (template, view, partials, send_fun) -> | |
renderer = new MustacheRenderer() # @renderer # D4 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! | |
# shouldn't this not initted each time! | |
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 | |
class Mustache.ICanHaz | |
VERSION: "0.9" | |
templates: {} | |
partials: {} | |
contextDefaults: {} | |
# 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. | |
addTemplate: (name, templateString) -> | |
self = this | |
if (self[name]) | |
throw "Invalid name: " + name + "." | |
if (self.templates[name]) | |
throw "Template \" + name + \" exists" | |
self.templates[name] = templateString | |
self[name] = (data, raw) -> | |
data = data || {} | |
# apply defaults if they exist, this code is also in the render_partials function | |
# D4 Hack!!!!! | |
if self.contextDefaults[name] | |
data = _.defaults data, self.contextDefaults[name] | |
result = Mustache.to_html(self.templates[name], data, self.partials) | |
# if not raw | |
if(!raw) #pretty much always raw! | |
$result = $(result) | |
hasOnRenderFlags = _.includes(result, 'onrender=') | |
# resolve onrenders | |
# before returning jquery object! | |
# D4 - NEEDS FIXING FOR NODE HACK!!!!!!!!!!!!!!!!!!!! | |
if(hasOnRenderFlags) | |
$onrenders = $result.find("[onrender]") | |
if( $result['is']("[onrender]") ) # D4 HACK !!!!! scared of coffeescirpt compiler | |
$onrenders = $onrenders.add($result) | |
# alert($onrenders.attr('onrender')) | |
$onrenders.each (i, el) -> | |
eval( $(el).attr('onrender') ) | |
# return jquery object | |
return $result | |
else | |
# return raw string | |
return result | |
# public function for adding partials | |
addPartial: (name, templateString, defaults) -> | |
self = this | |
if (self.partials[name]) | |
throw "Partial \" + name + \" exists" | |
else | |
self.partials[name] = templateString | |
# D4, addding defaults to partials! | |
defaults || defaults = false | |
if defaults and !_.isEmpty(defaults) | |
self.contextDefaults[name] = defaults | |
# all partials are also templates! | |
# D4 Hack!!!! | |
# this duplicates the template string. maybe have a single template dictionary instead of partials and templates! | |
self.addTemplate(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 | |
grabTemplates: () -> | |
self = this | |
$('script[type="text/html"]').each (a, b) -> | |
selector = if (typeof a is 'number') then b else a | |
script = $(selector) # Zepto doesn't bind this | |
text = if (''.trim) then script.html().trim() else $.trim(script.html()) | |
selfAttribute = if script.hasClass('partial') then 'addPartial' else 'addTemplate' | |
self[selfAttribute](script.attr('id'), text) | |
script.remove() | |
# clears all retrieval functions and empties caches | |
clearAll: () -> | |
self = this | |
_.each self.templates, (key) -> | |
delete self[key] | |
@templates = {} | |
@partials = {} | |
refresh: () -> | |
@clearAll() | |
@grabTemplates() | |
root.ich = new Mustache.ICanHaz() | |
ich.grabTemplates() | |
# Global Variables | |
# ==================== | |
# Global variable scoping, setup for use with either CommonJs or Node.js! | |
# Coffeescript limits global variable polluting, so if one is needed, just use root.(variabile name) | |
# - [stackoverflow](http://stackoverflow.com/questions/4214731/coffeescript-global-variables) | |
# | |
# Todo: | |
# ------------ | |
# - Use JS namespace instead of root? | |
root = exports ? this | |
# 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