Skip to content

Commit

Permalink
Merge pull request knockout#606 from SteveSanderson/465-foreach-named…
Browse files Browse the repository at this point in the history
…-iteration-variable-alternative-impl

"foreach" and "template" bindings accept "as" option to define alias for...
  • Loading branch information
SteveSanderson committed Aug 17, 2012
2 parents ffc54f9 + 8f250b6 commit 59cea3b
Show file tree
Hide file tree
Showing 5 changed files with 71 additions and 5 deletions.
43 changes: 43 additions & 0 deletions spec/defaultBindingsBehaviors.js
Original file line number Diff line number Diff line change
Expand Up @@ -1831,6 +1831,49 @@ describe('Binding: Foreach', {
value_of(testNode).should_contain_text("B");
},

'Should be able to give an alias to $data using \"as\"': function() {
testNode.innerHTML = "<div data-bind='foreach: { data: someItems, as: \"item\" }'><span data-bind='text: item'></span></div>";
var someItems = ['alpha', 'beta'];
ko.applyBindings({ someItems: someItems }, testNode);
value_of(testNode.childNodes[0]).should_contain_html('<span data-bind="text: item">alpha</span><span data-bind="text: item">beta</span>');
},

'Should be able to give an alias to $data using \"as\", and use it within a nested loop': function() {
testNode.innerHTML = "<div data-bind='foreach: { data: someItems, as: \"item\" }'>"
+ "<span data-bind='foreach: sub'>"
+ "<span data-bind='text: item.name+\":\"+$data'></span>,"
+ "</span>"
+ "</div>";
var someItems = [{ name: 'alpha', sub: ['a', 'b'] }, { name: 'beta', sub: ['c'] }];
ko.applyBindings({ someItems: someItems }, testNode);
value_of(testNode.childNodes[0]).should_contain_text('alpha:a,alpha:b,beta:c,');
},

'Should be able to set up multiple nested levels of aliases using \"as\"': function() {
testNode.innerHTML = "<div data-bind='foreach: { data: someItems, as: \"item\" }'>"
+ "<span data-bind='foreach: { data: sub, as: \"subvalue\" }'>"
+ "<span data-bind='text: item.name+\":\"+subvalue'></span>,"
+ "</span>"
+ "</div>";
var someItems = [{ name: 'alpha', sub: ['a', 'b'] }, { name: 'beta', sub: ['c','d'] }];
ko.applyBindings({ someItems: someItems }, testNode);
value_of(testNode.childNodes[0]).should_contain_text('alpha:a,alpha:b,beta:c,beta:d,');
},

'Should be able to give an alias to $data using \"as\", and use it within arbitrary descendant binding contexts': function() {
testNode.innerHTML = "<div data-bind='foreach: { data: someItems, as: \"item\" }'><span data-bind='if: item.length'><span data-bind='text: item'></span>,</span></div>";
var someItems = ['alpha', 'beta'];
ko.applyBindings({ someItems: someItems }, testNode);
value_of(testNode.childNodes[0]).should_contain_text('alpha,beta,');
},

'Should be able to give an alias to $data using \"as\", and use it within descendant binding contexts defined using containerless syntax': function() {
testNode.innerHTML = "<div data-bind='foreach: { data: someItems, as: \"item\" }'>x<!-- ko if: item.length --><span data-bind='text: item'></span>x,<!-- /ko --></div>";
var someItems = ['alpha', 'beta'];
ko.applyBindings({ someItems: someItems }, testNode);
value_of(testNode.childNodes[0]).should_contain_text('xalphax,xbetax,');
},

'Should be able to output HTML5 elements (even on IE<9, as long as you reference either innershiv.js or jQuery1.7+Modernizr)': function() {
// Represents https://github.com/SteveSanderson/knockout/issues/194
ko.utils.setHtml(testNode, "<div data-bind='foreach:someitems'><section data-bind='text: $data'></section></div>");
Expand Down
20 changes: 20 additions & 0 deletions spec/templatingBehaviors.js
Original file line number Diff line number Diff line change
Expand Up @@ -473,6 +473,17 @@ describe('Templating', {
value_of(testNode.childNodes[0].childNodes.length).should_be(0);
},

'Data binding \'foreach\' option should accept an \"as\" option to define an alias for the iteration variable': function() {
// Note: There are more detailed specs (e.g., covering nesting) associated with the "foreach" binding which
// uses this templating functionality internally.
var myArray = new ko.observableArray(["A", "B"]);
ko.setTemplateEngine(new dummyTemplateEngine({ itemTemplate: "[js:myAliasedItem]" }));
testNode.innerHTML = "<div data-bind='template: { name: \"itemTemplate\", foreach: myCollection, as: \"myAliasedItem\" }'></div>";

ko.applyBindings({ myCollection: myArray }, testNode);
value_of(testNode.childNodes[0]).should_contain_text("AB");
},

'Data binding \'foreach\' option should stop tracking inner observables when the container node is removed': function() {
var innerObservable = ko.observable("some value");
var myArray = new ko.observableArray([{obsVal:innerObservable}, {obsVal:innerObservable}]);
Expand Down Expand Up @@ -657,6 +668,15 @@ describe('Templating', {
value_of(testNode.childNodes[0]).should_contain_text("Alternative output");
},

'Should be able to bind $data to an alias using \'as\'': function() {
ko.setTemplateEngine(new dummyTemplateEngine({
myTemplate: "ValueLiteral: [js:item.prop], ValueBound: <span data-bind='text: item.prop'></span>"
}));
testNode.innerHTML = "<div data-bind='template: { name: \"myTemplate\", data: someItem, as: \"item\" }'></div>";
ko.applyBindings({ someItem: { prop: 'Hello' } }, testNode);
value_of(testNode.childNodes[0]).should_contain_text("ValueLiteral: Hello, ValueBound: Hello");
},

'Data-bind syntax should expose parent binding context as $parent if binding with an explicit \"data\" value': function() {
ko.setTemplateEngine(new dummyTemplateEngine({
myTemplate: "ValueLiteral: [js:$parent.parentProp], ValueBound: <span data-bind='text: $parent.parentProp'></span>"
Expand Down
8 changes: 5 additions & 3 deletions src/binding/bindingAttributeSyntax.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
(function () {
ko.bindingHandlers = {};

ko.bindingContext = function(dataItem, parentBindingContext) {
ko.bindingContext = function(dataItem, parentBindingContext, dataItemAlias) {
if (parentBindingContext) {
ko.utils.extend(this, parentBindingContext); // Inherit $root and any custom properties
this['$parentContext'] = parentBindingContext;
Expand All @@ -17,9 +17,11 @@
this['ko'] = ko;
}
this['$data'] = dataItem;
if (dataItemAlias)
this[dataItemAlias] = dataItem;
}
ko.bindingContext.prototype['createChildContext'] = function (dataItem) {
return new ko.bindingContext(dataItem, this);
ko.bindingContext.prototype['createChildContext'] = function (dataItem, dataItemAlias) {
return new ko.bindingContext(dataItem, this, dataItemAlias);
};
ko.bindingContext.prototype['extend'] = function(properties) {
var clone = ko.utils.extend(new ko.bindingContext(), this);
Expand Down
1 change: 1 addition & 0 deletions src/binding/defaultBindings.js
Original file line number Diff line number Diff line change
Expand Up @@ -550,6 +550,7 @@ ko.bindingHandlers['foreach'] = {
// If bindingValue.data is the array, preserve all relevant options
return {
'foreach': bindingValue['data'],
'as': bindingValue['as'],
'includeDestroyed': bindingValue['includeDestroyed'],
'afterAdd': bindingValue['afterAdd'],
'beforeRemove': bindingValue['beforeRemove'],
Expand Down
4 changes: 2 additions & 2 deletions src/templating/templating.js
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@
// This will be called by setDomNodeChildrenFromArrayMapping to get the nodes to add to targetNode
var executeTemplateForArrayItem = function (arrayValue, index) {
// Support selecting template as a function of the data being rendered
arrayItemContext = parentBindingContext['createChildContext'](ko.utils.unwrapObservable(arrayValue));
arrayItemContext = parentBindingContext['createChildContext'](ko.utils.unwrapObservable(arrayValue), options['as']);
arrayItemContext['$index'] = index;
var templateName = typeof(template) == 'function' ? template(arrayValue, arrayItemContext) : template;
return executeTemplate(null, "ignoreTargetNode", templateName, arrayItemContext, options);
Expand Down Expand Up @@ -200,7 +200,7 @@
if (shouldDisplay) {
// Render once for this single data point (or use the viewModel if no data was provided)
var innerBindingContext = (typeof bindingValue == 'object') && ('data' in bindingValue)
? bindingContext['createChildContext'](ko.utils.unwrapObservable(bindingValue['data'])) // Given an explitit 'data' value, we create a child binding context for it
? bindingContext['createChildContext'](ko.utils.unwrapObservable(bindingValue['data']), bindingValue['as']) // Given an explitit 'data' value, we create a child binding context for it
: bindingContext; // Given no explicit 'data' value, we retain the same binding context
templateSubscription = ko.renderTemplate(templateName || element, innerBindingContext, /* options: */ bindingValue, element);
} else
Expand Down

0 comments on commit 59cea3b

Please sign in to comment.