Knockout.js - foreach binding - test if last eleme

2019-01-31 06:34发布

问题:

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?

回答1:

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.



回答2:

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



回答3:

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



回答4:

Try the following:

  1. Use $root instead of $parent
  2. Prepare the last item in advance and pass this item to your template as additional parameter.
  3. Encapsulate the last element to observable.