Skip to content

Instantly share code, notes, and snippets.

@ryanand26
Created July 30, 2015 08:08
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ryanand26/456d687a02352e5c5118 to your computer and use it in GitHub Desktop.
Save ryanand26/456d687a02352e5c5118 to your computer and use it in GitHub Desktop.
Very basic template renderer
/**
* Take a basic template and return a populated result
* Required until our use of mustache is greenlit
*/
var Renderer = function () {
var startPattern = '{{',
endPattern = '}}',
tagPattern = /\{\{([#,\/])([\w,.]*\}\})/, //matches all opening or closing list tags
instance = this;
instance.content = null;
var tails = [], // track the nested recursions
activeTail = -1;
function getTail() {
return tails[activeTail];
}
function setTail(string) {
tails[activeTail] = string;
}
/**
* Returns parsed HTML given template and content object
* If the template contains a list parser is called recursivly
*/
function parser(template, content) {
var htmlResult = '',
head, match, matchType,
listPattern, listKey, listTemplate, listContent;
tails.push(template);
activeTail += 1;
//work through it finding the matching patterns
while (getTail().length > 0) {
//extract the string preceeding the match and add to result
head = scanUntil(startPattern);
htmlResult += head;
//what is our match?
matchType = getTail().charAt(2);
//if it's a loop
if (matchType === '#') {
//get pattern
match = tagPattern.exec(getTail());
listKey = match[2];
// //get all the template between the list tags
listTemplate = getListTemplate();
listContent = getContentForKey(listKey, content) || [];
//replace listTemplate through recursion and add to result
jQuery.each(listContent, function (index, listItemContent) {
htmlResult += parser(listTemplate, listItemContent);
});
}
//if it's a replace
else if (matchType !== '' && matchType !== '/') {
match = scanUntil(endPattern, true);
//replace match with content, add to result
htmlResult += getContentForKey(match, content);
}
//no further matches
else {
setTail('');
}
}
tails.pop();
activeTail -= 1;
return htmlResult;
}
/**
* Finds the given pattern in the template
* returns all markup before the pattern index
* sets the active tail to the remaining template
* Option: Bool includePattern add the pattern to the head.
*/
function scanUntil(pattern, includePattern) {
//for the given string, find an opening tag
var tail = getTail(),
index = tail.search(pattern),
head;
switch (index) {
case -1:
head = tail;
setTail('');
break;
case 0:
head = '';
break;
default:
if (includePattern) {
index += pattern.length;
}
head = tail.substring(0, index);
setTail(tail.substring(index));
}
return head;
}
/*
* For the current tail, extract the list template at position zero
* Sets tail to be the remaining template
*/
function getListTemplate() {
var tail,
head = '',
removeMatch = true,
isFound = false,
isFirstTag = true,
listKey, match, index,
skipCount = 0;
tail = getTail();
//from the start tag
//find a matching start or end tag
match = tail.match(tagPattern);
listKey = match[2]; //set the intial key
while (match !== null) {
index = match.index;
//if this matches our list
if (match[2] === listKey) {
//if it's a start tag, add a skip
if (match[1] === '#' && isFirstTag === false) {
skipCount += 1;
}
//if it's a end tag
else if (match[1] === '/') {
//if we have remaining skips
if (skipCount > 0) {
skipCount -= 1;
}
//else return this position
else {
isFound = true;
}
}
}
//if found we want to leave this out of the head and tail
if (isFound || isFirstTag) {
head += tail.substring(0, index);
tail = tail.substring(index + match[0].length);
isFirstTag = false;
}
//otherwise we've skipped it and it should go in the head
else {
//extract the string up to position
index = index + match[0].length;
head += tail.substring(0, index);
tail = tail.substring(index);
}
if (isFound) {
setTail(tail);
match = null;
break;
}
else {
match = tail.match(tagPattern);
}
}
if (isFound === false) {
//failure case
setTail('');
}
return head;
}
/**
* Looks for the given Match value on the content object
* Can handle paths, e.g. response.footerLinks
* If content isn't matched then we test for matching functions at the base of the object
*/
function getContentForKey(match, content) {
var key = getMatchKey(match),
keyParts = key.split('.'),
responseContent = content,
i,
iLen = keyParts.length;
//for each key part
for (i=0; i < iLen; i++) {
//does it exist on the content object
if (responseContent && responseContent[keyParts[i]]) {
responseContent = responseContent[keyParts[i]];
//prefix urls, not a great solution
if (keyParts[i] === 'url') {
responseContent = validateUrl(responseContent);
}
}
else {
responseContent = undefined;
i = iLen; //break loop
}
}
if (responseContent === undefined) {
//look for bound functions
responseContent = instance.content[key];
if (jQuery.isFunction( responseContent )) {
return responseContent.apply(content);
}
return '';
}
return responseContent;
}
/**
* Some of the keys might still have brackets around them, this cleans them.
*/
function getMatchKey(match) {
return $.trim(match.replace(startPattern,'').replace(endPattern,''));
}
/*
* Public method for starting the parsing
*/
function render(template, content) {
instance.content = content;
var result = parser(template, content);
instance.content = null;
return result;
}
instance.render = render;
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment