1
votes

I'm having trouble trying to get a number from each item in a knockout observable array and add the numbers together and assign it to another computed variable. Here's what I have right now...

Semesters: ko.observableArray([
    {
        semesterName: "Fall",
        semesterCode: "300",
        PlannedCourses: ko.observableArray([]),
        totalCredits: ko.computed(function(){
            var total = 0;
            ko.utils.arrayForEach(this.PlannedCourses, function (course) {
                total += course.MinHours();
            });

            return total;
        }),
    },
    ...

What I'm trying to do is, in the totalCredits variable, I'm trying to iterate through the PlannedCourses array and get the MinHours variable for each item and add them together in the total variable. Then I return it to the totalCredits item in the Semesters array. The issue I'm having is getting the PlannedCourses variable in the ko.utils.arrayForEach part. I'm getting an undefined on it and I'm not sure why. I think it's a simple syntax error but I can't see what's wrong.

The PlannedCourses observable array is a dynamic object that is getting the list of PlannedCourses properly. It's defined in the context of itself but I'm not passing it to the totalCredits computed function properly.

I hope this is clear enough. Thank you for your help!

Note: All the rest of the code is working as intended. The only part that isn't working is the totalCredits computed function. I'm not sure if anything within the ko.utils.arrayForEach is working as I haven't been able to get that far.

3

3 Answers

4
votes

You're going to need to change the way you populate your Semesters observable array to use a constructor function in order to get a reference to the correct scope for this:

function semester(name, code) {
    this.Name = name;
    this.Code = code;
    this.PlannedCourses = ko.observableArray([]);
    this.totalCredits = ko.computed(function(){
        var total = 0;
        ko.utils.arrayForEach(this.PlannedCourses(), function (course) {
            //Note the change to "this.PlannedCourses()" above to get the underlying array
            total += course.MinHours();
        });

        return total;
    }, this); //now we can pass "this" as the context for the computed
}

See how we can now pass in an object to the second argument for ko.computed to use as the context for this in the inner function. For more information, see the knockout docs: Managing 'this'.

You then create new instances of semester when populating your array:

Semesters: ko.observableArray([
    new semester("Fall", "300"),
    new semester(...)
]);

This approach also means you have a consistent way of creating your semester objects (the computed is only defined once for one thing), rather than possibly incorporating typos etc in any repetition you may originally have had.

0
votes

As others already mentioned your this is not what you think it is. In your case the context should be passed to the computed as follows:

totalCredits: ko.computed(function() {
    // Computation goes here..
}, this)

Another approach could be to store the correct this to some local variable during the object creation (ex. var self = this; and then use self instead of this).

However, ko.utils.arrayForEach doesn't work with observable arrays but works on pure JavaScript arrays, so you should unwrap the observable array to access the elements of the underlying array:

ko.utils.arrayForEach(this.PlannedCourses(), function(course) { 
    // ...
});

// Or

ko.utils.arrayForEach(ko.unwrap(this.PlannedCourses), function(course) { 
    // ... 
});
0
votes

The scope (this) isn't what you think it is.

See http://knockoutjs.com/documentation/computedObservables.html

try adding your context, like the following:

Semesters: ko.observableArray([
{
    semesterName: "Fall",
    semesterCode: "300",
    PlannedCourses: ko.observableArray([]),
    totalCredits: ko.computed(function(){
        var total = 0;
        ko.utils.arrayForEach(this.PlannedCourses, function (course) {
            total += course.MinHours();
        });

        return total;
    }, this), // new context passed in here
},
...

Doing this passes in the context of the array item itself into your computed function.

Edit: you may need to access the Semesters object inside you loop, and add some way to reference the current item:

Semesters: ko.observableArray([
{
    semesterName: "Fall",
    semesterCode: "300",
    PlannedCourses: ko.observableArray([]),
    totalCredits: ko.computed(function(){
        var total = 0;
        for( var i = 0, len = Semesters().length; i < len; i++ ) {
            // check current array item, possibly add an id?
            if( Semesters()[i].semesterName === "Fall" &&
                Semesters()[i].semesterCode === "300" ) {

                ko.utils.arrayForEach(Semesters()[i].PlannedCourses, function (course) {
                   total += course.MinHours();
                });
                break; // done searching
            }
        }

        return total;
    })
},