[#79,refactor][s]: switch from (old) jquery.mustache to latest mustache.js - fixes #79.
* This mustache has support for nested values e.g. {{sub.x.y}}
* Specifically mustache.js as of Fri Feb 24 09:58:31 2012 +0100 2f135e2e15dcc3c61385212261faaf00597bae10
This commit is contained in:
parent
bc8e47c6cf
commit
c6d4116cba
@ -35,7 +35,7 @@
|
||||
<script type="text/javascript" src="../vendor/underscore/1.1.6/underscore.js"></script>
|
||||
<script type="text/javascript" src="../vendor/backbone/0.5.1/backbone.js"></script>
|
||||
<script type="text/javascript" src="../vendor/jquery.flot/0.7/jquery.flot.js"></script>
|
||||
<script type="text/javascript" src="../vendor/jquery.mustache.js"></script>
|
||||
<script type="text/javascript" src="../vendor/mustache/0.5.0-dev/mustache.js"></script>
|
||||
<script type="text/javascript" src="../vendor/bootstrap/2.0.2/bootstrap.js"></script>
|
||||
<script type="text/javascript" src="../vendor/leaflet/0.3.1/leaflet.js"></script>
|
||||
<script type="text/javascript" src="../vendor/timeline/20120520/js/timeline.js"></script>
|
||||
|
||||
@ -102,7 +102,7 @@ var ExplorerApp = Backbone.View.extend({
|
||||
function makeEmbedLink(state) {
|
||||
var link = self.makePermaLink(state);
|
||||
link = link + '&embed=true';
|
||||
var out = $.mustache('<iframe src="{{link}}" width="100%" min-height="500px;"></iframe>', {link: link});
|
||||
var out = Mustache.render('<iframe src="{{link}}" width="100%" min-height="500px;"></iframe>', {link: link});
|
||||
return out;
|
||||
}
|
||||
explorer.state.bind('change', function() {
|
||||
|
||||
@ -120,7 +120,7 @@ my.Graph = Backbone.View.extend({
|
||||
render: function() {
|
||||
var self = this;
|
||||
var tmplData = this.model.toTemplateJSON();
|
||||
var htmls = $.mustache(this.template, tmplData);
|
||||
var htmls = Mustache.render(this.template, tmplData);
|
||||
$(this.el).html(htmls);
|
||||
this.$graph = this.el.find('.panel.graph');
|
||||
|
||||
@ -375,7 +375,7 @@ my.Graph = Backbone.View.extend({
|
||||
seriesName: String.fromCharCode(idx + 64 + 1),
|
||||
}, this.model.toTemplateJSON());
|
||||
|
||||
var htmls = $.mustache(this.templateSeriesEditor, data);
|
||||
var htmls = Mustache.render(this.templateSeriesEditor, data);
|
||||
this.el.find('.editor-series-group').append(htmls);
|
||||
return this;
|
||||
},
|
||||
|
||||
@ -53,7 +53,7 @@ my.Grid = Backbone.View.extend({
|
||||
{{#columns}} \
|
||||
<li><a data-action="showColumn" data-column="{{.}}" href="JavaScript:void(0);">Show column: {{.}}</a></li> \
|
||||
{{/columns}}';
|
||||
var tmp = $.mustache(tmpl, {'columns': this.state.get('hiddenFields')});
|
||||
var tmp = Mustache.render(tmpl, {'columns': this.state.get('hiddenFields')});
|
||||
this.el.find('.root-header-menu .dropdown-menu').html(tmp);
|
||||
},
|
||||
|
||||
@ -211,7 +211,7 @@ my.Grid = Backbone.View.extend({
|
||||
field.set({width: width});
|
||||
}
|
||||
});
|
||||
var htmls = $.mustache(this.template, this.toTemplateJSON());
|
||||
var htmls = Mustache.render(this.template, this.toTemplateJSON());
|
||||
this.el.html(htmls);
|
||||
this.model.currentDocuments.forEach(function(doc) {
|
||||
var tr = $('<tr />');
|
||||
@ -308,7 +308,7 @@ my.GridRow = Backbone.View.extend({
|
||||
|
||||
render: function() {
|
||||
this.el.attr('data-id', this.model.id);
|
||||
var html = $.mustache(this.template, this.toTemplateJSON());
|
||||
var html = Mustache.render(this.template, this.toTemplateJSON());
|
||||
$(this.el).html(html);
|
||||
return this;
|
||||
},
|
||||
@ -336,7 +336,7 @@ my.GridRow = Backbone.View.extend({
|
||||
$(e.target).addClass("hidden");
|
||||
var cell = $(e.target).siblings('.data-table-cell-value');
|
||||
cell.data("previousContents", cell.text());
|
||||
var templated = $.mustache(this.cellEditorTemplate, {value: cell.text()});
|
||||
var templated = Mustache.render(this.cellEditorTemplate, {value: cell.text()});
|
||||
cell.html(templated);
|
||||
},
|
||||
|
||||
|
||||
@ -161,7 +161,7 @@ my.Map = Backbone.View.extend({
|
||||
|
||||
var self = this;
|
||||
|
||||
htmls = $.mustache(this.template, this.model.toTemplateJSON());
|
||||
htmls = Mustache.render(this.template, this.model.toTemplateJSON());
|
||||
|
||||
$(this.el).html(htmls);
|
||||
this.$map = this.el.find('.panel.map');
|
||||
|
||||
@ -47,7 +47,7 @@ my.Timeline = Backbone.View.extend({
|
||||
|
||||
render: function() {
|
||||
var tmplData = {};
|
||||
var htmls = $.mustache(this.template, tmplData);
|
||||
var htmls = Mustache.render(this.template, tmplData);
|
||||
this.el.html(htmls);
|
||||
},
|
||||
|
||||
|
||||
@ -76,7 +76,7 @@ my.ColumnTransform = Backbone.View.extend({
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var htmls = $.mustache(this.template,
|
||||
var htmls = Mustache.render(this.template,
|
||||
{name: this.state.currentColumn}
|
||||
);
|
||||
this.el.html(htmls);
|
||||
@ -163,7 +163,7 @@ my.ColumnTransform = Backbone.View.extend({
|
||||
});
|
||||
var previewData = costco.previewTransform(docs, editFunc, self.state.currentColumn);
|
||||
var $el = self.el.find('.expression-preview-container');
|
||||
var templated = $.mustache(self.editPreviewTemplate, {rows: previewData.slice(0,4)});
|
||||
var templated = Mustache.render(self.editPreviewTemplate, {rows: previewData.slice(0,4)});
|
||||
$el.html(templated);
|
||||
} else {
|
||||
errors.text(editFunc.errorMessage);
|
||||
|
||||
10
src/view.js
10
src/view.js
@ -286,7 +286,7 @@ my.DataExplorer = Backbone.View.extend({
|
||||
render: function() {
|
||||
var tmplData = this.model.toTemplateJSON();
|
||||
tmplData.views = this.pageViews;
|
||||
var template = $.mustache(this.template, tmplData);
|
||||
var template = Mustache.render(this.template, tmplData);
|
||||
$(this.el).html(template);
|
||||
var $dataViewContainer = this.el.find('.data-view-container');
|
||||
_.each(this.pageViews, function(view, pageName) {
|
||||
@ -431,7 +431,7 @@ my.DataExplorer = Backbone.View.extend({
|
||||
{{message}} \
|
||||
</div>';
|
||||
}
|
||||
var _templated = $($.mustache(_template, tmplData));
|
||||
var _templated = $(Mustache.render(_template, tmplData));
|
||||
_templated = $(_templated).appendTo($('.recline-data-explorer .alert-messages'));
|
||||
if (!flash.persist) {
|
||||
setTimeout(function() {
|
||||
@ -516,7 +516,7 @@ my.QueryEditor = Backbone.View.extend({
|
||||
render: function() {
|
||||
var tmplData = this.model.toJSON();
|
||||
tmplData.to = this.model.get('from') + this.model.get('size');
|
||||
var templated = $.mustache(this.template, tmplData);
|
||||
var templated = Mustache.render(this.template, tmplData);
|
||||
this.el.html(templated);
|
||||
}
|
||||
});
|
||||
@ -584,7 +584,7 @@ my.FilterEditor = Backbone.View.extend({
|
||||
value: filter.term[fieldId]
|
||||
};
|
||||
});
|
||||
var out = $.mustache(this.template, tmplData);
|
||||
var out = Mustache.render(this.template, tmplData);
|
||||
this.el.html(out);
|
||||
// are there actually any facets to show?
|
||||
if (this.model.get('filters').length > 0) {
|
||||
@ -669,7 +669,7 @@ my.FacetViewer = Backbone.View.extend({
|
||||
}
|
||||
return facet;
|
||||
});
|
||||
var templated = $.mustache(this.template, tmplData);
|
||||
var templated = Mustache.render(this.template, tmplData);
|
||||
this.el.html(templated);
|
||||
// are there actually any facets to show?
|
||||
if (this.model.facets.length > 0) {
|
||||
|
||||
@ -12,7 +12,7 @@
|
||||
<script type="text/javascript" src="../vendor/underscore/1.1.6/underscore.js"></script>
|
||||
<script type="text/javascript" src="../vendor/backbone/0.5.1/backbone.js"></script>
|
||||
<script type="text/javascript" src="../vendor/jquery.flot/0.7/jquery.flot.js"></script>
|
||||
<script type="text/javascript" src="../vendor/jquery.mustache.js"></script>
|
||||
<script type="text/javascript" src="../vendor/mustache/0.5.0-dev/mustache.js"></script>
|
||||
<script type="text/javascript" src="../vendor/bootstrap/2.0.2/bootstrap.js"></script>
|
||||
<script type="text/javascript" src="../vendor/leaflet/0.3.1/leaflet.js"></script>
|
||||
<script type="text/javascript" src="../vendor/timeline/20120520/js/timeline.js"></script>
|
||||
|
||||
346
vendor/jquery.mustache.js
vendored
346
vendor/jquery.mustache.js
vendored
@ -1,346 +0,0 @@
|
||||
/*
|
||||
Shameless port of a shameless port
|
||||
@defunkt => @janl => @aq
|
||||
|
||||
See http://github.com/defunkt/mustache for more info.
|
||||
*/
|
||||
|
||||
;(function($) {
|
||||
|
||||
/*
|
||||
mustache.js — Logic-less templates in JavaScript
|
||||
|
||||
See http://mustache.github.com/ for more info.
|
||||
*/
|
||||
|
||||
var Mustache = function() {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
template = this.render_pragmas(template);
|
||||
var 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: function(line) {
|
||||
if(line != "") {
|
||||
this.buffer.push(line);
|
||||
}
|
||||
},
|
||||
|
||||
/*
|
||||
Looks for %PRAGMAS
|
||||
*/
|
||||
render_pragmas: function(template) {
|
||||
// no pragmas
|
||||
if(!this.includes("%", template)) {
|
||||
return template;
|
||||
}
|
||||
|
||||
var that = this;
|
||||
var regex = new RegExp(this.otag + "%([\\w-]+) ?([\\w]+=[\\w]+)?" +
|
||||
this.ctag);
|
||||
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)) {
|
||||
return template;
|
||||
}
|
||||
|
||||
var that = this;
|
||||
// CSW - Added "+?" so it finds the tighest bound, not the widest
|
||||
var 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, function(match, type, name, content) {
|
||||
var value = that.find(name, context);
|
||||
if(type == "^") { // inverted section
|
||||
if(!value || that.is_array(value) && value.length === 0) {
|
||||
// false or empty list, render it
|
||||
return that.render(content, context, partials, true);
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
} else if(type == "#") { // normal section
|
||||
if(that.is_array(value)) { // Enumerable, Let's loop!
|
||||
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 === "function") {
|
||||
// higher order section
|
||||
return value.call(context, content, function(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: function(template, context, partials, in_recursion) {
|
||||
// tit for tat
|
||||
var that = this;
|
||||
|
||||
var new_regex = function() {
|
||||
return new RegExp(that.otag + "(=|!|>|\\{|%)?([^\\/#\\^]+?)\\1?" +
|
||||
that.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;
|
||||
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 "";
|
||||
},
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return({
|
||||
name: "mustache.js",
|
||||
version: "0.3.1-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");
|
||||
}
|
||||
},
|
||||
escape : function(text) {
|
||||
return new Renderer().escape(text);
|
||||
}
|
||||
});
|
||||
}();
|
||||
|
||||
$.mustache = function(template, view, partials) {
|
||||
return Mustache.to_html(template, view, partials);
|
||||
};
|
||||
|
||||
$.mustache.escape = function(text) {
|
||||
return Mustache.escape(text);
|
||||
};
|
||||
|
||||
})(jQuery);
|
||||
536
vendor/mustache/0.5.0-dev/mustache.js
vendored
Normal file
536
vendor/mustache/0.5.0-dev/mustache.js
vendored
Normal file
@ -0,0 +1,536 @@
|
||||
/*!
|
||||
* mustache.js - Logic-less {{mustache}} templates with JavaScript
|
||||
* http://github.com/janl/mustache.js
|
||||
*/
|
||||
var Mustache = (typeof module !== "undefined" && module.exports) || {};
|
||||
|
||||
(function (exports) {
|
||||
|
||||
exports.name = "mustache.js";
|
||||
exports.version = "0.5.0-dev";
|
||||
exports.tags = ["{{", "}}"];
|
||||
exports.parse = parse;
|
||||
exports.compile = compile;
|
||||
exports.render = render;
|
||||
exports.clearCache = clearCache;
|
||||
|
||||
// This is here for backwards compatibility with 0.4.x.
|
||||
exports.to_html = function (template, view, partials, send) {
|
||||
var result = render(template, view, partials);
|
||||
|
||||
if (typeof send === "function") {
|
||||
send(result);
|
||||
} else {
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
var _toString = Object.prototype.toString;
|
||||
var _isArray = Array.isArray;
|
||||
var _forEach = Array.prototype.forEach;
|
||||
var _trim = String.prototype.trim;
|
||||
|
||||
var isArray;
|
||||
if (_isArray) {
|
||||
isArray = _isArray;
|
||||
} else {
|
||||
isArray = function (obj) {
|
||||
return _toString.call(obj) === "[object Array]";
|
||||
};
|
||||
}
|
||||
|
||||
var forEach;
|
||||
if (_forEach) {
|
||||
forEach = function (obj, callback, scope) {
|
||||
return _forEach.call(obj, callback, scope);
|
||||
};
|
||||
} else {
|
||||
forEach = function (obj, callback, scope) {
|
||||
for (var i = 0, len = obj.length; i < len; ++i) {
|
||||
callback.call(scope, obj[i], i, obj);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
var spaceRe = /^\s*$/;
|
||||
|
||||
function isWhitespace(string) {
|
||||
return spaceRe.test(string);
|
||||
}
|
||||
|
||||
var trim;
|
||||
if (_trim) {
|
||||
trim = function (string) {
|
||||
return string == null ? "" : _trim.call(string);
|
||||
};
|
||||
} else {
|
||||
var trimLeft, trimRight;
|
||||
|
||||
if (isWhitespace("\xA0")) {
|
||||
trimLeft = /^\s+/;
|
||||
trimRight = /\s+$/;
|
||||
} else {
|
||||
// IE doesn't match non-breaking spaces with \s, thanks jQuery.
|
||||
trimLeft = /^[\s\xA0]+/;
|
||||
trimRight = /[\s\xA0]+$/;
|
||||
}
|
||||
|
||||
trim = function (string) {
|
||||
return string == null ? "" :
|
||||
String(string).replace(trimLeft, "").replace(trimRight, "");
|
||||
};
|
||||
}
|
||||
|
||||
var escapeMap = {
|
||||
"&": "&",
|
||||
"<": "<",
|
||||
">": ">",
|
||||
'"': '"',
|
||||
"'": '''
|
||||
};
|
||||
|
||||
function escapeHTML(string) {
|
||||
return String(string).replace(/&(?!\w+;)|[<>"']/g, function (s) {
|
||||
return escapeMap[s] || s;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the `template`, `line`, and `file` properties to the given error
|
||||
* object and alters the message to provide more useful debugging information.
|
||||
*/
|
||||
function debug(e, template, line, file) {
|
||||
file = file || "<template>";
|
||||
|
||||
var lines = template.split("\n"),
|
||||
start = Math.max(line - 3, 0),
|
||||
end = Math.min(lines.length, line + 3),
|
||||
context = lines.slice(start, end);
|
||||
|
||||
var c;
|
||||
for (var i = 0, len = context.length; i < len; ++i) {
|
||||
c = i + start + 1;
|
||||
context[i] = (c === line ? " >> " : " ") + context[i];
|
||||
}
|
||||
|
||||
e.template = template;
|
||||
e.line = line;
|
||||
e.file = file;
|
||||
e.message = [file + ":" + line, context.join("\n"), "", e.message].join("\n");
|
||||
|
||||
return e;
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks up the value of the given `name` in the given context `stack`.
|
||||
*/
|
||||
function lookup(name, stack, defaultValue) {
|
||||
if (name === ".") {
|
||||
return stack[stack.length - 1];
|
||||
}
|
||||
|
||||
var names = name.split(".");
|
||||
var lastIndex = names.length - 1;
|
||||
var target = names[lastIndex];
|
||||
|
||||
var value, context, i = stack.length, j, localStack;
|
||||
while (i) {
|
||||
localStack = stack.slice(0);
|
||||
context = stack[--i];
|
||||
|
||||
j = 0;
|
||||
while (j < lastIndex) {
|
||||
context = context[names[j++]];
|
||||
|
||||
if (context == null) {
|
||||
break;
|
||||
}
|
||||
|
||||
localStack.push(context);
|
||||
}
|
||||
|
||||
if (context && typeof context === "object" && target in context) {
|
||||
value = context[target];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If the value is a function, call it in the current context.
|
||||
if (typeof value === "function") {
|
||||
value = value.call(localStack[localStack.length - 1]);
|
||||
}
|
||||
|
||||
if (value == null) {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
function renderSection(name, stack, callback, inverted) {
|
||||
var buffer = "";
|
||||
var value = lookup(name, stack);
|
||||
|
||||
if (inverted) {
|
||||
// From the spec: inverted sections may render text once based on the
|
||||
// inverse value of the key. That is, they will be rendered if the key
|
||||
// doesn't exist, is false, or is an empty list.
|
||||
if (value == null || value === false || (isArray(value) && value.length === 0)) {
|
||||
buffer += callback();
|
||||
}
|
||||
} else if (isArray(value)) {
|
||||
forEach(value, function (value) {
|
||||
stack.push(value);
|
||||
buffer += callback();
|
||||
stack.pop();
|
||||
});
|
||||
} else if (typeof value === "object") {
|
||||
stack.push(value);
|
||||
buffer += callback();
|
||||
stack.pop();
|
||||
} else if (typeof value === "function") {
|
||||
var scope = stack[stack.length - 1];
|
||||
var scopedRender = function (template) {
|
||||
return render(template, scope);
|
||||
};
|
||||
buffer += value.call(scope, callback(), scopedRender) || "";
|
||||
} else if (value) {
|
||||
buffer += callback();
|
||||
}
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the given `template` and returns the source of a function that,
|
||||
* with the proper arguments, will render the template. Recognized options
|
||||
* include the following:
|
||||
*
|
||||
* - file The name of the file the template comes from (displayed in
|
||||
* error messages)
|
||||
* - tags An array of open and close tags the `template` uses. Defaults
|
||||
* to the value of Mustache.tags
|
||||
* - debug Set `true` to log the body of the generated function to the
|
||||
* console
|
||||
* - space Set `true` to preserve whitespace from lines that otherwise
|
||||
* contain only a {{tag}}. Defaults to `false`
|
||||
*/
|
||||
function parse(template, options) {
|
||||
options = options || {};
|
||||
|
||||
var tags = options.tags || exports.tags,
|
||||
openTag = tags[0],
|
||||
closeTag = tags[tags.length - 1];
|
||||
|
||||
var code = [
|
||||
'var buffer = "";', // output buffer
|
||||
"\nvar line = 1;", // keep track of source line number
|
||||
"\ntry {",
|
||||
'\nbuffer += "'
|
||||
];
|
||||
|
||||
var spaces = [], // indices of whitespace in code on the current line
|
||||
hasTag = false, // is there a {{tag}} on the current line?
|
||||
nonSpace = false; // is there a non-space char on the current line?
|
||||
|
||||
// Strips all space characters from the code array for the current line
|
||||
// if there was a {{tag}} on it and otherwise only spaces.
|
||||
var stripSpace = function () {
|
||||
if (hasTag && !nonSpace && !options.space) {
|
||||
while (spaces.length) {
|
||||
code.splice(spaces.pop(), 1);
|
||||
}
|
||||
} else {
|
||||
spaces = [];
|
||||
}
|
||||
|
||||
hasTag = false;
|
||||
nonSpace = false;
|
||||
};
|
||||
|
||||
var sectionStack = [], updateLine, nextOpenTag, nextCloseTag;
|
||||
|
||||
var setTags = function (source) {
|
||||
tags = trim(source).split(/\s+/);
|
||||
nextOpenTag = tags[0];
|
||||
nextCloseTag = tags[tags.length - 1];
|
||||
};
|
||||
|
||||
var includePartial = function (source) {
|
||||
code.push(
|
||||
'";',
|
||||
updateLine,
|
||||
'\nvar partial = partials["' + trim(source) + '"];',
|
||||
'\nif (partial) {',
|
||||
'\n buffer += render(partial,stack[stack.length - 1],partials);',
|
||||
'\n}',
|
||||
'\nbuffer += "'
|
||||
);
|
||||
};
|
||||
|
||||
var openSection = function (source, inverted) {
|
||||
var name = trim(source);
|
||||
|
||||
if (name === "") {
|
||||
throw debug(new Error("Section name may not be empty"), template, line, options.file);
|
||||
}
|
||||
|
||||
sectionStack.push({name: name, inverted: inverted});
|
||||
|
||||
code.push(
|
||||
'";',
|
||||
updateLine,
|
||||
'\nvar name = "' + name + '";',
|
||||
'\nvar callback = (function () {',
|
||||
'\n return function () {',
|
||||
'\n var buffer = "";',
|
||||
'\nbuffer += "'
|
||||
);
|
||||
};
|
||||
|
||||
var openInvertedSection = function (source) {
|
||||
openSection(source, true);
|
||||
};
|
||||
|
||||
var closeSection = function (source) {
|
||||
var name = trim(source);
|
||||
var openName = sectionStack.length != 0 && sectionStack[sectionStack.length - 1].name;
|
||||
|
||||
if (!openName || name != openName) {
|
||||
throw debug(new Error('Section named "' + name + '" was never opened'), template, line, options.file);
|
||||
}
|
||||
|
||||
var section = sectionStack.pop();
|
||||
|
||||
code.push(
|
||||
'";',
|
||||
'\n return buffer;',
|
||||
'\n };',
|
||||
'\n})();'
|
||||
);
|
||||
|
||||
if (section.inverted) {
|
||||
code.push("\nbuffer += renderSection(name,stack,callback,true);");
|
||||
} else {
|
||||
code.push("\nbuffer += renderSection(name,stack,callback);");
|
||||
}
|
||||
|
||||
code.push('\nbuffer += "');
|
||||
};
|
||||
|
||||
var sendPlain = function (source) {
|
||||
code.push(
|
||||
'";',
|
||||
updateLine,
|
||||
'\nbuffer += lookup("' + trim(source) + '",stack,"");',
|
||||
'\nbuffer += "'
|
||||
);
|
||||
};
|
||||
|
||||
var sendEscaped = function (source) {
|
||||
code.push(
|
||||
'";',
|
||||
updateLine,
|
||||
'\nbuffer += escapeHTML(lookup("' + trim(source) + '",stack,""));',
|
||||
'\nbuffer += "'
|
||||
);
|
||||
};
|
||||
|
||||
var line = 1, c, callback;
|
||||
for (var i = 0, len = template.length; i < len; ++i) {
|
||||
if (template.slice(i, i + openTag.length) === openTag) {
|
||||
i += openTag.length;
|
||||
c = template.substr(i, 1);
|
||||
updateLine = '\nline = ' + line + ';';
|
||||
nextOpenTag = openTag;
|
||||
nextCloseTag = closeTag;
|
||||
hasTag = true;
|
||||
|
||||
switch (c) {
|
||||
case "!": // comment
|
||||
i++;
|
||||
callback = null;
|
||||
break;
|
||||
case "=": // change open/close tags, e.g. {{=<% %>=}}
|
||||
i++;
|
||||
closeTag = "=" + closeTag;
|
||||
callback = setTags;
|
||||
break;
|
||||
case ">": // include partial
|
||||
i++;
|
||||
callback = includePartial;
|
||||
break;
|
||||
case "#": // start section
|
||||
i++;
|
||||
callback = openSection;
|
||||
break;
|
||||
case "^": // start inverted section
|
||||
i++;
|
||||
callback = openInvertedSection;
|
||||
break;
|
||||
case "/": // end section
|
||||
i++;
|
||||
callback = closeSection;
|
||||
break;
|
||||
case "{": // plain variable
|
||||
closeTag = "}" + closeTag;
|
||||
// fall through
|
||||
case "&": // plain variable
|
||||
i++;
|
||||
nonSpace = true;
|
||||
callback = sendPlain;
|
||||
break;
|
||||
default: // escaped variable
|
||||
nonSpace = true;
|
||||
callback = sendEscaped;
|
||||
}
|
||||
|
||||
var end = template.indexOf(closeTag, i);
|
||||
|
||||
if (end === -1) {
|
||||
throw debug(new Error('Tag "' + openTag + '" was not closed properly'), template, line, options.file);
|
||||
}
|
||||
|
||||
var source = template.substring(i, end);
|
||||
|
||||
if (callback) {
|
||||
callback(source);
|
||||
}
|
||||
|
||||
// Maintain line count for \n in source.
|
||||
var n = 0;
|
||||
while (~(n = source.indexOf("\n", n))) {
|
||||
line++;
|
||||
n++;
|
||||
}
|
||||
|
||||
i = end + closeTag.length - 1;
|
||||
openTag = nextOpenTag;
|
||||
closeTag = nextCloseTag;
|
||||
} else {
|
||||
c = template.substr(i, 1);
|
||||
|
||||
switch (c) {
|
||||
case '"':
|
||||
case "\\":
|
||||
nonSpace = true;
|
||||
code.push("\\" + c);
|
||||
break;
|
||||
case "\r":
|
||||
// Ignore carriage returns.
|
||||
break;
|
||||
case "\n":
|
||||
spaces.push(code.length);
|
||||
code.push("\\n");
|
||||
stripSpace(); // Check for whitespace on the current line.
|
||||
line++;
|
||||
break;
|
||||
default:
|
||||
if (isWhitespace(c)) {
|
||||
spaces.push(code.length);
|
||||
} else {
|
||||
nonSpace = true;
|
||||
}
|
||||
|
||||
code.push(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (sectionStack.length != 0) {
|
||||
throw debug(new Error('Section "' + sectionStack[sectionStack.length - 1].name + '" was not closed properly'), template, line, options.file);
|
||||
}
|
||||
|
||||
// Clean up any whitespace from a closing {{tag}} that was at the end
|
||||
// of the template without a trailing \n.
|
||||
stripSpace();
|
||||
|
||||
code.push(
|
||||
'";',
|
||||
"\nreturn buffer;",
|
||||
"\n} catch (e) { throw {error: e, line: line}; }"
|
||||
);
|
||||
|
||||
// Ignore `buffer += "";` statements.
|
||||
var body = code.join("").replace(/buffer \+= "";\n/g, "");
|
||||
|
||||
if (options.debug) {
|
||||
if (typeof console != "undefined" && console.log) {
|
||||
console.log(body);
|
||||
} else if (typeof print === "function") {
|
||||
print(body);
|
||||
}
|
||||
}
|
||||
|
||||
return body;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used by `compile` to generate a reusable function for the given `template`.
|
||||
*/
|
||||
function _compile(template, options) {
|
||||
var args = "view,partials,stack,lookup,escapeHTML,renderSection,render";
|
||||
var body = parse(template, options);
|
||||
var fn = new Function(args, body);
|
||||
|
||||
// This anonymous function wraps the generated function so we can do
|
||||
// argument coercion, setup some variables, and handle any errors
|
||||
// encountered while executing it.
|
||||
return function (view, partials) {
|
||||
partials = partials || {};
|
||||
|
||||
var stack = [view]; // context stack
|
||||
|
||||
try {
|
||||
return fn(view, partials, stack, lookup, escapeHTML, renderSection, render);
|
||||
} catch (e) {
|
||||
throw debug(e.error, template, e.line, options.file);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Cache of pre-compiled templates.
|
||||
var _cache = {};
|
||||
|
||||
/**
|
||||
* Clear the cache of compiled templates.
|
||||
*/
|
||||
function clearCache() {
|
||||
_cache = {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Compiles the given `template` into a reusable function using the given
|
||||
* `options`. In addition to the options accepted by Mustache.parse,
|
||||
* recognized options include the following:
|
||||
*
|
||||
* - cache Set `false` to bypass any pre-compiled version of the given
|
||||
* template. Otherwise, a given `template` string will be cached
|
||||
* the first time it is parsed
|
||||
*/
|
||||
function compile(template, options) {
|
||||
options = options || {};
|
||||
|
||||
// Use a pre-compiled version from the cache if we have one.
|
||||
if (options.cache !== false) {
|
||||
if (!_cache[template]) {
|
||||
_cache[template] = _compile(template, options);
|
||||
}
|
||||
|
||||
return _cache[template];
|
||||
}
|
||||
|
||||
return _compile(template, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* High-level function that renders the given `template` using the given
|
||||
* `view` and `partials`. If you need to use any of the template options (see
|
||||
* `compile` above), you must compile in a separate step, and then call that
|
||||
* compiled function.
|
||||
*/
|
||||
function render(template, view, partials) {
|
||||
return compile(template)(view, partials);
|
||||
}
|
||||
|
||||
})(Mustache);
|
||||
Loading…
x
Reference in New Issue
Block a user