Question

I am working with the following objects/structure: Course, SubCategory, SubUniversity, Category, SubCategory, CourseSchedule.

A course can have one and only one subcategory, but can be a part of many subuniversities (hence the CourseSchedule object with one Course and one SubUniversity).

Each SubCategory has one parent Category; each SubUniversity has one parent University.

I have a courseadd view and a courseedit view. Once the Course object is created with the courseadd view, SubUniversities (via CourseSchedules) can be added on the courseedit view.

[Screenshot 0]

When I try to add SubUniversites, the first appears twice.

Screenshot 1

When I add subsequent SubUniversites, they appear correctly with the first still being duplicated.

[Screenshot 2]

Here is the View Code

<section id="course-edit" class="view">
    <h3 class="page-title" data-bind="text: title"></h3>
    <div class="button-bar">
        <button class="btn btn-info"
            data-bind="click: goBack"><i class="icon-hand-left"></i></button>
        <button class="btn btn-info"
            data-bind="click: cancel, enable: canSave"><i class="icon-undo"></i> Cancel</button>
        <button class="btn btn-info"
                data-bind="click: save, enable: canSave"><i class="icon-save"></i> Save</button>
        <button class="btn btn-danger"
                data-bind="click: deleteCourse, disable: hasChanges">
            <i class="icon-trash"></i> Delete
        </button>
        <i class="icon-asterisk" data-bind="visible: hasChanges"></i>
    </div>
    <div data-bind="with: course">
        <div>
            <label for="courseName">Name</label>
            <input id="courseName" data-bind="value: courseName" placeholder="Course Name" />
        </div>
        <div>
            <label for="category">Category</label>
            <select id="category" data-bind="options: $parent.subcategories, optionsText: 'subCategoryName', value: subCategory"></select>
        </div>
        <div>
            <label for="courseMaterialURL">Material URL</label>
            <input id="courseMaterialURL" data-bind="value: courseMaterialURL" placeholder="http://" />
        </div>
        <div>
            <label for="courseImageURL">Image URL</label>
            <input id="courseImageURL" data-bind="value: courseImageURL" placeholder="http://" />
        </div>
        <div>
            <label for="courseDescription">Description</label>
            <textarea id="courseDescription" data-bind="value: courseDescription" placeholder="Course Description" rows="4"></textarea>
        </div>
        <div style="width:600px">
            <div style="float:right">
                <label for="courseUniversity">&nbsp;</label>
                <section id="courseScheduleNode" class="view-list" data-bind="foreach: courseSchedules" >
                    <article>
                        <div>
                            <span style="margin-right: 10px" data-bind="text: subUniversity().subUniversityName"></span>
                            <button class="btn btn-danger" data-bind="click: $root.removeSubUniversity" style="float:right"><i class="icon-remove"></i></button>
                        </div>
                        <br />
                    </article>
                </section>
            </div>
            <div>
                <label for="courseUniversity">Add University</label>
                <select id="courseUniversity" data-bind="options: $parent.subuniversities, optionsText: 'subUniversityName', value: selectedSubUniversity, optionsCaption: ' '"></select>
                <button class="btn btn-success" data-bind="click: $parent.addSubUniversity"><i class="icon-ok"></i></button>
            </div>
        </div>
    </div>
</section>

This part of the viewmodel is the code for the add and remove onClick functions.

var addSubUniversity = function (selectedCourse) {
            if (selectedCourse) {
                var cs = datacontext.createCourseSchedule();
                cs.courseId(selectedCourse.id());
                cs.subUniversityId(selectedCourse.selectedSubUniversity().id());
                selectedCourse.courseSchedules.push(cs);
                save();
            }
        };

        var removeSubUniversity = function (selectedCourseSchedule) {
            if (selectedCourseSchedule) {
                selectedCourseSchedule.entityAspect.setDeleted();
                save().then(success).fail(failed).fin(finish);

                function success() {
                    inflateCourseSchedules();
                }

                function failed(error) {
                    cancel();
                    var errorMsg = 'Error: ' + error.message;
                    logger.logError(errorMsg, error, system.getModuleId(vm), true);
                }

                function finish() {

                }
            }
        };

The data is correct in the database, so this appears to be a knockout binding issue. What would cause the first value to bind twice?

Was it helpful?

Solution

Below is the key code. If subuniversity hides deleted rows. With subuniversity resolves the duplicate issue. The problem was caused by calling subuniversity().subuniversityname. The () broke the relationship between the bound item and the displayed item. When save was called the id was changed causing knockout to think it was a new item and bind it again causing the displayed collection to get out of sync with the databound collection.

<!-- ko if: subUniversity -->
<article>
    <div>
        <!-- ko with: subUniversity -->
        <span style="margin-right: 10px" data-bind="text: subUniversityName"></span>
        <!-- /ko -->
        <button class="btn btn-danger" data-bind="click: $root.removeSubUniversity" style="float:right"><i class="icon-remove"></i></button>
    </div>
    <br />
</article>
<!-- /ko -->

Below is some more useful code. The add function adds the item to the collection waiting for save to be called. Since you have the cancel button it seems like it would be good if it was honored. The delete function cancels the add if the item was added in this context. Otherwise it sets it as deleted. Thanks to the if statement in the markup deleted items disappear from the list.

var addSubUniversity = function (selectedCourse) {
    if (selectedCourse) {
        var cs = datacontext.createCourseSchedule();
        cs.courseId(selectedCourse.id());
        cs.subUniversityId(selectedCourse.selectedSubUniversity().id());
        selectedCourse.courseSchedules.push(cs);
    }
};

var removeSubUniversity = function (selectedCourseSchedule) {
    if (selectedCourseSchedule) {
        if (selectedCourseSchedule.entityAspect.entityState.isAdded()) {
            selectedCourseSchedule.entityAspect.rejectChanges();
            course().courseSchedules.remove(selectedCourseSchedule);
        }
        else
        {
            selectedCourseSchedule.entityAspect.setDeleted();
        }
    }
};
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top