Skip to content

Instantly share code, notes, and snippets.

@d4tocchini
Created August 6, 2011 05:34
Show Gist options
  • Save d4tocchini/1129047 to your computer and use it in GitHub Desktop.
Save d4tocchini/1129047 to your computer and use it in GitHub Desktop.
$ = 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 "&amp;"
when "\\"
return "\\\\"
when '"'
return '\"'
when "<"
return "&lt;"
when ">"
return "&gt;"
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 &nbsp;
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