Last active
December 20, 2015 12:39
-
-
Save jpellerin/6133182 to your computer and use it in GitHub Desktop.
Mustache-like syntax for knockoutjs (3.0 or better), based on http://blog.stevensanderson.com/2013/07/09/knockout-v2-3-0-released-v3-0-0-beta-available/
This file contains hidden or 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
<html> | |
<head> | |
<style> | |
.goo { font-family: sans-serif; } | |
.boo { color: #999 }; | |
</style> | |
</head> | |
<body> | |
<ol class="{{ things }} listy {{ stuff }}"> | |
{{#people}} | |
<li id="{{ id }}" data-bind="css: { blue: name=='joe' }" burp="{{ name }}"> | |
{{ name.toUpperCase() }} is {{ age }} years old. | |
<p>Set age: <input type="range" value="{{ age }}" /></p> | |
</li> | |
{{/people}} | |
</ol> | |
<script src="build/output/knockout-latest.debug.js"></script> | |
<script src="stachebinding.js"></script> | |
<script src="runexample.js"></script> | |
</body> | |
</html> |
This file contains hidden or 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
ko.bindingProvider.instance = new MustacheBindingProvider(); | |
function MyViewModel() { | |
this.people = [ | |
{ id: 1, name: 'Bert', age: ko.observable(94) }, | |
{ id: 2, name: 'Frankie', age: ko.observable(2) } | |
]; | |
this.things = 'goo'; | |
this.stuff = 'boo'; | |
} | |
ko.applyBindings(new MyViewModel()); | |
console.log('ran!'); |
This file contains hidden or 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
var MustacheBindingProvider = function () { | |
var _this = this; | |
var expressionRegex = /{{([\s\S]*?)}}/g; | |
this.constructor = MustacheBindingProvider; | |
this.preprocessNode = function( node ) { | |
if (node.nodeType === 3 && node.nodeValue) { | |
// text node | |
return _this.preprocessTextNode(node); | |
} else if (node.nodeType === 1) { | |
return _this.preprocessElementNode(node); | |
} | |
} | |
this.preprocessTextNode = function( node ) { | |
var newNodes = replaceExpressionsInText( | |
node.nodeValue, expressionRegex, function( expressionText ) { | |
var fchar = expressionText.charAt(0); | |
if (fchar == '#') { | |
expressionText = expressionText.substring(1); | |
return [ | |
document.createComment("ko foreach: " + expressionText), | |
]; | |
} else if (fchar == '/') { | |
return [document.createComment("/ko")]; | |
} else if (fchar == '^') { | |
expressionText = expressionText.substring(1); | |
return [document.createComment("ko ifnot: " + expressionText)]; | |
} else { | |
return [ | |
document.createComment("ko text: " + expressionText), | |
document.createComment("/ko") | |
]; | |
} | |
}); | |
// Insert the resulting nodes into the DOM and remove the original | |
// unpreprocessed node | |
console.log('node', node); | |
console.log('newNodes', newNodes); | |
if (newNodes) { | |
for (var i = 0; i < newNodes.length; i++) { | |
node.parentNode.insertBefore(newNodes[i], node); | |
} | |
node.parentNode.removeChild(node); | |
return newNodes; | |
} | |
} | |
this.preprocessElementNode = function( node ) { | |
var attrs = node.attributes, toRemove = [], toBind = {}, | |
attrBinding = '', allBindings = '', | |
current = node.attributes['data-bind'], | |
bindingMap = { | |
'value': 'value', | |
'style': 'style', | |
'disabled': 'disable', | |
'checked': 'checked' | |
}; | |
if (current) { | |
allBindings = current.value; | |
} | |
for (var i=attrs.length-1; i>=0; i--) { | |
var attr = attrs[i], | |
bindingName = bindingMap[attr.name] || 'attr'; | |
console.log(attr.name, bindingMap[attr.name], bindingName); | |
exprText = replaceExpressionsInAttr(attr.value); | |
if (exprText !== null) { | |
toRemove.push(attr.name); | |
if (typeof toBind[bindingName] === 'undefined') { | |
toBind[bindingName] = []; | |
} | |
toBind[bindingName].push({'name': attr.name, 'value': exprText}); | |
} | |
} | |
for (var i=0, ln=toRemove.length; i<ln; i++) { | |
node.removeAttribute(toRemove[i]); | |
} | |
for(var prop in toBind) { | |
var binders = toBind[prop]; | |
if (binders && binders.length > 0) { | |
for (var i=0, ln=binders.length; i<ln; i++) { | |
var term = binders[i]; | |
if (i>0) { | |
attrBinding += ', '; | |
} | |
if (prop === 'attr') { | |
attrBinding += "'" + term.name + "': " + term.value; | |
} else { | |
attrBinding += term.value; | |
} | |
} | |
if (prop === 'attr') { | |
attrBinding = 'attr: {' + attrBinding + '}'; | |
} else { | |
attrBinding = prop + ': ' + attrBinding; | |
} | |
if (allBindings === '') { | |
allBindings = attrBinding; | |
} else { | |
allBindings += ', ' + attrBinding; | |
} | |
} | |
} | |
if (allBindings !== '') { | |
node.setAttribute('data-bind', allBindings); | |
console.log(node, allBindings); | |
} | |
} | |
function replaceExpressionsInText(text, expressionRegex, callback) { | |
var prevIndex = expressionRegex.lastIndex = 0, | |
resultNodes = null, | |
match; | |
// Find each expression marker, and for each one, invoke the callback | |
// to get an array of nodes that should replace that part of the text | |
while (match = expressionRegex.exec(text)) { | |
var leadingText = text.substring(prevIndex, match.index); | |
prevIndex = expressionRegex.lastIndex; | |
resultNodes = resultNodes || []; | |
// Preserve leading text | |
if (leadingText) { | |
resultNodes.push(document.createTextNode(leadingText)); | |
} | |
resultNodes.push.apply(resultNodes, callback(match[1])); | |
} | |
// Preserve trailing text | |
var trailingText = text.substring(prevIndex); | |
if (resultNodes && trailingText) { | |
resultNodes.push(document.createTextNode(trailingText)); | |
} | |
return resultNodes; | |
} | |
function replaceExpressionsInAttr(text) { | |
var prevIndex = expressionRegex.lastIndex = 0, resultText = ''; | |
// FIXME this breaks 2-way bindings | |
while (match = expressionRegex.exec(text)) { | |
var leadingText = text.substring(prevIndex, match.index); | |
prevIndex = expressionRegex.lastIndex; | |
if (leadingText) { | |
if (resultText) { | |
resultText += ' + '; | |
} | |
resultText += "'" + leadingText + "'"; | |
} | |
if (resultText) { | |
resultText += ' + '; | |
} | |
resultText += match[1]; | |
} | |
return resultText || null; | |
} | |
} | |
MustacheBindingProvider.prototype = ko.bindingProvider.instance; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment