Skip to content

Commit

Permalink
foreach: $array, $isFirst, $isLast, $isEven, $isOdd, $next, $previous
Browse files Browse the repository at this point in the history
  • Loading branch information
barsh committed Sep 18, 2018
1 parent 4d93283 commit c523594
Show file tree
Hide file tree
Showing 2 changed files with 161 additions and 0 deletions.
140 changes: 140 additions & 0 deletions spec/defaultBindings/foreachBehaviors.js
Original file line number Diff line number Diff line change
Expand Up @@ -542,6 +542,14 @@ describe('Binding: Foreach', function() {
expect(testNode).toContainText("B");
});

it('Should be able to access a filtered array using \"$array\"', function() {
testNode.innerHTML = "<div data-bind='foreach: someItems.filter(function(el) { return el > 10 })'><span data-bind='text: ko.toJSON($array)'></span></div>";
var someItems = [10, 20];
ko.applyBindings({ someItems: someItems }, testNode);
expect(testNode.childNodes[0]).toContainText('[20]');
});


it('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'];
Expand Down Expand Up @@ -807,6 +815,72 @@ describe('Binding: Foreach', function() {
ko.applyBindings({ someItems: someItems }, testNode);
expect(testNode.childNodes[0]).toContainText('0alpha:0a,0alpha:1b,1beta:0c,1beta:1d,');
});

it('Should provide itemIsEven as pureComputed in the context accessible across multiple nested levels', function() {
testNode.innerHTML = "<div data-bind='foreach: { data: someItems, as: \"item\", noChildContext: true }'>"
+ "<span data-bind='foreach: { data: item.sub, as: \"subvalue\", noChildContext: true }'>"
+ "<span data-bind='text: itemIsEven()+item.name+\":\"+subvalueIsEven()+subvalue'></span>,"
+ "</span>"
+ "</div>";
var someItems = [{ name: 'alpha', sub: ['a', 'b'] }, { name: 'beta', sub: ['c','d'] }];
ko.applyBindings({ someItems: someItems }, testNode);
expect(testNode.childNodes[0]).toContainText('truealpha:truea,truealpha:falseb,falsebeta:truec,falsebeta:falsed,');
});

it('Should provide itemIsFirst as pureComputed in the context accessible across multiple nested levels', function() {
testNode.innerHTML = "<div data-bind='foreach: { data: someItems, as: \"item\", noChildContext: true }'>"
+ "<span data-bind='foreach: { data: item.sub, as: \"subvalue\", noChildContext: true }'>"
+ "<span data-bind='text: itemIsFirst()+item.name+\":\"+subvalueIsFirst()+subvalue'></span>,"
+ "</span>"
+ "</div>";
var someItems = [{ name: 'alpha', sub: ['a', 'b'] }, { name: 'beta', sub: ['c','d'] }];
ko.applyBindings({ someItems: someItems }, testNode);
expect(testNode.childNodes[0]).toContainText('truealpha:truea,truealpha:falseb,falsebeta:truec,falsebeta:falsed,');
});

it('Should provide itemIsLast as pureComputed in the context accessible across multiple nested levels', function() {
testNode.innerHTML = "<div data-bind='foreach: { data: someItems, as: \"item\", noChildContext: true }'>"
+ "<span data-bind='foreach: { data: item.sub, as: \"subvalue\", noChildContext: true }'>"
+ "<span data-bind='text: itemIsLast()+item.name+\":\"+subvalueIsLast()+subvalue'></span>,"
+ "</span>"
+ "</div>";
var someItems = [{ name: 'alpha', sub: ['a', 'b'] }, { name: 'beta', sub: ['c','d'] }];
ko.applyBindings({ someItems: someItems }, testNode);
expect(testNode.childNodes[0]).toContainText('falsealpha:falsea,falsealpha:trueb,truebeta:falsec,truebeta:trued,');
});

it('Should provide itemArray as pureComputed in the context accessible across multiple nested levels', function() {
testNode.innerHTML = "<div data-bind='foreach: { data: someItems, as: \"item\", noChildContext: true }'>"
+ "<span data-bind='foreach: { data: item.sub, as: \"subvalue\", noChildContext: true }'>"
+ "<span data-bind='text: itemArray[itemIndex()].name+item.name+\":\"+subvalueArray[subvalueIndex()]+subvalue'></span>,"
+ "</span>"
+ "</div>";
var someItems = [{ name: 'alpha', sub: ['a', 'b'] }, { name: 'beta', sub: ['c','d'] }];
ko.applyBindings({ someItems: someItems }, testNode);
expect(testNode.childNodes[0]).toContainText('alphaalpha:aa,alphaalpha:bb,betabeta:cc,betabeta:dd,');
});

it('Should provide itemNext as pureComputed in the context accessible across multiple nested levels', function() {
testNode.innerHTML = "<div data-bind='foreach: { data: someItems, as: \"item\", noChildContext: true }'>"
+ "<span data-bind='foreach: { data: item.sub, as: \"subvalue\", noChildContext: true }'>"
+ "<span data-bind='text: itemNext() && itemNext().name+item.name+\":\"+subvalueNext() && subvalueNext()+subvalue'></span>,"
+ "</span>"
+ "</div>";
var someItems = [{ name: 'alpha', sub: ['a', 'b'] }, { name: 'beta', sub: ['c','d'] }];
ko.applyBindings({ someItems: someItems }, testNode);
expect(testNode.childNodes[0]).toContainText('ba,undefinedb,,,');
});

it('Should provide itemPrevious as pureComputed in the context accessible across multiple nested levels', function() {
testNode.innerHTML = "<div data-bind='foreach: { data: someItems, as: \"item\", noChildContext: true }'>"
+ "<span data-bind='foreach: { data: item.sub, as: \"subvalue\", noChildContext: true }'>"
+ "<span data-bind='text: itemPrevious() && itemPrevious().name+item.name+\":\"+subvaluePrevious() && subvaluePrevious()+subvalue'></span>,"
+ "</span>"
+ "</div>";
var someItems = [{ name: 'alpha', sub: ['a', 'b'] }, { name: 'beta', sub: ['c','d'] }];
ko.applyBindings({ someItems: someItems }, testNode);
expect(testNode.childNodes[0]).toContainText(',,undefinedc,cd,');
});
});

describe('With "noChildContext = false" and "as"', function () {
Expand Down Expand Up @@ -884,5 +958,71 @@ describe('Binding: Foreach', function() {
ko.applyBindings({ someItems: someItems }, testNode);
expect(testNode.childNodes[0]).toContainText('0alpha:0a,0alpha:1b,1beta:0c,1beta:1d,');
});

it('Should provide itemIsEven as pureComputed in the context accessible across multiple nested levels', function() {
testNode.innerHTML = "<div data-bind='foreach: { data: someItems, as: \"item\", noChildContext: true }'>"
+ "<span data-bind='foreach: { data: item.sub, as: \"subvalue\", noChildContext: true }'>"
+ "<span data-bind='text: itemIsEven()+item.name+\":\"+subvalueIsEven()+subvalue'></span>,"
+ "</span>"
+ "</div>";
var someItems = [{ name: 'alpha', sub: ['a', 'b'] }, { name: 'beta', sub: ['c','d'] }];
ko.applyBindings({ someItems: someItems }, testNode);
expect(testNode.childNodes[0]).toContainText('truealpha:truea,truealpha:falseb,falsebeta:truec,falsebeta:falsed,');
});

it('Should provide itemIsFirst as pureComputed in the context accessible across multiple nested levels', function() {
testNode.innerHTML = "<div data-bind='foreach: { data: someItems, as: \"item\", noChildContext: true }'>"
+ "<span data-bind='foreach: { data: item.sub, as: \"subvalue\", noChildContext: true }'>"
+ "<span data-bind='text: itemIsFirst()+item.name+\":\"+subvalueIsFirst()+subvalue'></span>,"
+ "</span>"
+ "</div>";
var someItems = [{ name: 'alpha', sub: ['a', 'b'] }, { name: 'beta', sub: ['c','d'] }];
ko.applyBindings({ someItems: someItems }, testNode);
expect(testNode.childNodes[0]).toContainText('truealpha:truea,truealpha:falseb,falsebeta:truec,falsebeta:falsed,');
});

it('Should provide itemIsLast as pureComputed in the context accessible across multiple nested levels', function() {
testNode.innerHTML = "<div data-bind='foreach: { data: someItems, as: \"item\", noChildContext: true }'>"
+ "<span data-bind='foreach: { data: item.sub, as: \"subvalue\", noChildContext: true }'>"
+ "<span data-bind='text: itemIsLast()+item.name+\":\"+subvalueIsLast()+subvalue'></span>,"
+ "</span>"
+ "</div>";
var someItems = [{ name: 'alpha', sub: ['a', 'b'] }, { name: 'beta', sub: ['c','d'] }];
ko.applyBindings({ someItems: someItems }, testNode);
expect(testNode.childNodes[0]).toContainText('falsealpha:falsea,falsealpha:trueb,truebeta:falsec,truebeta:trued,');
});

it('Should provide itemArray as pureComputed in the context accessible across multiple nested levels', function() {
testNode.innerHTML = "<div data-bind='foreach: { data: someItems, as: \"item\", noChildContext: true }'>"
+ "<span data-bind='foreach: { data: item.sub, as: \"subvalue\", noChildContext: true }'>"
+ "<span data-bind='text: itemArray[itemIndex()].name+item.name+\":\"+subvalueArray[subvalueIndex()]+subvalue'></span>,"
+ "</span>"
+ "</div>";
var someItems = [{ name: 'alpha', sub: ['a', 'b'] }, { name: 'beta', sub: ['c','d'] }];
ko.applyBindings({ someItems: someItems }, testNode);
expect(testNode.childNodes[0]).toContainText('alphaalpha:aa,alphaalpha:bb,betabeta:cc,betabeta:dd,');
});

it('Should provide itemNext as pureComputed in the context accessible across multiple nested levels', function() {
testNode.innerHTML = "<div data-bind='foreach: { data: someItems, as: \"item\", noChildContext: true }'>"
+ "<span data-bind='foreach: { data: item.sub, as: \"subvalue\", noChildContext: true }'>"
+ "<span data-bind='text: itemNext() && itemNext().name+item.name+\":\"+subvalueNext() && subvalueNext()+subvalue'></span>,"
+ "</span>"
+ "</div>";
var someItems = [{ name: 'alpha', sub: ['a', 'b'] }, { name: 'beta', sub: ['c','d'] }];
ko.applyBindings({ someItems: someItems }, testNode);
expect(testNode.childNodes[0]).toContainText('ba,undefinedb,,,');
});

it('Should provide itemPrevious as pureComputed in the context accessible across multiple nested levels', function() {
testNode.innerHTML = "<div data-bind='foreach: { data: someItems, as: \"item\", noChildContext: true }'>"
+ "<span data-bind='foreach: { data: item.sub, as: \"subvalue\", noChildContext: true }'>"
+ "<span data-bind='text: itemPrevious() && itemPrevious().name+item.name+\":\"+subvaluePrevious() && subvaluePrevious()+subvalue'></span>,"
+ "</span>"
+ "</div>";
var someItems = [{ name: 'alpha', sub: ['a', 'b'] }, { name: 'beta', sub: ['c','d'] }];
ko.applyBindings({ someItems: someItems }, testNode);
expect(testNode.childNodes[0]).toContainText(',,undefinedc,cd,');
});
});
});
21 changes: 21 additions & 0 deletions src/templating/templating.js
Original file line number Diff line number Diff line change
Expand Up @@ -176,14 +176,35 @@

// This will be called by setDomNodeChildrenFromArrayMapping to get the nodes to add to targetNode
var executeTemplateForArrayItem = function (arrayValue, index) {
var isFirst = function() { return index() === 0; }
var isLast = function() { return index() === ko.utils.unwrapObservable(arrayOrObservableArray).length-1; }
var isEven = function() { return index() % 2 == 0; }
var isOdd = function() { return index() % 2 == 1; }
var previous = function() { return ko.utils.unwrapObservable(arrayOrObservableArray)[index()-1]; }
var next = function() { return ko.utils.unwrapObservable(arrayOrObservableArray)[index()+1]; }

// Support selecting template as a function of the data being rendered
arrayItemContext = parentBindingContext['createChildContext'](arrayValue, {
'as': asName,
'noChildContext': options['noChildContext'],
'extend': function(context) {
context['$index'] = index;
context['$isFirst'] = ko.pureComputed(isFirst);
context['$isLast'] = ko.pureComputed(isLast);
context['$isEven'] = ko.pureComputed(isEven);
context['$isOdd'] = ko.pureComputed(isOdd);
context['$previous'] = ko.pureComputed(previous);
context['$next'] = ko.pureComputed(next);
context['$array'] = arrayOrObservableArray;
if (asName) {
context[asName + "Index"] = index;
context[asName + 'IsFirst'] = ko.pureComputed(isFirst);
context[asName + 'IsLast'] = ko.pureComputed(isLast);
context[asName + 'IsEven'] = ko.pureComputed(isEven);
context[asName + 'IsOdd'] = ko.pureComputed(isOdd);
context[asName + 'Previous'] = ko.pureComputed(previous);
context[asName + 'Next'] = ko.pureComputed(next);
context[asName + 'Array'] = arrayOrObservableArray;
}
}
});
Expand Down

0 comments on commit c523594

Please sign in to comment.