Ted
Ted

Reputation: 4067

Nested loops in Knockout.js with if:clause

I am having a problem with nested foreach statements in KO.

The following statements work fine on their own but when I combine them the inner one doesn't work and doesn't throw an error either.

Ideally I would like the inner join to be conditional but I have tried it without the if clause and still no luck.

Experts have a property array of all ExpertRoles assigned to the particular user. The outer loop is meant to print all ExpertRoles and the inner loop is meant to print the Experts that match the Expert role of the outer loop

1st foreach: WORKS

<ul data-bind="foreach: ExpertRoles">
    <li class="expert ui-menu-item" role="menuitem" data-bind="attr: { 'data-cid': Id }, text: Name">       
    </li>
</ul>

2nd foreach: WORKS

<ul data-bind="foreach: Experts">
    <li class="expert ui-menu-item" role="menuitem" data-bind="attr: { 'data-cid': ConnectionId, 'title': Username }">
        <a href="javascript:void(0);" data-bind="text: Username"></a>
    </li>
</ul>

combined with if clause: inner loop doesn't work, throws no error

<ul data-bind="foreach: ExpertRoles">
    <li class="expert ui-menu-item" role="menuitem" data-bind="attr: { 'data-cid': Id }, text: Name">
        <ul data-bind="foreach: Experts">
            <li class="expert ui-menu-item" role="menuitem" data-bind="if: ExpertRoles.indexOf($parent.Id) > 0, attr: { 'data-cid': ConnectionId, 'title': Username }">
                <a href="javascript:void(0);" data-bind="text: Username"></a>
            </li>
        </ul>
    </li>
</ul>

no if clause: inner loop doesn't work, throws no error

<ul data-bind="foreach: ExpertRoles">
    <li class="expert ui-menu-item" role="menuitem" data-bind="attr: { 'data-cid': Id }, text: Name">
        <ul data-bind="foreach: Experts">
            <li class="expert ui-menu-item" role="menuitem" data-bind="attr: { 'data-cid': ConnectionId, 'title': Username }">
                <a href="javascript:void(0);" data-bind="text: Username"></a>
            </li>
        </ul>
    </li>
</ul>

EDIT:

Let me add that I have tried container-less syntax (<!-- ko foreach:, <!-- ko if:, etc)

This is a part of my viewmodel, the data gets updated by SignalR so it is difficult for me to make a fiddle. Please, focus on that they work on their own.

var viewModel = {
    Users: ko.mapping.fromJS([]),
    ExpertRoles: ko.mapping.fromJS([])
};

// experts are a subset of users
viewModel.Experts = ko.computed(function () {
    return ko.utils.arrayFilter(this.Users(), function (item) {
        return item.IsExpert() === true;
    });
}, viewModel);

EDIT 2:

With your help I have managed to make the loops work. For some reason binding Name on the outer li causes the inner loop to stop working.

<ul>
    <!-- ko foreach: $root.ExpertRoles -->
        <li class="expert ui-menu-item" role="menuitem" data-bind="attr: { 'data-cid': Id }">

            <span data-bind="text: Name"></span>
            <ul>
                <!-- ko foreach: $root.Experts -->                                    
                    <li class="expert ui-menu-item" role="menuitem" data-bind="attr: { 'data-cid': ConnectionId }">
                        <a href="javascript:void(0);" data-bind="text: Username"></a>
                    </li>
                <!-- /ko -->
            </ul>

        </li>
    <!-- /ko -->
</ul>

Upvotes: 0

Views: 450

Answers (1)

Timur Osadchiy
Timur Osadchiy

Reputation: 6209

When you are using foreach in a nested loop you can access only properties of a parent model. Experts is not a property of an item from the ExpertRoles. It is a property of viewModel model. That's why it doesn't work. Try using $root:

<ul data-bind="foreach: $root.Experts">
    <li class="expert ui-menu-item" role="menuitem" data-bind="if: $root.ExpertRoles.indexOf($parent.Id) >= 0, attr: { 'data-cid': ConnectionId, 'title': Username }">
        <a href="javascript:void(0);" data-bind="text: Username"></a>
    </li>
</ul>

Upvotes: 1

Related Questions