Skip to content
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,10 @@ desktop.ini
.eprj
perf/*
*.orig

MBestKnockout.csproj
MBestKnockout.sln
Properties/AssemblyInfo.cs
Web.config
Web.Debug.config
Web.Release.config
2 changes: 1 addition & 1 deletion build/fragments/version.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
2.1.0pre+mbest/smart-binding/beta.3
2.1.0pre+mbest/smart-binding/beta.3+GilesBradshaw/BindingProviderConfiguration
1,976 changes: 1,223 additions & 753 deletions build/output/knockout-latest.debug.js

Large diffs are not rendered by default.

94 changes: 10 additions & 84 deletions build/output/knockout-latest.js

Large diffs are not rendered by default.

1,654 changes: 838 additions & 816 deletions spec/bindingAttributeBehaviors.js

Large diffs are not rendered by default.

3,449 changes: 1,734 additions & 1,715 deletions spec/defaultBindingsBehaviors.js

Large diffs are not rendered by default.

1,378 changes: 706 additions & 672 deletions spec/templatingBehaviors.js

Large diffs are not rendered by default.

40 changes: 27 additions & 13 deletions src/binding/bindingProvider.js
Original file line number Diff line number Diff line change
@@ -1,63 +1,77 @@
(function() {
var defaultBindingAttributeName = "data-bind";
(function () {

ko.bindingProvider = function() {
ko.bindingProvider = function (configuration) {
this.configuration = setDefaultConfiguration(configuration);
this.bindingCache = {};
this['clearCache'] = function() {
this['clearCache'] = function () {
this.bindingCache = {};
};
};

ko.utils.extendInternal(ko.bindingProvider.prototype, {
'nodeHasBindings': function(node) {
'nodeHasBindings': function (node) {
switch (node.nodeType) {
case 1: return node.getAttribute(defaultBindingAttributeName) != null; // Element
case 1: return node.getAttribute(this.configuration.bindingAttribute) != null; // Element
case 8: return ko.virtualElements.virtualNodeBindingValue(node) != null; // Comment node
default: return false;
}
},

'getBindings': function(node, bindingContext) {
'getBindings': function (node, bindingContext) {
var bindingsString = this['getBindingsString'](node, bindingContext);
return bindingsString ? this['parseBindingsString'](bindingsString, bindingContext) : null;
},

// The following function is only used internally by this default provider.
// It's not part of the interface definition for a general binding provider.
'getBindingsString': function(node, bindingContext) {
'getBindingsString': function (node, bindingContext) {
switch (node.nodeType) {
case 1: return node.getAttribute(defaultBindingAttributeName); // Element
case 1: return node.getAttribute(this.configuration.bindingAttribute); // Element
case 8: return ko.virtualElements.virtualNodeBindingValue(node); // Comment node
default: return null;
}
},

// The following function is only used internally by this default provider.
// It's not part of the interface definition for a general binding provider.
'parseBindingsString': function(bindingsString, bindingContext) {
'parseBindingsString': function (bindingsString, bindingContext) {
try {
var viewModel = bindingContext['$data'],
scopes = (typeof viewModel == 'object' && viewModel != null) ? [viewModel, bindingContext] : [bindingContext],
bindingFunction = createBindingsStringEvaluatorViaCache(bindingsString, bindingContext['$options'], scopes.length, this.bindingCache);
return bindingFunction(scopes);
} catch (ex) {
throw new Error("Unable to parse bindings.\nMessage: " + ex + ";\nBindings value: " + bindingsString);
}
}
}
});

ko.bindingProvider['instance'] = new ko.bindingProvider();

ko.bindingProvider.configuration = function (bindingProvider) {
bindingProvider = bindingProvider ? bindingProvider : ko.bindingProvider["instance"];
var configuration = bindingProvider.configuration ? bindingProvider.configuration : {};
return setDefaultConfiguration(configuration);
};

function setDefaultConfiguration(configuration) {
if (!configuration) configuration = {};
configuration.name = configuration.name ? configuration.name : 'default';
configuration.bindingAttribute = configuration.bindingAttribute ? configuration.bindingAttribute : 'data-bind';
configuration.virtualElementTag = configuration.virtualElementTag ? configuration.virtualElementTag : "ko";
return configuration;
}

function createBindingsStringEvaluatorViaCache(bindingsString, bindingOptions, scopesCount, cache) {
var cacheKey = scopesCount + '_' + bindingsString;
return cache[cacheKey]
return cache[cacheKey]
|| (cache[cacheKey] = createBindingsStringEvaluator(bindingsString, bindingOptions, scopesCount));
}

function createBindingsStringEvaluator(bindingsString, bindingOptions, scopesCount) {
var rewrittenBindings = " { " + ko.bindingExpressionRewriting.insertPropertyAccessors(bindingsString, bindingOptions) + " } ";
return ko.utils.buildEvalWithinScopeFunction(rewrittenBindings, scopesCount);
}
}
})();

ko.exportSymbol('bindingProvider', ko.bindingProvider);
4 changes: 2 additions & 2 deletions src/templating/templateEngine.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
// // - bindingContext.$data is the data you should pass into the template
// // - you might also want to make bindingContext.$parent, bindingContext.$parents,
// // and bindingContext.$root available in the template too
// // - options gives you access to any other properties set on "data-bind: { template: options }"
// // - options gives you access to any other properties set on "data-bind: { template: options } (where data-bind is the configured binding attribute)"
// //
// // Return value: an array of DOM nodes
// }
Expand All @@ -20,7 +20,7 @@
// // For example, the jquery.tmpl template engine converts 'someScript' to '${ someScript }'
// }
//
// This is only necessary if you want to allow data-bind attributes to reference arbitrary template variables.
// This is only necessary if you want to allow binding attributes to reference arbitrary template variables.
// If you don't want to allow that, you can set the property 'allowTemplateRewriting' to false (like ko.nativeTemplateEngine does)
// and then you don't need to override 'createJavaScriptEvaluatorBlock'.

Expand Down
16 changes: 10 additions & 6 deletions src/templating/templateRewriting.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@

ko.templateRewriting = (function () {
var memoizeDataBindingAttributeSyntaxRegex = /(<[a-z]+\d*(\s+(?!data-bind=)[a-z0-9\-]+(=(\"[^\"]*\"|\'[^\']*\'))?)*\s+)data-bind=(["'])([\s\S]*?)\5/gi;
var memoizeVirtualContainerBindingSyntaxRegex = /<!--\s*ko\b\s*([\s\S]*?)\s*-->/g;
var memoizeDataBindingAttributeSyntaxRegex = function (bindingProvider) {
return new RegExp("(<[a-z]+\\d*(\\s+(?!" + ko.bindingProvider.configuration(bindingProvider).bindingAttribute + "=)[a-z0-9\\-]+(=(\\\"[^\\\"]*\\\"|\\'[^\\']*\\'))?)*\\s+)" + ko.bindingProvider.configuration(bindingProvider).bindingAttribute + "=([\"'])([\\s\\S]*?)\\5", "gi");
};
var memoizeVirtualContainerBindingSyntaxRegex = function (bindingProvider) {
return new RegExp("<!--\\s*" + ko.bindingProvider.configuration(bindingProvider).virtualElementTag + "\\b\\s*([\\s\\S]*?)\\s*-->", "g");
}

function validateDataBindValuesForRewriting(keyValueArray) {
var allValidators = ko.templateRewriting.bindingRewriteValidators;
Expand Down Expand Up @@ -44,10 +48,10 @@ ko.templateRewriting = (function () {
},

memoizeBindingAttributeSyntax: function (htmlString, templateEngine) {
return htmlString.replace(memoizeDataBindingAttributeSyntaxRegex, function () {
return constructMemoizedTagReplacement(/* dataBindAttributeValue: */ arguments[6], /* tagToRetain: */ arguments[1], templateEngine);
}).replace(memoizeVirtualContainerBindingSyntaxRegex, function() {
return constructMemoizedTagReplacement(/* dataBindAttributeValue: */ arguments[1], /* tagToRetain: */ "<!-- ko -->", templateEngine);
return htmlString.replace(memoizeDataBindingAttributeSyntaxRegex(), function () {
return constructMemoizedTagReplacement(/* dataBindAttributeValue: */arguments[6], /* tagToRetain: */arguments[1], templateEngine);
}).replace(memoizeVirtualContainerBindingSyntaxRegex(), function () {
return constructMemoizedTagReplacement(/* dataBindAttributeValue: */arguments[1], /* tagToRetain: */"<!-- ko -->", templateEngine);
});
},

Expand Down
14 changes: 10 additions & 4 deletions src/virtualElements.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,25 @@ ko.virtualElements = (function() {
// but it does give them a nonstandard alternative property called "text" that it can read reliably. Other browsers don't have that property.
// So, use node.text where available, and node.nodeValue elsewhere
var commentNodesHaveTextProperty = document.createComment("test").text === "<!--test-->";
var startCommentRegex = function (bindingProvider) {
return commentNodesHaveTextProperty ? new RegExp("^<!--\\s*" + ko.bindingProvider.configuration(bindingProvider).virtualElementTag + "\\s+(.*\\:.*)\\s*-->$") : new RegExp("^\\s*" + ko.bindingProvider.configuration(bindingProvider).virtualElementTag + "\\s+(.*\\:.*)\\s*$");
};

var startCommentRegex = commentNodesHaveTextProperty ? /^<!--\s*ko\s+(.*\:.*)\s*-->$/ : /^\s*ko\s+(.*\:.*)\s*$/;
var endCommentRegex = commentNodesHaveTextProperty ? /^<!--\s*\/ko\s*-->$/ : /^\s*\/ko\s*$/;
var endCommentRegex = function (bindingProvider) {
return commentNodesHaveTextProperty ? new RegExp("^<!--\\s*\\/" + ko.bindingProvider.configuration(bindingProvider).virtualElementTag + "\\s*-->$") : new RegExp("^\\s*\\/" + ko.bindingProvider.configuration(bindingProvider).virtualElementTag + "\\s*$");
};
var htmlTagsWithOptionallyClosingChildren = { 'ul': true, 'ol': true };


function isStartComment(node) {
return (node.nodeType == 8) && (commentNodesHaveTextProperty ? node.text : node.nodeValue).match(startCommentRegex);
return (node.nodeType == 8) && (commentNodesHaveTextProperty ? node.text : node.nodeValue).match(startCommentRegex());
}

function isEndComment(node) {
return (node.nodeType == 8) && (commentNodesHaveTextProperty ? node.text : node.nodeValue).match(endCommentRegex);
return (node.nodeType == 8) && (commentNodesHaveTextProperty ? node.text : node.nodeValue).match(endCommentRegex());
}


function getVirtualChildren(startComment, allowUnbalanced) {
var currentNode = startComment;
var depth = 1;
Expand Down