<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>TPL Engine</title>
</head>
<body>
<div id="output"></div>
<script type="text/javascript">
;(function (global, factory) {
if (typeof define === "function" && define.amd) {
define(factory);
} else if (typeof module === "object" && typeof module.exports === "object") {
module.exports = factory();
} else {
global.TPL = factory();
}
})(this, function () {
// Usage:
// console.log( new TPL.Template("<div>Total: {{= value }}<div>", {delimiters: ["{{", "}}"]}).render({value: "<span>15</span>"}) );
// console.log( new TPL.Template("<div>Total: {{- block('<div>{{= value }}</div>', {delimiters: ['{{', '}}']}, {value: value}) }}<div>", {delimiters: ["{{", "}}"]}).render({block: TPL.block, value: "<span>15</span>"}) );
// Options:
// delimiters - characters to use for open and close delimiters
// rmWhitespace - remove whitespace between HTML-tags
// Tags:
// <% - scriptlet tag for control-flow, no output
// <%% - outputs raw template part
// <%- - outputs the raw unescaped value into the template
// <%= - outputs the HTML escaped value into the template
// <%# - comment tag, no execution, no output
// %> - plain ending tag
// Block:
// "<div><%- block('<div><%= value %></div>', {delimiters: ['<%', '%>']}, {value: outerValue}) %></div>"
function Template (templateString, settingsObject) {
var delimiters = ["<%", "%>"]
, rmWhitespace = false; // removeWhitespace
if (settingsObject) {
if ('delimiters' in settingsObject) {delimiters = settingsObject.delimiters;}
if ('rmWhitespace' in settingsObject) {rmWhitespace = settingsObject.rmWhitespace;}
}
var renderFunction = createRenderFunction(templateString, delimiters);
return {render: function (dataObject) {
var renderedHTML = renderFunction(dataObject, escapeHtmlFunction);
if (rmWhitespace === true) {
renderedHTML = removeSpaces(renderedHTML);
}
return renderedHTML;
}};
}
function removeSpaces (renderedHTML) {
return renderedHTML.replace(/\r/g, "") // Remove carriage return symbols
.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, "") // Trim space: \s, BOM: \uFEFF, NBSP: \xA0
.replace(/\s+(<[^%(?:span|b|strong|i|em|del|s|u|a|sub|sup|small|abbr|img)])/g, "$1") // Remove spaces between start tag and content
.replace(/([^%(?:span|b|strong|i|em|del|s|u|a|sub|sup|small|abbr|img)]>)\s+/g, "$1") // Remove spaces between end tag and content
.replace(/\s+/g, " "); // Remove spaces inside content
}
function createMatchRegExp (delimiters) { // delimiters = ["<%", "%>"]
var openDelimiter = delimiters[0] // <%
, closeDelimiter = delimiters[1] // %>
, stringInsideDelimiters = "([\\s\\S]+?)" // any string
, tags = {
rawTemplatePart: openDelimiter + "%" + stringInsideDelimiters + closeDelimiter // <%% "<div><%= value %></div>" %> --> "<div><%= value %></div>"
, rawHtmlUnescapedValue: openDelimiter + "-" + stringInsideDelimiters + closeDelimiter // <%- "1<br />2" %> --> "1<br />2"
, htmlEscapedValue: openDelimiter + "=" + stringInsideDelimiters + closeDelimiter // <%= "1<br />2" %> --> "1<br />2"
, comment: openDelimiter + "#" + stringInsideDelimiters + closeDelimiter // <%# any comment %> --> ""
, evaluate: openDelimiter + stringInsideDelimiters + closeDelimiter // <% if (value === 1) { %>one<% } %> --> "one"
}
, matchRegExp = new RegExp([
tags.rawTemplatePart // Important!!! Must be first!
, tags.rawHtmlUnescapedValue
, tags.htmlEscapedValue
, tags.comment
, tags.evaluate // Important!!! Must be last!
].join("|") + "|$", "g"); // /<%%([\s\S]+?)%>|<%-([\s\S]+?)%>|<%=([\s\S]+?)%>|<%#([\s\S]+?)%>|<%([\s\S]+?)%>|$/g
return matchRegExp;
}
function createRenderFunction (templateString, delimiters) {
var matchRegExp = createMatchRegExp(delimiters)
, renderFunction;
try {
renderFunction = new Function ("_dataObject_", "_escapeHtmlFunction_" // ) {
, "if (!_dataObject_) {_dataObject_ = {};}" + "\n"
+ "var _parsedTemplateString_ = '';" + "\n"
+ "with (_dataObject_) {" + "\n"
+ "_parsedTemplateString_ += '" + (function(){
var resultString = ""
, index = 0;
templateString.replace(matchRegExp, function (
match
, rawTemplatePartCodeString
, rawUnescapedValueCodeString
, htmlEscapedValueCodeString
, commentCodeString
, evaluateCodeString
, offset
) {
resultString += escapeString(templateString.slice(index, offset));
index = offset + match.length;
if (rawTemplatePartCodeString) {
resultString += delimiters[0] + escapeString(rawTemplatePartCodeString) + delimiters[1];
} else if (rawUnescapedValueCodeString) {
resultString += "'" + "\n"
+ "+ (function(){" + "\n"
+ "var _value_ = " + rawUnescapedValueCodeString + ";" + "\n"
+ "if (_value_ === undefined || _value_ === null) {return '';} else {return _value_;}" + "\n"
+ "})()" + "\n"
+ "+ '";
} else if (htmlEscapedValueCodeString) {
resultString += "'" + "\n"
+ "+ (function(){" + "\n"
+ "var _value_ = " + htmlEscapedValueCodeString + ";" + "\n"
+ "if (_value_ === undefined || _value_ === null) {return '';} else {return _escapeHtmlFunction_(_value_);}" + "\n"
+ "})()" + "\n"
+ "+ '";
} else if (commentCodeString) {
// Do nothing
} else if (evaluateCodeString) {
resultString += "';" + "\n"
+ evaluateCodeString + "\n"
+ "_parsedTemplateString_ += '";
}
return match;
});
return resultString;
})()
+ "';" + "\n"
+ "}" + "\n"
+ "return _parsedTemplateString_;"
// }
);
} catch (error) {
if (error instanceof SyntaxError) {error.message += " while compiling template: " + templateString;}
throw error;
}
return renderFunction;
}
function escapeString (string) {
var escapeCharacters = {
"'": "\\'"
, "\\": "\\\\"
, "\r": "\\r"
, "\n": "\\n"
, "\u2028": "\\u2028"
, "\u2029": "\\u2029"
};
return string.replace(/\\|'|\r|\n|\u2028|\u2029/g, function (match) {return escapeCharacters[match];});
}
function escapeHtmlFunction (html) {
var escapeCharacters = {
"&": "&"
, "<": "<"
, ">": ">"
, "\"": """
, "'": "'"
, "`": "`"
};
return String(html).replace(/(?:&|<|>|"|'|`)/g, function (match) {return escapeCharacters[match];});
}
function block (templatePartString, settingsObject, dataObject) {
return new Template(templatePartString, settingsObject).render(dataObject);
}
return {
Template: Template
, block: block
};
});
// Тесты
var template = '<div><p>Total: <%- value %></p><% var a = 2; while (a--) { %><%- block(innerTemplate, {delimiters: ["{{", "}}"]}, {innerValue: innerValue}) %><% } %></div>'
, innerTemplate = '<p>Last: {{- innerValue }}</p>';
var result = new TPL.Template(template).render({value: '<span>15</span>', block: TPL.block, innerTemplate: innerTemplate, innerValue: '<span>20</span>'});
var fragment = document.createDocumentFragment()
, element = document.createElement('div');
element.innerHTML = result;
fragment.appendChild(element);
document.getElementsByTagName('body')[0].appendChild(fragment);
</script>
</body>
</html>
Комментариев нет:
Отправить комментарий