Fix hogan js templates in IE8

Fixes: bug #1110226

Incorrect usage of the templating framework
as the html should be provided and not text? Weird
that it worked in other browsers though.

As part of the fix hogan has also been updated to
the latest official version 2.0.0 which may prevent
other bugs.

Upstream bug has been filed here:
https://github.com/twitter/hogan.js/issues/130

Change-Id: Id5b6cef85dab1f9a497a525f9c065b410c52b38f
This commit is contained in:
Sam Stoelinga 2013-01-31 13:19:37 +08:00
parent 3392d492cd
commit a6832ce678
4 changed files with 578 additions and 7 deletions

View File

@ -7,7 +7,7 @@ horizon.templates = {
/* Pre-loads and compiles the client-side templates. */
horizon.templates.compile_templates = function () {
$.each(horizon.templates.template_ids, function (ind, template_id) {
horizon.templates.compiled_templates[template_id] = Hogan.compile($(template_id).text());
horizon.templates.compiled_templates[template_id] = Hogan.compile($(template_id).html());
});
};

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,576 @@
/*
* Copyright 2011 Twitter, Inc.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var Hogan = {};
(function (Hogan, useArrayBuffer) {
Hogan.Template = function (renderFunc, text, compiler, options) {
this.r = renderFunc || this.r;
this.c = compiler;
this.options = options;
this.text = text || '';
this.buf = (useArrayBuffer) ? [] : '';
}
Hogan.Template.prototype = {
// render: replaced by generated code.
r: function (context, partials, indent) { return ''; },
// variable escaping
v: hoganEscape,
// triple stache
t: coerceToString,
render: function render(context, partials, indent) {
return this.ri([context], partials || {}, indent);
},
// render internal -- a hook for overrides that catches partials too
ri: function (context, partials, indent) {
return this.r(context, partials, indent);
},
// tries to find a partial in the curent scope and render it
rp: function(name, context, partials, indent) {
var partial = partials[name];
if (!partial) {
return '';
}
if (this.c && typeof partial == 'string') {
partial = this.c.compile(partial, this.options);
}
return partial.ri(context, partials, indent);
},
// render a section
rs: function(context, partials, section) {
var tail = context[context.length - 1];
if (!isArray(tail)) {
section(context, partials, this);
return;
}
for (var i = 0; i < tail.length; i++) {
context.push(tail[i]);
section(context, partials, this);
context.pop();
}
},
// maybe start a section
s: function(val, ctx, partials, inverted, start, end, tags) {
var pass;
if (isArray(val) && val.length === 0) {
return false;
}
if (typeof val == 'function') {
val = this.ls(val, ctx, partials, inverted, start, end, tags);
}
pass = (val === '') || !!val;
if (!inverted && pass && ctx) {
ctx.push((typeof val == 'object') ? val : ctx[ctx.length - 1]);
}
return pass;
},
// find values with dotted names
d: function(key, ctx, partials, returnFound) {
var names = key.split('.'),
val = this.f(names[0], ctx, partials, returnFound),
cx = null;
if (key === '.' && isArray(ctx[ctx.length - 2])) {
return ctx[ctx.length - 1];
}
for (var i = 1; i < names.length; i++) {
if (val && typeof val == 'object' && names[i] in val) {
cx = val;
val = val[names[i]];
} else {
val = '';
}
}
if (returnFound && !val) {
return false;
}
if (!returnFound && typeof val == 'function') {
ctx.push(cx);
val = this.lv(val, ctx, partials);
ctx.pop();
}
return val;
},
// find values with normal names
f: function(key, ctx, partials, returnFound) {
var val = false,
v = null,
found = false;
for (var i = ctx.length - 1; i >= 0; i--) {
v = ctx[i];
if (v && typeof v == 'object' && key in v) {
val = v[key];
found = true;
break;
}
}
if (!found) {
return (returnFound) ? false : "";
}
if (!returnFound && typeof val == 'function') {
val = this.lv(val, ctx, partials);
}
return val;
},
// higher order templates
ho: function(val, cx, partials, text, tags) {
var compiler = this.c;
var options = this.options;
options.delimiters = tags;
var text = val.call(cx, text);
text = (text == null) ? String(text) : text.toString();
this.b(compiler.compile(text, options).render(cx, partials));
return false;
},
// template result buffering
b: (useArrayBuffer) ? function(s) { this.buf.push(s); } :
function(s) { this.buf += s; },
fl: (useArrayBuffer) ? function() { var r = this.buf.join(''); this.buf = []; return r; } :
function() { var r = this.buf; this.buf = ''; return r; },
// lambda replace section
ls: function(val, ctx, partials, inverted, start, end, tags) {
var cx = ctx[ctx.length - 1],
t = null;
if (!inverted && this.c && val.length > 0) {
return this.ho(val, cx, partials, this.text.substring(start, end), tags);
}
t = val.call(cx);
if (typeof t == 'function') {
if (inverted) {
return true;
} else if (this.c) {
return this.ho(t, cx, partials, this.text.substring(start, end), tags);
}
}
return t;
},
// lambda replace variable
lv: function(val, ctx, partials) {
var cx = ctx[ctx.length - 1];
var result = val.call(cx);
if (typeof result == 'function') {
result = coerceToString(result.call(cx));
if (this.c && ~result.indexOf("{\u007B")) {
return this.c.compile(result, this.options).render(cx, partials);
}
}
return coerceToString(result);
}
};
var rAmp = /&/g,
rLt = /</g,
rGt = />/g,
rApos =/\'/g,
rQuot = /\"/g,
hChars =/[&<>\"\']/;
function coerceToString(val) {
return String((val === null || val === undefined) ? '' : val);
}
function hoganEscape(str) {
str = coerceToString(str);
return hChars.test(str) ?
str
.replace(rAmp,'&amp;')
.replace(rLt,'&lt;')
.replace(rGt,'&gt;')
.replace(rApos,'&#39;')
.replace(rQuot, '&quot;') :
str;
}
var isArray = Array.isArray || function(a) {
return Object.prototype.toString.call(a) === '[object Array]';
};
})(typeof exports !== 'undefined' ? exports : Hogan);
(function (Hogan) {
// Setup regex assignments
// remove whitespace according to Mustache spec
var rIsWhitespace = /\S/,
rQuot = /\"/g,
rNewline = /\n/g,
rCr = /\r/g,
rSlash = /\\/g,
tagTypes = {
'#': 1, '^': 2, '/': 3, '!': 4, '>': 5,
'<': 6, '=': 7, '_v': 8, '{': 9, '&': 10
};
Hogan.scan = function scan(text, delimiters) {
var len = text.length,
IN_TEXT = 0,
IN_TAG_TYPE = 1,
IN_TAG = 2,
state = IN_TEXT,
tagType = null,
tag = null,
buf = '',
tokens = [],
seenTag = false,
i = 0,
lineStart = 0,
otag = '{{',
ctag = '}}';
function addBuf() {
if (buf.length > 0) {
tokens.push(new String(buf));
buf = '';
}
}
function lineIsWhitespace() {
var isAllWhitespace = true;
for (var j = lineStart; j < tokens.length; j++) {
isAllWhitespace =
(tokens[j].tag && tagTypes[tokens[j].tag] < tagTypes['_v']) ||
(!tokens[j].tag && tokens[j].match(rIsWhitespace) === null);
if (!isAllWhitespace) {
return false;
}
}
return isAllWhitespace;
}
function filterLine(haveSeenTag, noNewLine) {
addBuf();
if (haveSeenTag && lineIsWhitespace()) {
for (var j = lineStart, next; j < tokens.length; j++) {
if (!tokens[j].tag) {
if ((next = tokens[j+1]) && next.tag == '>') {
// set indent to token value
next.indent = tokens[j].toString()
}
tokens.splice(j, 1);
}
}
} else if (!noNewLine) {
tokens.push({tag:'\n'});
}
seenTag = false;
lineStart = tokens.length;
}
function changeDelimiters(text, index) {
var close = '=' + ctag,
closeIndex = text.indexOf(close, index),
delimiters = trim(
text.substring(text.indexOf('=', index) + 1, closeIndex)
).split(' ');
otag = delimiters[0];
ctag = delimiters[1];
return closeIndex + close.length - 1;
}
if (delimiters) {
delimiters = delimiters.split(' ');
otag = delimiters[0];
ctag = delimiters[1];
}
for (i = 0; i < len; i++) {
if (state == IN_TEXT) {
if (tagChange(otag, text, i)) {
--i;
addBuf();
state = IN_TAG_TYPE;
} else {
if (text.charAt(i) == '\n') {
filterLine(seenTag);
} else {
buf += text.charAt(i);
}
}
} else if (state == IN_TAG_TYPE) {
i += otag.length - 1;
tag = tagTypes[text.charAt(i + 1)];
tagType = tag ? text.charAt(i + 1) : '_v';
if (tagType == '=') {
i = changeDelimiters(text, i);
state = IN_TEXT;
} else {
if (tag) {
i++;
}
state = IN_TAG;
}
seenTag = i;
} else {
if (tagChange(ctag, text, i)) {
tokens.push({tag: tagType, n: trim(buf), otag: otag, ctag: ctag,
i: (tagType == '/') ? seenTag - ctag.length : i + otag.length});
buf = '';
i += ctag.length - 1;
state = IN_TEXT;
if (tagType == '{') {
if (ctag == '}}') {
i++;
} else {
cleanTripleStache(tokens[tokens.length - 1]);
}
}
} else {
buf += text.charAt(i);
}
}
}
filterLine(seenTag, true);
return tokens;
}
function cleanTripleStache(token) {
if (token.n.substr(token.n.length - 1) === '}') {
token.n = token.n.substring(0, token.n.length - 1);
}
}
function trim(s) {
if (s.trim) {
return s.trim();
}
return s.replace(/^\s*|\s*$/g, '');
}
function tagChange(tag, text, index) {
if (text.charAt(index) != tag.charAt(0)) {
return false;
}
for (var i = 1, l = tag.length; i < l; i++) {
if (text.charAt(index + i) != tag.charAt(i)) {
return false;
}
}
return true;
}
function buildTree(tokens, kind, stack, customTags) {
var instructions = [],
opener = null,
token = null;
while (tokens.length > 0) {
token = tokens.shift();
if (token.tag == '#' || token.tag == '^' || isOpener(token, customTags)) {
stack.push(token);
token.nodes = buildTree(tokens, token.tag, stack, customTags);
instructions.push(token);
} else if (token.tag == '/') {
if (stack.length === 0) {
throw new Error('Closing tag without opener: /' + token.n);
}
opener = stack.pop();
if (token.n != opener.n && !isCloser(token.n, opener.n, customTags)) {
throw new Error('Nesting error: ' + opener.n + ' vs. ' + token.n);
}
opener.end = token.i;
return instructions;
} else {
instructions.push(token);
}
}
if (stack.length > 0) {
throw new Error('missing closing tag: ' + stack.pop().n);
}
return instructions;
}
function isOpener(token, tags) {
for (var i = 0, l = tags.length; i < l; i++) {
if (tags[i].o == token.n) {
token.tag = '#';
return true;
}
}
}
function isCloser(close, open, tags) {
for (var i = 0, l = tags.length; i < l; i++) {
if (tags[i].c == close && tags[i].o == open) {
return true;
}
}
}
Hogan.generate = function (tree, text, options) {
var code = 'var _=this;_.b(i=i||"");' + walk(tree) + 'return _.fl();';
if (options.asString) {
return 'function(c,p,i){' + code + ';}';
}
return new Hogan.Template(new Function('c', 'p', 'i', code), text, Hogan, options);
}
function esc(s) {
return s.replace(rSlash, '\\\\')
.replace(rQuot, '\\\"')
.replace(rNewline, '\\n')
.replace(rCr, '\\r');
}
function chooseMethod(s) {
return (~s.indexOf('.')) ? 'd' : 'f';
}
function walk(tree) {
var code = '';
for (var i = 0, l = tree.length; i < l; i++) {
var tag = tree[i].tag;
if (tag == '#') {
code += section(tree[i].nodes, tree[i].n, chooseMethod(tree[i].n),
tree[i].i, tree[i].end, tree[i].otag + " " + tree[i].ctag);
} else if (tag == '^') {
code += invertedSection(tree[i].nodes, tree[i].n,
chooseMethod(tree[i].n));
} else if (tag == '<' || tag == '>') {
code += partial(tree[i]);
} else if (tag == '{' || tag == '&') {
code += tripleStache(tree[i].n, chooseMethod(tree[i].n));
} else if (tag == '\n') {
code += text('"\\n"' + (tree.length-1 == i ? '' : ' + i'));
} else if (tag == '_v') {
code += variable(tree[i].n, chooseMethod(tree[i].n));
} else if (tag === undefined) {
code += text('"' + esc(tree[i]) + '"');
}
}
return code;
}
function section(nodes, id, method, start, end, tags) {
return 'if(_.s(_.' + method + '("' + esc(id) + '",c,p,1),' +
'c,p,0,' + start + ',' + end + ',"' + tags + '")){' +
'_.rs(c,p,' +
'function(c,p,_){' +
walk(nodes) +
'});c.pop();}';
}
function invertedSection(nodes, id, method) {
return 'if(!_.s(_.' + method + '("' + esc(id) + '",c,p,1),c,p,1,0,0,"")){' +
walk(nodes) +
'};';
}
function partial(tok) {
return '_.b(_.rp("' + esc(tok.n) + '",c,p,"' + (tok.indent || '') + '"));';
}
function tripleStache(id, method) {
return '_.b(_.t(_.' + method + '("' + esc(id) + '",c,p,0)));';
}
function variable(id, method) {
return '_.b(_.v(_.' + method + '("' + esc(id) + '",c,p,0)));';
}
function text(id) {
return '_.b(' + id + ');';
}
Hogan.parse = function(tokens, text, options) {
options = options || {};
return buildTree(tokens, '', [], options.sectionTags || []);
},
Hogan.cache = {};
Hogan.compile = function(text, options) {
// options
//
// asString: false (default)
//
// sectionTags: [{o: '_foo', c: 'foo'}]
// An array of object with o and c fields that indicate names for custom
// section tags. The example above allows parsing of {{_foo}}{{/foo}}.
//
// delimiters: A string that overrides the default delimiters.
// Example: "<% %>"
//
options = options || {};
var key = text + '||' + !!options.asString;
var t = this.cache[key];
if (t) {
return t;
}
t = this.generate(this.parse(this.scan(text, options.delimiters), text, options), text, options);
return this.cache[key] = t;
};
})(typeof exports !== 'undefined' ? exports : Hogan);

View File

@ -17,7 +17,7 @@
<script src="{{ STATIC_URL }}bootstrap/js/bootstrap.min.js" type="text/javascript" charset="utf-8"></script>
<script src="{{ STATIC_URL }}horizon/lib/hogan-1.0.5.min.js" type="text/javascript" charset='utf-8'></script>
<script src="{{ STATIC_URL }}horizon/lib/hogan-2.0.0.js" type="text/javascript" charset='utf-8'></script>
<script src='{{ STATIC_URL }}horizon/js/horizon.communication.js' type='text/javascript' charset='utf-8'></script>
<script src='{{ STATIC_URL }}horizon/js/horizon.cookies.js' type='text/javascript' charset='utf-8'></script>