2

I need to get the $index in a javascript function to implement prev/next buttons for a list of values. The method I found seems cumbersome, perhaps there is a more straightforward way.

Now I do this to get the $index in javascript, then put it in an observable:

<div data-bind="foreach: myarray">
    <div data-bind="click: function(data, event){ onclick(data, event, $index()); }"

function onclick(idata, event, index) {
        theAppViewModel.choiceindex(index);

On SO I found a small improvement by getting $index from the event:

<div data-bind="foreach: myarray">
    <div data-bind="click: onclick"

function onclick(idata, event) {
        var context = ko.contextFor(event.target);
        theAppViewModel.choiceindex(context.$index());

From a commenter came the method to get the index by searching the array for the selected value, that usually has its own observable, e.g. choice:, like:

var i = TheArray.indexOf(theAppViewModel.choice());

Normally, the length of the array on the page isn't huge, and if it has big objects, you could just search over one of its fields with fun syntax like:

myarray.find(x => x.id === searchvalue);

But I wonder if it isn't possible to access the $index even more directly, without storing in my own observable choiceindex, as the Knockout docs say that $index is already an observable.


Here is a complete sample code to play with:

<!DOCTYPE html>
<html>
<head>
    <title>Test Knockout Foreach</title>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <script src='https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.1/knockout-min.js'></script>
    <style>
        .selected {background-color: #f0f0f0;}
    </style>
</head>
<body>
    <div data-bind="foreach: myarray" style="width: 10em;">
        <div data-bind="click: function(data, event){ onclick(data, event, $index()); }, 
                        css: { selected: $data == $root.choice() }">
            <span data-bind="text: $index"></span>
            <span data-bind="text: $data"></span>
        </div>
    </div>
    <input type="button" value="Prev" data-bind="click: onprev" />
    <input type="button" value="Next" data-bind="click: onnext" />
    <p>
        Choice:
        <span data-bind="text: choice"></span>
    </p>
    <p>
        Index:
        <span data-bind="text: choiceindex"></span>
    </p>
    <script>
        var TheArray = ["apple", "pear", "banana", "coconut", "peanut"];
        function onclick(idata, event, index) {
            theAppViewModel.choice(idata);
            theAppViewModel.choiceindex(index);
            //var context = ko.contextFor(event.target);
            //theAppViewModel.choiceindex(context.$index());
            //theAppViewModel.choiceindex(index);
        }
        function onprev(idata, event) {
            var i = theAppViewModel.choiceindex() - 1;
            if (i >= 0) {
                theAppViewModel.choice(TheArray[i]);
                theAppViewModel.choiceindex(i);
            }
        }
        function onnext(idata, event) {
            //var i = theAppViewModel.choiceindex() + 1;
            //var dummydata = theAppViewModel.choice();
            //var dummy = TheArray.indexOf(dummydata);
            var i = TheArray.indexOf(theAppViewModel.choice()) + 1;
            if (i < TheArray.length) {
                theAppViewModel.choice(TheArray[i]);
                theAppViewModel.choiceindex(i);
            }
        }
        function AppViewModel() {
            var self = this;
            self.myarray = ko.observableArray(TheArray);
            self.choice = ko.observable();
            self.choiceindex = ko.observable();
        }
        var theAppViewModel = new AppViewModel();
        window.onload = function () {
            ko.applyBindings(theAppViewModel);
        }
    </script>
</body>
</html>
4
  • 2
    The solutions you've already found are the only that I know of, but I am curious why you need the index if you have the item itself ($data). You can always use myarray.indexOf($data). Commented Apr 7, 2017 at 16:05
  • @JasonSpake Great! This eliminates the extra observable. But it incurs a (linear) search over the array for each time I click the prev or next button, instead of just getting the index from the extra observable. So I would still be interested in getting direct access to the $index observable. Commented Apr 7, 2017 at 16:15
  • It does iterate the array certainly, but in the case where there are so many items in the array that the cost of iteration becomes tangible then it probably shouldn't be drawn to the DOM with a foreach binding. ;) Commented Apr 7, 2017 at 16:19
  • @JasonSpake Correct. Also, if the array elements are big objects, there is the option of searching over an ID field with syntax like myarray.find(x => x.id === $data.id). Still curious about direct access to the $index observable as the most logical solution, but if nobody has a clue, I can certainly live with your method. Commented Apr 7, 2017 at 16:25

2 Answers 2

1

There is no built-in binding to set a viewmodel value from a binding, but it's simple create one to do so:

ko.bindingHandlers.copyIndex = {
    init: function (element, valueAccessor, allBindings, vm, bindingContext) {
        vm.index = bindingContext.index;
    }
};

The use it as follows:

<div data-bind="foreach: myarray">
    <div data-bind="copyIndex"></div>
</div>

However, I'd still not recommend this approach, since it ties viewmodel behavior to the presence of specific bindings. Jason Spake's suggestion to use myarray.indexOf($data) (or ko.utils.arrayIndexOf(myarray, $data)) would be more robust.

Sign up to request clarification or add additional context in comments.

Comments

-1

write 'index' and not 'index()' in the html part. Try this:

<div data-bind="foreach: myarray" style="width: 10em;">
    <div data-bind="click: function(data, event){ onclick(data, event, $index}, 
                    css: { selected: $data == $root.choice() }">
        <span data-bind="text: $index"></span>
        <span data-bind="text: $data"></span>
    </div>
</div>

3 Comments

Thanks, but this does not work. My 1st method works, sending the index number, as in $index(), to the onclick() method, so changing to sending the index nr observable as a function should not work.
knockout needs you write index. not index () then the onclick would do in js with jquery you can also retrieve the index in the feature using the event as you did. I did not notice other errors
So you propose to write index, not index() in the html, and index(), not index, in the javascript. Will probably work, but what is the advantage?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.