Question

I've been looking at examples of nested foreach loops in knockout all afternoon, and I haven't been able to get anything working. My current setup, at least the relevant parts, are below.

ViewModel:

var sample = {
    this.pageData = ko.observable();
    this.panels = ko.observableArray();
};

ko.utils.extend(sample.prototype, {
    activate: { 

        this.pageData(sampleData);
        this.panels([
            {
        name: 'column1',
        keys: ['key1', 'key2', 'key3'],
        loadedWidgets: ko.observableArray()
            },
            {
                name: 'column2',
                keys: ['key4', 'key5'],
                loadedWidgets: ko.observableArray()
            },
            {
                name: 'column3',
                keys: ['key6'],
                loadedWidgets: ko.observableArray()
            }
        ]);

        this.loadWidgetPanels(this.panels(), this.pageData());

    },
    loadWidgetPanels: function (panels, pageData) {
            for (var i = 0; i < panels.length; i++) {
                    var screens = filterContentByKey(pageData.Content, panels[i].keys);
                    if (screens) {
                        panels[i].loadedWidgets.push(widgetFactory.getWidgets(screens));
                    }
            }
    }
}

View:

<div>
    <!-- ko foreach: panels -->
        <div class="3columnStyle">
            <!-- ko foreach: loadedWidgets -->
                <!--ko compose: $data --><!-- /ko -->
            <!-- /ko -->
        </div>
    <!-- /ko -->
</div>

I've confirmed that I'm getting back the right data in the right format in my loadedWidgets, but they don't display in the view. I can tell that the view at least knows how much data is there, because my DOM has a ko compose element for each widget. For example, the first column has a handful of widgets, and that div gets created with a handful of widgets in it. Column 2 has 2 widgets, and it gets 2 compose elements. Column 3 has 1 widget and gets one element. It's just not displaying the widgets themselves. Do I need additional elements or additional binding somewhere?

I have a working model of this that doesn't rely on nested loops. Instead of using the array of objects, it just creates each of the observable arrays. In other words, it's not looping. Instead of one array containing three objects, each with its own array, I have three arrays:

this.column1Widgets();
this.column2Widgets();
this.column3Widgets();

They're constructed the same way, just manually instead of looping. And the View looks more like this:

<div class="3columnStyle">
    <!-- ko foreach: column3Widgets -->
        <!-- ko compose: $data --><!-- /ko -->
    <!-- /ko -->
</div>
<div class="3columnStyle">
    <!-- ko foreach: column3Widgets -->
        <!-- ko compose: $data --><!-- /ko -->
    <!-- /ko -->
</div>
<div class="3columnStyle">
    <!-- ko foreach: column3Widgets -->
        <!-- ko compose: $data --><!-- /ko -->
    <!-- /ko -->
</div>

I'm not sure why it's not working with the nested loop, but since the data is identical, I'm sure there's something I'm missing in setting up the View.

Was it helpful?

Solution 2

As Eric Taylor suggested, it must have something to do with the containerless binding. I created a jsfiddle with some oversimplified object models, but changing my DOM from the above to the following immediately fixed the issue:

<div>Test</div>
<div data-bind="foreach: panels">
<ul data-bind="foreach: loadedWidgets">
    <li data-bind="text: $data"></li>
</ul>
</div>

I don't think I have a good grasp of how the containers interact with the binding yet.

OTHER TIPS

Without seeing the rest of your code, it's difficult to tell for sure. I'm a little suspicious of the sample object in your viewmodel. But it seems to me that you're not actually nesting your foreach's.

In your view, replace

foreach: loadedWidgets 

with

foreach: $data.loadedWidgets

You need to reference the parent foreach in some way. $data represents the current item in the parent foreach, and that item, if I understand your model correctly, contains a loadedWidgets key.

Also, there's no need for containerless binding in your case.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top