Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

added $index value in the binding context of foreach templates. #182

Merged
merged 14 commits into from

9 participants

@barkmadley

There is probably a much better way to accomplish this. Code review required

@rniemeyer
Collaborator

I had been thinking a little about $index and I believe that it would be useful to make $index an observable.

Currently, your method adds $index, but does not adjust it when items are shifted. So, if it was adjusted and if $index was an observable, then if a binding does use it, it would get re-evaluated on a change.

Additionally, when I tried your changes, I had to add an index parameter to activateBindingsCallback and pass it to the call to createInnerBindingContext in that function.

@barkmadley

You are probably right. I will need to think a bit harder about how to make that work (and some more tests).

The test I added works without your change, but that could be because the dummyTemplateEngine is implemented slightly differently to a normal template engine (I found some funny behaviour in the memoization functionality, took me a while of stepping through the code to see what was going on). I updated the branch to reflect the added parameter.

@barkmadley

I tried to get observable indices working, however I am still getting a bug (the new test case fails on the 3rd assertion) that I can't seem to figure out. Can you take a look at it ryan?

@rniemeyer
Collaborator

Will try to help with this one, as soon as I get a chance.

I kind of have mixed feelings on this functionality though. In most cases, it is not needed. I wonder if this is too much work/complexity/overhead, when people can easily attach an index to their observableArray using the subscribe technique that I usually recommend and in a dependentObservable (filtered array) they can attach an index themselves. There are downsides to that way though (adds properties to your items, doesn't work on arrays of primitives).

I suppose there could be an option to turn index tracking on/off in the template binding.

not sure if Steve can think of an easier way to provide this functionality

@barkmadley

I think the main problem is that native templates are perceived as a replacement for jQuery templates. Since jQuery templates support an index parameter in their looping construct, not having one makes it appear as if native templates are incomplete in comparison. Undoubtedly this was probably a feature that some users used so they will likely complain. We are already starting to see this coming up on the mailing list more often.

@barkmadley

The most recent commit fixes the logic error that was in the patch. However the testsuite still does not work.

After a lot more debugging I got fed up with the testsuite and tried to use the code I had already written in a simple test case. Funnily enough it does work with the native template engine. The problem appears to be a disparity between the native template engine and the dummyTemplateEngine used in the testsuite.

@barkmadley

most notably using strings internally causes the "nodes" of the dummyTemplateEngine to be immutable, whereas with real template engines that work with nodes, the children are mutable. We can either fix the dummyTemplateEngine to use something other than strings internally or we can change the $index tactic such that when "retaining" nodes, it will perform a callback that can update sub-nodes.

@rniemeyer
Collaborator

Glad that you got it working. It does seem like a feature that many people want. Will be interested to see what Steve thinks about this approach.

@barkmadley barkmadley finally cracked it. the problem was duplicate context creation.
it is also necessary to perform double buffering of the stored contexts such
that we do not overwrite new nodes or move new nodes by mistake.
c8a3839
@barkmadley

So I found the problem. There are two calls to createInnerBindingContext in ko.renderTemplateForEach which is where I was creating the $index observable.

Deduplicating this with a contexts buffer that can then be updated with new contexts and moved contexts via double buffering solves all the problems with the test suite. Give it a go.

@SteveSanderson

Wow, excellent. Nice work with this.

I haven't yet looked through the implementation in detail, but in principle I agree it would be a useful feature to add.

Is it OK if I schedule this for inclusion in 1.3.1? I'm trying to avoid all new features for 1.3.0 now so that the release can be completed in a finite amount of time :)

@barkmadley

1.3.1 is fine. I am surprised this update isn't being called 2.0 considering the amount of work going into it.

@sibartlett

If semantic versioning were used it would be a 2.0 release, as this version of Knockout has breaking changes (particularly in regards to template engines, and the requirement on latest version of jquery templates).

@barkmadley barkmadley Merge branch 'master' into foreach-context-index
* master:
  More indentation tweaks for tidiness
  Update the build
  Minor indentation tweaks
  Stylistic tweaks to previous commit
  Eliminate redundant IE6/7 workarounds for radio/checkbox issues. These problems no longer apply now that bindings are always applied to elements after they've been put into the DOM. Fixes issue #169
  Refactor bad code
  minor fix
  Fix render array with \"undefined\" and \"null\" items in \"foreach\"  template
  Updated remove and removeAll to modify their underlying arrays rather than creating new arrays. This makes them consistent with the rest of the array write functions. Added tests to verify original array is modified. Also added test to verify there is no notification when nothing is removed.
  changing IE detection to not rely on user agents
  Fixed a variable which should not have been global.
d190e51
@pilavdzic

I am looking forward to 1.3.1 where this feature will be added.

It is very useful in places where the order of your list matters, for example.

Also, if you are working with legacy jquery templates that called custom functions that used index and you are now upgrading to ko built in templates but don't want to re-write a lot of code so you don't introduce bugs ;)

Finally, when there are nested arrays, it is lots of extra work to attach dependantobservables to each child array every time a new one is created to just implement this yourself.

I think it's GREAT idea to include in knockout.. it has been requested by many people. Sure there are other ways of doing it, but they are all much more complex and this makes things much easier for newer folks.

Thanks to everyone for this patch. I am glad it's coming and can't wait... Until then I have to write all this extra code now to workaround the issues I have currently.

@barkmadley

one of the limitations of the current patch is if you have nested foreach templates then it will be difficult to create a reference to the outer $index from the inner foreach. (jQuery templates didn't have this problem because you bound a custom name to the index in the loop). I might put some effort into adding a parameter to the template options that allow you to specify a variable name for this use case.

@while0pass

Besides $index variable in templates it would be also great to have variables like $isLast and $isFirst. It is also usefull to have a reference to the parent loop. In django templates all those variables are composed within a namespace "forloop" that is accessible within every for loop as a variable:

https://docs.djangoproject.com/en/dev/ref/templates/builtins/#for

I suppose all those variables are rather handy to have: 4 versions of index counter (0-based, 1-based, and two reversed ones), first and last items booleans and parent loop reference. for instance:

$forloop.index0
$forloop.index1
$forloop.revIndex0
$forloop.revIndex1
$forloop.isLast
$forloop.isFirst
$forloop.parentLoop

or smth like this.

@barkmadley

I did wonder why the binding context has the parent data item as the $parent context variable, but the parent binding context isn't available (this would essentially give you the parent loop or with or other context creating template binding).

Every variable listed there (except for the 0-index and the parent context) is computable from the given list and the current index so I don't see a reason to add them to the framework merely for convenience. Perhaps as some utility functions to make it less verbose. Really I just feel that pre-computing these values isn't worth it if 99% of the time they aren't going to be used.

@while0pass

Every variable listed there (except for the 0-index and the parent context)
is computable from the given list and the current index so I don't see a reason
to add them to the framework merely for convenience.

I suppose a reason is that this set of variables covers 99% of programmers' use cases with loops. So it is more than convenience, it is pragmatics. Otherwise, $index can be regarded just as a convenient feature as well. Of course, it is not, it is not only that kind of feature. But I agree that most of them are implemented much more easy if we have $index implemented than if we have not.

@barkmadley

Actually I withdraw my assertion that we would have to pre-compute the other variables. Since they can be calculated from the one or two essential variables then we can provide them as dependent observables that defer evaluation.

Mark Bradley added some commits
Mark Bradley Merge branch 'master' into foreach-context-index
Conflicts:
	spec/templatingBehaviors.js
	src/binding/editDetection/arrayToDomNodeChildren.js
	src/templating/templating.js
0535189
Mark Bradley Merge branch 'master' into foreach-context-index 9e91edb
@jamesfoster

What is the status of this feature?

@vamp

errors of the current solution:
1. $index not applied to child contexts
2. $index not recalculated when one of child element was deleted

@SteveSanderson

What is the status of this feature?

Still keen to have this in the next point release (note that v1.3.1, which this was originally estimated for, is the same as v2.1.0 in the new numbering scheme).

errors of the current solution

Thanks for pointing that out - we will need to resolve that before this can become part of the master branch. I need to check the current suggested implementation carefully, as I think there will be some refactoring needed.

@mbest
Collaborator

$index not applied to child contexts

One of the changes I made in #290 is to copy custom properties to the child context, which would fix this problem.

mbest added some commits
@mbest mbest Merge barkmadley:foreach-context-index into 182-foreach-index
Conflicts:
	spec/templatingBehaviors.js
	src/templating/templating.js
37ffbac
@mbest mbest Continuation of #182 - Simplify by having setDomNodeChildrenFromArray…
…Mapping manage the observable index.
2c3b918
@mbest
Collaborator

Currently one of the new specs fails in IE because IE strips out some spaces. It's "Data binding 'foreach' option should update bindings that reference an $index if the list changes".

@rniemeyer rniemeyer referenced this pull request
Closed

2.1 release discussion #338

mbest added some commits
@mbest mbest foreach $index: fix spec that failed in IE; help performance a bit
renderTemplateForEach now assumes that setDomNodeChildrenFromArrayMapping will call the mapping callback and then the afterAdding callback once for each new or changed item. Modify specs to verify this behavior.
3146a3a
@mbest mbest Extend the specs for setDomNodeChildrenFromArrayMapping to include re…
…placing a value through an observable item.
35704d9
@vamp

what about availability $array and $length variables context?

@SteveSanderson

what about availability $array and $length variables context?

You can get this information already using $parent.someArray and $parent.someArray.length

@vamp

Steve, in this case I need to know variable name of parent context (it breaks creating of reusable bindings)...

@vamp

what about:

        arrayItemContext['$array'] = arrayOrObservableArray;
        arrayItemContext['$length'] = length;

where (shared for all child contexts, outside executeTemplateForArrayItem)

    var length = ko.dependentObservable(function(){
        return (ko.utils.unwrapObservable(arrayOrObservableArray) || []).length;
    }, null, {'disposeWhenNodeIsRemoved': targetNode});
@rniemeyer
Collaborator

Took another look at this one. Functionality works well and the simplified implementation is pretty straightforward. I do kind of wish that it was opt-in, so we can avoid creating an extra observable for each item when $index is not needed, although the performance overhead is likely minimal.

@SteveSanderson SteveSanderson merged commit f9db930 into master
@SteveSanderson

Fantastic - this looks great. Thanks very much!

About perf, my quick foreach stress testing didn't show any significant extra cost to maintaining the $index observable. It was a lot slower if I actually referenced $index in my view (not surprising, because of course then a lot more of the view has to be redrawn whenever you mutate your array). But anyone who doesn't make use of $index shouldn't see any noticeable degradation of performance.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Oct 21, 2011
  1. @barkmadley
Commits on Oct 22, 2011
  1. @barkmadley
  2. @barkmadley
  3. @barkmadley
  4. @barkmadley

    comments.

    barkmadley authored
Commits on Oct 24, 2011
  1. @barkmadley

    we need to create the new indices by tracking added and retained node…

    barkmadley authored
    …s, using i is insufficient.
  2. @barkmadley

    finally cracked it. the problem was duplicate context creation.

    barkmadley authored
    it is also necessary to perform double buffering of the stored contexts such
    that we do not overwrite new nodes or move new nodes by mistake.
Commits on Oct 27, 2011
  1. @barkmadley

    Merge branch 'master' into foreach-context-index

    barkmadley authored
    * master:
      More indentation tweaks for tidiness
      Update the build
      Minor indentation tweaks
      Stylistic tweaks to previous commit
      Eliminate redundant IE6/7 workarounds for radio/checkbox issues. These problems no longer apply now that bindings are always applied to elements after they've been put into the DOM. Fixes issue #169
      Refactor bad code
      minor fix
      Fix render array with \"undefined\" and \"null\" items in \"foreach\"  template
      Updated remove and removeAll to modify their underlying arrays rather than creating new arrays. This makes them consistent with the rest of the array write functions. Added tests to verify original array is modified. Also added test to verify there is no notification when nothing is removed.
      changing IE detection to not rely on user agents
      Fixed a variable which should not have been global.
Commits on Dec 22, 2011
  1. Merge branch 'master' into foreach-context-index

    Mark Bradley authored
    Conflicts:
    	spec/templatingBehaviors.js
    	src/binding/editDetection/arrayToDomNodeChildren.js
    	src/templating/templating.js
  2. Merge branch 'master' into foreach-context-index

    Mark Bradley authored
Commits on Feb 22, 2012
  1. @mbest

    Merge barkmadley:foreach-context-index into 182-foreach-index

    mbest authored
    Conflicts:
    	spec/templatingBehaviors.js
    	src/templating/templating.js
  2. @mbest

    Continuation of #182 - Simplify by having setDomNodeChildrenFromArray…

    mbest authored
    …Mapping manage the observable index.
Commits on Feb 25, 2012
  1. @mbest

    foreach $index: fix spec that failed in IE; help performance a bit

    mbest authored
    renderTemplateForEach now assumes that setDomNodeChildrenFromArrayMapping will call the mapping callback and then the afterAdding callback once for each new or changed item. Modify specs to verify this behavior.
  2. @mbest

    Extend the specs for setDomNodeChildrenFromArrayMapping to include re…

    mbest authored
    …placing a value through an observable item.
This page is out of date. Refresh to see the latest.
View
40 spec/editDetectionBehaviors.js
@@ -62,7 +62,12 @@ describe('Compare Arrays', {
describe('Array to DOM node children mapping', {
before_each: function () {
+ var existingNode = document.getElementById("testNode");
+ if (existingNode != null)
+ existingNode.parentNode.removeChild(existingNode);
testNode = document.createElement("div");
+ testNode.id = "testNode";
+ document.body.appendChild(testNode);
},
'Should populate the DOM node by mapping array elements': function () {
@@ -154,31 +159,46 @@ describe('Array to DOM node children mapping', {
},
'Should handle sequences of mixed insertions and deletions': function () {
- var mappingInvocations = [];
+ var mappingInvocations = [], countCallbackInvocations = 0;
var mapping = function (arrayItem) {
mappingInvocations.push(arrayItem);
var output = document.createElement("DIV");
- output.innerHTML = arrayItem || "null";
+ output.innerHTML = ko.utils.unwrapObservable(arrayItem) || "null";
return [output];
};
+ var callback = function(arrayItem, nodes) {
+ ++countCallbackInvocations;
+ value_of(mappingInvocations[mappingInvocations.length-1]).should_be(arrayItem);
+ }
- ko.utils.setDomNodeChildrenFromArrayMapping(testNode, ["A"], mapping);
+ ko.utils.setDomNodeChildrenFromArrayMapping(testNode, ["A"], mapping, null, callback);
value_of(ko.utils.arrayMap(testNode.childNodes, function (x) { return x.innerHTML })).should_be(["A"]);
value_of(mappingInvocations).should_be(["A"]);
+ value_of(countCallbackInvocations).should_be(mappingInvocations.length);
- mappingInvocations = [];
- ko.utils.setDomNodeChildrenFromArrayMapping(testNode, ["B"], mapping); // Delete and replace single item
+ mappingInvocations = [], countCallbackInvocations = 0;
+ ko.utils.setDomNodeChildrenFromArrayMapping(testNode, ["B"], mapping, null, callback); // Delete and replace single item
value_of(ko.utils.arrayMap(testNode.childNodes, function (x) { return x.innerHTML })).should_be(["B"]);
value_of(mappingInvocations).should_be(["B"]);
+ value_of(countCallbackInvocations).should_be(mappingInvocations.length);
- mappingInvocations = [];
- ko.utils.setDomNodeChildrenFromArrayMapping(testNode, ["A", "B", "C"], mapping); // Add at beginning and end
+ mappingInvocations = [], countCallbackInvocations = 0;
+ ko.utils.setDomNodeChildrenFromArrayMapping(testNode, ["A", "B", "C"], mapping, null, callback); // Add at beginning and end
value_of(ko.utils.arrayMap(testNode.childNodes, function (x) { return x.innerHTML })).should_be(["A", "B", "C"]);
value_of(mappingInvocations).should_be(["A", "C"]);
+ value_of(countCallbackInvocations).should_be(mappingInvocations.length);
- mappingInvocations = [];
- ko.utils.setDomNodeChildrenFromArrayMapping(testNode, [1, null, "B"], mapping); // Add to beginning; delete from end
+ mappingInvocations = [], countCallbackInvocations = 0;
+ var observable = ko.observable(1);
+ ko.utils.setDomNodeChildrenFromArrayMapping(testNode, [observable, null, "B"], mapping, null, callback); // Add to beginning; delete from end
value_of(ko.utils.arrayMap(testNode.childNodes, function (x) { return x.innerHTML })).should_be(["1", "null", "B"]);
- value_of(mappingInvocations).should_be([1, null]);
+ value_of(mappingInvocations).should_be([observable, null]);
+ value_of(countCallbackInvocations).should_be(mappingInvocations.length);
+
+ mappingInvocations = [], countCallbackInvocations = 0;
+ observable(2); // Change the value of the observable
+ value_of(ko.utils.arrayMap(testNode.childNodes, function (x) { return x.innerHTML })).should_be(["2", "null", "B"]);
+ value_of(mappingInvocations).should_be([observable]);
+ value_of(countCallbackInvocations).should_be(mappingInvocations.length);
}
});
View
25 spec/templatingBehaviors.js
@@ -361,6 +361,31 @@ describe('Templating', {
ko.applyBindings({ myCollection: [1,2,3] }, testNode);
value_of(initCalls).should_be(3); // 3 because there were 3 items in myCollection
},
+
+ 'Data binding \'foreach\' option should apply bindings with an $index in the context': function () {
+ var myArray = new ko.observableArray([{ personName: "Bob" }, { personName: "Frank"}]);
+ ko.setTemplateEngine(new dummyTemplateEngine({ itemTemplate: "The item # is <span data-bind='text: $index'></span>" }));
+ testNode.innerHTML = "<div data-bind='template: { name: \"itemTemplate\", foreach: myCollection }'></div>";
+
+ ko.applyBindings({ myCollection: myArray }, testNode);
+ value_of(testNode.childNodes[0]).should_contain_html("<div>the item # is <span>0</span></div><div>the item # is <span>1</span></div>");
+ },
+
+ 'Data binding \'foreach\' option should update bindings that reference an $index if the list changes': function () {
+ var myArray = new ko.observableArray([{ personName: "Bob" }, { personName: "Frank"}]);
+ ko.setTemplateEngine(new dummyTemplateEngine({ itemTemplate: "The item <span data-bind='text: personName'></span>is <span data-bind='text: $index'></span>" }));
+ testNode.innerHTML = "<div data-bind='template: { name: \"itemTemplate\", foreach: myCollection }'></div>";
+
+ ko.applyBindings({ myCollection: myArray }, testNode);
+ value_of(testNode.childNodes[0]).should_contain_html("<div>the item <span>bob</span>is <span>0</span></div><div>the item <span>frank</span>is <span>1</span></div>");
+
+ var frank = myArray.pop(); // remove frank
+ value_of(testNode.childNodes[0]).should_contain_html("<div>the item <span>bob</span>is <span>0</span></div>");
+
+ myArray.unshift(frank); // put frank in the front
+ value_of(testNode.childNodes[0]).should_contain_html("<div>the item <span>frank</span>is <span>0</span></div><div>the item <span>bob</span>is <span>1</span></div>");
+
+ },
'Data binding \'foreach\' option should accept array with "undefined" and "null" items': function () {
var myArray = new ko.observableArray([undefined, null]);
View
31 src/binding/editDetection/arrayToDomNodeChildren.js
@@ -32,11 +32,11 @@
}
}
- function mapNodeAndRefreshWhenChanged(containerNode, mapping, valueToMap, callbackAfterAddingNodes) {
+ function mapNodeAndRefreshWhenChanged(containerNode, mapping, valueToMap, callbackAfterAddingNodes, index) {
// Map this array value inside a dependentObservable so we re-map when any dependency changes
var mappedNodes = [];
var dependentObservable = ko.dependentObservable(function() {
- var newMappedNodes = mapping(valueToMap) || [];
+ var newMappedNodes = mapping(valueToMap, index) || [];
// On subsequent evaluations, just replace the previously-inserted DOM nodes
if (mappedNodes.length > 0) {
@@ -69,6 +69,7 @@
var newMappingResult = [];
var lastMappingResultIndex = 0;
var nodesToDelete = [];
+ var newMappingResultIndex = 0;
var nodesAdded = [];
var insertAfterNode = null;
for (var i = 0, j = editScript.length; i < j; i++) {
@@ -76,7 +77,8 @@
case "retained":
// Just keep the information - don't touch the nodes
var dataToRetain = lastMappingResult[lastMappingResultIndex];
- newMappingResult.push(dataToRetain);
+ dataToRetain.indexObservable(newMappingResultIndex);
+ newMappingResultIndex = newMappingResult.push(dataToRetain);
if (dataToRetain.domNodes.length > 0)
insertAfterNode = dataToRetain.domNodes[dataToRetain.domNodes.length - 1];
lastMappingResultIndex++;
@@ -101,11 +103,17 @@
case "added":
var valueToMap = editScript[i].value;
- var mapData = mapNodeAndRefreshWhenChanged(domNode, mapping, valueToMap, callbackAfterAddingNodes);
+ var indexObservable = ko.observable(newMappingResultIndex);
+ var mapData = mapNodeAndRefreshWhenChanged(domNode, mapping, valueToMap, callbackAfterAddingNodes, indexObservable);
var mappedNodes = mapData.mappedNodes;
-
+
// On the first evaluation, insert the nodes at the current insertion point
- newMappingResult.push({ arrayEntry: editScript[i].value, domNodes: mappedNodes, dependentObservable: mapData.dependentObservable });
+ newMappingResultIndex = newMappingResult.push({
+ arrayEntry: editScript[i].value,
+ domNodes: mappedNodes,
+ dependentObservable: mapData.dependentObservable,
+ indexObservable: indexObservable
+ });
for (var nodeIndex = 0, nodeIndexMax = mappedNodes.length; nodeIndex < nodeIndexMax; nodeIndex++) {
var node = mappedNodes[nodeIndex];
nodesAdded.push({
@@ -123,7 +131,7 @@
insertAfterNode = node;
}
if (callbackAfterAddingNodes)
- callbackAfterAddingNodes(valueToMap, mappedNodes);
+ callbackAfterAddingNodes(valueToMap, mappedNodes, indexObservable);
break;
}
}
@@ -142,10 +150,11 @@
invokedBeforeRemoveCallback = true;
}
}
- if (!invokedBeforeRemoveCallback)
- ko.utils.arrayForEach(nodesToDelete, function (node) {
- ko.removeNode(node.element);
- });
+ if (!invokedBeforeRemoveCallback && nodesToDelete.length) {
+ var commonParent = nodesToDelete[0].element.parentNode;
+ for (var i = 0; i < nodesToDelete.length; i++)
+ commonParent.removeChild(nodesToDelete[i].element);
+ }
// Store a copy of the array items we just considered so we can difference it next time
ko.utils.domData.set(domNode, lastMappingResultDomDataKey, newMappingResult);
View
32 src/templating/templating.js
@@ -115,19 +115,27 @@
}
};
- ko.renderTemplateForEach = function (template, arrayOrObservableArray, options, targetNode, parentBindingContext) {
- var createInnerBindingContext = function(arrayValue) {
- return parentBindingContext['createChildContext'](ko.utils.unwrapObservable(arrayValue));
- };
+ ko.renderTemplateForEach = function (template, arrayOrObservableArray, options, targetNode, parentBindingContext) {
+ // Since setDomNodeChildrenFromArrayMapping always calls executeTemplateForArrayItem and then
+ // activateBindingsCallback for added items, we can store the binding context in the former to use in the latter.
+ var arrayItemContext;
+
+ // 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
+ var templateName = typeof(template) == 'function' ? template(arrayValue) : template;
+ arrayItemContext = parentBindingContext['createChildContext'](ko.utils.unwrapObservable(arrayValue));
+ arrayItemContext['$index'] = index;
+ return executeTemplate(null, "ignoreTargetNode", templateName, arrayItemContext, options);
+ }
// This will be called whenever setDomNodeChildrenFromArrayMapping has added nodes to targetNode
- var activateBindingsCallback = function(arrayValue, addedNodesArray) {
- var bindingContext = createInnerBindingContext(arrayValue);
- activateBindingsOnContinuousNodeArray(addedNodesArray, bindingContext);
+ var activateBindingsCallback = function(arrayValue, addedNodesArray, index) {
+ activateBindingsOnContinuousNodeArray(addedNodesArray, arrayItemContext);
if (options['afterRender'])
- options['afterRender'](addedNodesArray, bindingContext['$data']);
+ options['afterRender'](addedNodesArray, arrayValue);
};
-
+
return ko.dependentObservable(function () {
var unwrappedArray = ko.utils.unwrapObservable(arrayOrObservableArray) || [];
if (typeof unwrappedArray.length == "undefined") // Coerce single value into array
@@ -138,11 +146,7 @@
return options['includeDestroyed'] || item === undefined || item === null || !ko.utils.unwrapObservable(item['_destroy']);
});
- ko.utils.setDomNodeChildrenFromArrayMapping(targetNode, filteredArray, function (arrayValue) {
- // Support selecting template as a function of the data being rendered
- var templateName = typeof(template) == 'function' ? template(arrayValue) : template;
- return executeTemplate(null, "ignoreTargetNode", templateName, createInnerBindingContext(arrayValue), options);
- }, options, activateBindingsCallback);
+ ko.utils.setDomNodeChildrenFromArrayMapping(targetNode, filteredArray, executeTemplateForArrayItem, options, activateBindingsCallback);
}, null, { 'disposeWhenNodeIsRemoved': targetNode });
};
Something went wrong with that request. Please try again.