Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Binding provider configuration #13

Open
wants to merge 9 commits into
base: smart-binding
Choose a base branch
from
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