Julien L
Julien L

Reputation: 1678

3 levels nested foreach in KnockoutJS

I have 3 levels of data to manage in Knockout, but only the first two are updating when add new data. The 3rd level is not updating.

Here is my code so far: http://jsfiddle.net/26medias/Zy8Wr/

There are Quizzes, each containing questions, which contain answers. I can add a quiz, I can add questions, but the answers are not updating.

All the examples I find online and on stackoverflow are only for 2 levels of data.

How can I have the 3rd level as an Observable for the [+] button to work?

Thanks!

Upvotes: 3

Views: 1293

Answers (2)

John Papa
John Papa

Reputation: 22318

Your array of answers is just an array, its not an observableArray. You need to define your array as ko.observableArray. Notice the code snippet bnelow uses observableArray forall 3 levels of arrays.

var initialData = ko.observableArray([
    {
        name:    "Quiz #1",
        questions:    ko.observableArray([
            {
                text: "Question #1.1?",
                answers: ko.observableArray([
                    {
                        text:     "Yes",
                        valid:    true
                    },
                    {
                        text:     "No",
                        valid:    false
                    }
                ])
            },

Upvotes: 1

Jeff Mercado
Jeff Mercado

Reputation: 134981

For every level of nesting you wish to be able to bind to, you need to map each level to an observable.

You mapped only up to an observable array of questions. However you should have mapped each of the answers in the questions as well.

scope.quizzes = ko.observableArray(
    ko.utils.arrayMap(quizzes, function(quiz) {
        return {
            name:      quiz.name,
            questions: ko.observableArray(quiz.questions) // not far enough
        };
    })
);

Since it appears you want to be able to edit the name of the quizzes and question and answer texts, you should make those properties observable as well.

scope.quizzes = ko.observableArray(
    ko.utils.arrayMap(quizzes, function(quiz) {
        return {
            name:      ko.observable(quiz.name),
            questions: ko.observableArray(
                ko.utils.arrayMap(quiz.questions, function (question) {
                    return {
                        text:    ko.observable(question.text),
                        answers: ko.observableArray(
                            ko.utils.arrayMap(question.answers, function (answer) {
                                return {
                                    text:  ko.observable(answer.text),
                                    valid: ko.observable(answer.valid)
                                };
                            })
                        )
                    };
                })
            )
        };
    })
);

This might seem a lot but that's what you have to do to be able to bind to these objects. You can make this process easier by using the mapping plugin and it will do this mapping for you.

scope.quizzes = ko.mapping.fromJS(quizzes);

Upvotes: 3

Related Questions