I am using the following template:
<div class="datatypeOptions" data-bind="if: $data.datatypeTemplate().allowOptions">
<h3>Allowed responses</h3>
<p data-bind="if: $data.datatypeTemplate().datatypeOptions().length == 0">There are no responses for this question, yet. <a href="#" data-bind="click: function(d, e){$root.addDatatypeOption($data.datatypeTemplate());}">Add one</a>
<ul data-bind="foreach: $data.datatypeTemplate().datatypeOptions()">
<li>
<a href="#" data-bind="text: name, click: $root.selectedDatatypeOption, visible: $data !== $root.selectedDatatypeOption()"></a>
<input data-bind="value: name, visibleAndSelect: $data === $root.selectedDatatypeOption(), event: { blur: $root.clearDatatypeOption }, executeOnEnter: { callback: function(){ $root.addDatatypeOption($parent.datatypeTemplate()); } }" />
//I want to show this a tag only if $data is the last element in the array.
Problem here ===> <a href="#" data-bind="if: $data == $parent.datatypeTemplate().datatypeOptions()[ $parent.datatypeTemplate().datatypeOptions().length - 1 ], click: function(d, e){$root.addDatatypeOption($data.datatypeTemplate());}"><img src='/static/img/icons/custom-task-wizard/black/plus_12x12.png' title='Add option'></a>
</li>
</ul>
</div>
I get this error in the console:
Uncaught Error: Unable to parse bindings.
Message: TypeError: Object [object Object] has no method 'datatypeTemplate';
Bindings value: if: $data == $parent.datatypeTemplate().datatypeOptions()[ $parent.datatypeTemplate().datatypeOptions().length - 1 ], click: function(d, e){$root.addDatatypeOption($data.datatypeTemplate());}
Is my only option to add a function to my viewmodel that returns true/false if the passed element is last in the array?
I've simplified the problem, but this jsFiddle demonstrates a possible solution.
"if" binding:
<div data-bind="if: ($index() === ($parent.data().length - 1))">I'm the last element</div>
Containerless "if" binding:
<!-- ko if: ($index() === ($parent.data().length - 1)) -->
<div>I'm the last element again</div>
<!-- /ko -->
You can use $index
within a foreach
binding to get the index of the currently bound item. Use that to compare against the original collection of the parent.
See HERE for more information regarding binding contexts.
I noticed there are a number of requests for enhancements to KO to support the $length
, $last
, or $array
reserved properties in the foreach
binding although, for a variety of reasons (often performance), they have not made it into the codebase.
If anyone is interested in exposing these elements for a specific case using a custom binding, here is a simple example of exposing the $length
variable in order to print a "pretty" list.
You simply use forEachWithLength
anywhere you would normally use foreach
.
ko.bindingHandlers.forEachWithLength = {
init: function (element, valueAccessor, allBindingsAccessor, viewModel, context)
{
return ko.bindingHandlers.foreach.init(element, valueAccessor, allBindingsAccessor, viewModel, context);
},
update: function (element, valueAccessor, allBindingsAccessor, viewModel, context)
{
var array = ko.utils.unwrapObservable(valueAccessor());
var extendedContext = context.extend({"$length" : array.length });
ko.bindingHandlers.foreach.update(element, valueAccessor, allBindingsAccessor, viewModel, extendedContext);
}
};
Example usage:
<div data-bind="forEachWithLength: myArray">
<span data-bind="text: $data"></span>
<!-- ko if: ($index() < $length-2) -->, <!-- /ko -->
<!-- ko if: ($index() === $length-2) --> and <!-- /ko -->
</div>
Input : ["One", "Two", "Three", "Four"]
Output : One, Two, Three and Four
Fiddle
Further reading
If you are NOT using as
option in the foreach
binding, then go to the most-upvoted answer to this question.
If you are DO using as
operator in the foreach
binding. then that answer will NOT work.
Here is the solution in that case
<div data-bind="foreach:{data: Items, as :'item'}">
<div data-bind="if: ($index() === ($parent.Items().length - 1))">I'm the last element</div>
</div>
The secret with replacing the $parent.data()
with the name of the observable array you are using In my case it was named Items
, so I replaced the $parent.data()
with $parent.Items()
NOTE this solution is working in all cases, in case you are using as
option or not,
but in the first case it solves something that the most-upvoted answer did not solve