Search code examples
javascriptknockout.jsknockout-2.0

How to nest object in Knockout.js


In Knockout.js, what is the best way to nest object inside of an an array?

I am trying to have an observable that is sorted by a group of courses title and inside there would be different course offered to a student with the same group property ( course code).

This is my snippet

function course(_id, _code, _title, _campus) {
    var self = this;
    this.id = ko.observable(_id);
    this.courseCode = ko.observable(_code);
    this.courseTitle = ko.observable(_title);
    this.coursecampus = ko.observable(_campus);
}

function gpCourseProperties(_code, _isHidden) {
    var self = this;
    this.gpCode = ko.observable(_code);
    this.hide = ko.observable(_isHidden);
    this.courses = ko.observableArray();

    this.addCourse = function (_id, _courseCode, _courseTitle, _courseCampus) {
        self.courses.push(new course(_id, _courseCode, _courseTitle, _courseCampus));
    }

    this.switchMutated = function (code) {
        self.hide(!self.hide());
    };
    this.switchText = ko.computed(function () {
        if (self.hide() == true) {
            return '+'
        }
        return '-';
    }, this);
}

function ViewModel() {
    var self = this;
    this.gpCourseProp = ko.observableArray();

self.gpCourseProp.push(new gpCourseProperties("MATH1030", true));
self.gpCourseProp.push(new gpCourseProperties("MATH1006", true));
self.gpCourseProp.push(new gpCourseProperties("MATH1046", true));

    for (i = 0; i < self.gpCourseProp().length; i++) {
    if (self.gpCourseProp()[i].gpCode == "MATH1030") {
        self.gpCourseProp()[i].addCourse(new course("1", "MATH1030", "Calculus", "City1"));
        self.gpCourseProp()[i].addCourse(new course("2", "MATH1030", "Calculus", "City2"));
        self.gpCourseProp()[i].addCourse(new course("3", "MATH1030", "Calculus", "City3"));
    }
    if (self.gpCourseProp()[i].gpCode == "MATH1006") {
        self.gpCourseProp()[i].addCourse(new course("4", "MATH1006", "Linear algebra", "City1"));
        self.gpCourseProp()[i].addCourse(new course("6", "MATH1006", "Linear algebra", "City2"));
    }
    if (self.gpCourseProp()[i].gpCode == "MATH1046") {
        self.gpCourseProp()[i].addCourse(new course("5", "MATH1046", "Discrete Math", "City1"));
        self.gpCourseProp()[i].addCourse(new course("7", "MATH1046", "Discrete Math", "City2"));
    }
    }
}

var vm = new ViewModel();
ko.applyBindings(vm);
    tr.mutated {
        display: none;
    }
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<table class="table">
    <thead>
        <tr>
            <th>ID</th>
            <th>Course Code</th>
            <th>Course Title</th>
            <th>Course Campus</th>
        </tr>
    </thead>
    <tbody data-bind="foreach: gpCourseProp">
        <tr class="table-dark">
            <td></td>
            <td><span data-bind="text: $data.gpCode"></span></td>
            <td><span></span></td>
            <td></td>
        </tr>
        <!-- ko foreach: $data.courses -->
        <tr data-bind="css: { mutated: $parent.mutated.hide() == true }">
            <td><span data-bind="text: $data.id"></span></td>
            <td><span data-bind="text: $data.courseCode"></span></td>
            <td><span data-bind="text: $data.courseTitle"></span></td>
            <td><span data-bind="text: $data.coursecampus"></span></td>
        </tr>
        <!-- /ko -->
    </tbody>
</table>

The issue i have is trying to add courses to the gpCourseProperties.


Solution

  • Main issues are:

    • When you initialize your data, you only push the arguments for gpCourseProperties to the observable array. You need to construct the viewmodel using new.
    • When you create the course content for your courseProperties, you check gpCode === "Some string". Since gpCode is observable, you need to extract its value using () for this to ever be true.

    Here's an updated version with the minimum changes required to make it work. (note, I removed the CSS that hid some of the rows for debug purposes).

    The fixes are marked in the comments.

    function course(_id, _code, _title, _campus) {
        var self = this;
        this.id = ko.observable(_id);
        this.courseCode = ko.observable(_code);
        this.courseTitle = ko.observable(_title);
        this.coursecampus = ko.observable(_campus);
    }
    
    function gpCourseProperties(_code, _isHidden) {
        var self = this;
        this.gpCode = ko.observable(_code);
        this.hide = ko.observable(_isHidden);
        this.courses = ko.observableArray();
    
        this.addCourse = function (_id, _courseCode, _courseTitle, _courseCampus) {
            self.courses.push(new course(_id, _courseCode, _courseTitle, _courseCampus));
        }
    
        this.switchMutated = function (code) {
            self.hide(!self.hide());
        };
        this.switchText = ko.computed(function () {
            if (self.hide() == true) {
                return '+'
            }
            return '-';
        }, this);
    }
    
    function ViewModel() {
        var self = this;
        this.gpCourseProp = ko.observableArray();
    
        // Fix 1:
        //                     vvvvvvvvvvvvvvvvvvvvvvv                v
        self.gpCourseProp.push(new gpCourseProperties("MATH1030", true));
        self.gpCourseProp.push(new gpCourseProperties("MATH1006", true));
        self.gpCourseProp.push(new gpCourseProperties("MATH1046", true));
    
        for (i = 0; i < self.gpCourseProp().length; i++) {
            // Fix 2:
            //                               vv
            if (self.gpCourseProp()[i].gpCode() == "MATH1030") {
                self.gpCourseProp()[i].addCourse("1", "MATH1030", "Calculus", "City1");
                self.gpCourseProp()[i].addCourse("2", "MATH1030", "Calculus", "City2");
                self.gpCourseProp()[i].addCourse("3", "MATH1030", "Calculus", "City3");
            }
            if (self.gpCourseProp()[i].gpCode() == "MATH1006") {
                self.gpCourseProp()[i].addCourse("4", "MATH1006", "Linear algebra", "City1");
                self.gpCourseProp()[i].addCourse("6", "MATH1006", "Linear algebra", "City2");
            }
            if (self.gpCourseProp()[i].gpCode() == "MATH1046") {
                self.gpCourseProp()[i].addCourse("5", "MATH1046", "Discrete Math", "City1");
                self.gpCourseProp()[i].addCourse("7", "MATH1046", "Discrete Math", "City2");
            }
        }
    }
    
    var vm = new ViewModel();
    ko.applyBindings(vm);
    <script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
    <table class="table">
        <thead>
            <tr>
                <th>ID</th>
                <th>Course Code</th>
                <th>Course Title</th>
                <th>Course Campus</th>
            </tr>
        </thead>
        <tbody data-bind="foreach: gpCourseProp">
            <tr class="table-dark">
                <td></td>
                <td><span data-bind="text: gpCode"></span></td>
                <td><span></span></td>
                <td></td>
            </tr>
            <!-- ko foreach: courses -->
            <tr data-bind="css: { mutated: $parent.hide() == true }">
                <td><span data-bind="text: id"></span></td>
                <td><span data-bind="text: courseCode"></span></td>
                <td><span data-bind="text: courseTitle"></span></td>
                <td><span data-bind="text: coursecampus"></span></td>
            </tr>
            <!-- /ko -->
        </tbody>
    </table>