devC
devC

Reputation: 1444

Issue with knockout subscribe

Hell All,

I have bit of a complex knockout viewmodel with child models. Following is the viewmodel definition:

var DailyItems = function (data) {
var p = this;
this.Date = ko.observable(data.Date);
this.Required = ko.observable(data.Required);
this.SetupTime = ko.observable(data.SetupTime);
this.CloseTime = ko.observable(data.CloseTime);
this.MinHrsPerDay = ko.observable(data.MinHrsPerDay);
this.MaxHrsPerDay = ko.observable(data.MaxHrsPerDay);
this.WorkSegments = ko.observableArray([]);
var records = $.map(data.WorkSegments, function (x) { return new WorkShift(p, x) });
this.WorkSegments(records);

this.EnableAdd =  ko.computed(function () {
    return this.WorkSegments().length < 8;
}, this);

this.Add = function () {
    var data = {
        Parent: p,
        ID: "",
        Date: this.Date,
        Location: UNIT_ID,
        Role: "",
        EmployeeRoles: this.WorkSegments()[0].EmployeeRoles(),//add the roles of the first work segment
        ShiftStart: "",
        ShiftEnd: ""
    };
    var child = new WorkShift(p, data);
    this.WorkSegments.push(child);      
}

this.Delete = function (item) {
    this.WorkSegments.remove(item);
}
};

var WorkShift = function (parent, data) {
var self = this;
this.Parent = ko.observable(parent);
this.ID = ko.observable(data.ID);
this.Day = ko.observable(data.Day);
this.Location = ko.observable(data.Location);
this.ShiftStart = ko.observable(data.ShiftStart);
this.ShiftEnd = ko.observable(data.ShiftEnd);
this.EmployeeRoles = ko.observableArray(data.EmployeeRoles);

this.Location.subscribe(function (branchId) {
    $.ajax({
        type: "POST",
        url: SERVER_PATH + '/WebServices/AttributeService.asmx/GetDataOnLocationChange',
        data: "{" + "clientId: '" + CLIENT_ID
                    + "', unitId: '" + branchId
                    + "', effectiveDate:'" + EFFECTIVE_DATE
                    + "'}",
        contentType: "application/json; charset=utf-8",
        dataType: "json",
        success: function (res) {
            var d = JSON.parse(res.d);
            self.EmployeeRoles(d.Roles);

            var tasks = self.Parent().WorkSegments();
            //Requirement: for any day of the week, if there is more than one work segment
            //at different branches the required type should be set to 'On' and made disable
            if (tasks.length > 1) {
                for (var i = 0; i < tasks.length; i++) {
                    if ((d.IsSection == false && tasks[i].Location() != self.Location()) || (d.IsSection == true && self.ParentBranch() != tasks[i].ParentBranch())) {
                        self.Parent().Required('O');
                    }
                    else {
                        self.Parent().Required('E');
                    }
                }
            }
        },
        error: HandleLocationChangeError
    });
}.bind(this));

this.Role = ko.observable(data.Role);
}

What happens here is that the Location() observable in the DailyItems() is a value from a dropdownlist, which is populated through the main viewmodel. When the location changes, it should change the EmployeeRoles() observablearray according to the selected location, hence the Location.Subscribe method.

My problem is that this subscription method gets called even during the initial data load. So there's an unnecessary call to the server at the beginning. I only want the it to gets called when the user actually changes the dropdown selection.

What option do I have to achieve this?

Regards, Chathu

Upvotes: 2

Views: 1157

Answers (3)

Patrick Szalapski
Patrick Szalapski

Reputation: 9439

Could it be as simple as not running the AJAX call if it is the first change? It isn't pretty, but it is simple:

var locationInitialized = false;
this.Location.subscribe(function (branchId) {
    if (!locationInitialized) {
         locationInitialized = true;
         return;
    }
    $.ajax({
    ...

Of course, you could set locationInitialized in the initial population success function instead.

Alternatively, set up the function but don't subscribe it until the initial population is done. @explunit showed one way to do this.

Upvotes: 0

Steve P
Steve P

Reputation: 19377

Passing the data into the WorkShift constructor is what triggers the change event on the location observable. One approach is to move the .subscribe call to the parent object after the child is constructed and the observable has its initial value.

Your Add function would then look something like this:

this.Add = function () {
    var data = {
        // SNIP
    };
    var child = new WorkShift(p, data);

    child.subscribe(function (branchId) {
        $.ajax({ 
            // SNIP       
            });
    }.bind(child)); // <--- important to use child and not 'this'!!!

    this.WorkSegments.push(child);
};

You would then also have to use "this" instead of "self" inside the ajax call since you've bound the this context to child.

Upvotes: 1

devC
devC

Reputation: 1444

Following is my UI binding:

<tbody data-bind="foreach: WorkSegments">
                                            <tr>
                                                <td class="formFields">

                                                   ct class="combobox" data-bind="options:$root.WorkLocations, value:Location, optionsText: 'Name', optionsValue: 'ID', enable: LocationActive"

                                                        style="font-size: x-small; font-style: normal; font-family: Tahoma; width: 80px">
                                                    </select>
                                                </td>
                                            </tr>
                                        </tbody>

Here the dropdown options are set from the main viewmodel. After that the selected element is bound. After that when I debug the code at initial load, the Location subscribe method gets called.

Upvotes: 0

Related Questions