Created
July 30, 2015 08:08
-
-
Save ryanand26/456d687a02352e5c5118 to your computer and use it in GitHub Desktop.
Very basic template renderer
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
/** | |
* 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