Change event not binding correctly on second/multiple Child View Model with KnockoutMapping

60 views Asked by At

I'm working on an MVC/Entity Framework app (my first application, I'm primarily a SQL DBA/BI Developer) and I'm using knockout.js with the mapping plugin. I've got a view model with two child view models:

  1. EntityBridges - A list of bridge records relating the base entity to other entities (as children, a sort of object hierarchy)
  2. ExtProperties - holding a list of "additional properties" for the base entity.

What should render on the page is:

  1. A business entity/object with some properties attributed directly to this entity class
  2. A list of Additional Properties related to this entity by way of a bridge table
  3. A list of other entities which are child to this entity, again related by way of a bridge table

I'm running into a problem when trying to add: event: { change: flagExtPropertyAsEdited } to the second view model. I get this error:

Uncaught ReferenceError: Unable to process binding "foreach: function(){return ExtProperties }"

Message: Unable to process binding "event: function(){return { change:flagExtPropertyAsEdited} }"

Message: flagExtPropertyAsEdited is not defined

Some elements of the Child View-Models and their mappings must be working correctly, because without this change event I'm able to add new items to both view-models, and those additions make it all the way back thru the Entity Framework into my database. But I can't seem to assign the change event to more than one view model at a time.

What I've noticed is that if I swap the order of the mapping definitions in this section here:

EntityViewModel = function (data) {
    var self = this;
    ko.mapping.fromJS(data, entityBridgeMapping, self);
    ko.mapping.fromJS(data, extPropertyMapping, self);
The first of these two View Models populate correctly when the page renders, and the second fails with the error. I'm worried that I may be going about mapping the child view models back in the wrong way.

If it makes any difference, I've been using Adam Churvis's "Parent-Child Data with EF, MVC, Knockout, Ajax, and Validation" course in Pluralsight as a foundation because of its focus on Parent-Child Relationships.

This is my first question here, I hope I'm asking it clearly. I'm grateful for any feedback on my issue, or on the posting of my question itself.

Here's the section of my .js file that contains the view models:

var entityBridgeMapping = {
    'EntityBridges': {
        key: function (entityBridge) {
            return ko.utils.unwrapObservable(entityBridge.Id);
        },
        create: function (options) {
            return new EntityBridgeViewModel(options.data);
            
        }
    }
};


EntityBridgeViewModel = function (data) {
    var self = this;
    ko.mapping.fromJS(data, entityBridgeMapping, self);

    self.flagEntityBridgeAsEdited = function () {
        if (self.ObjectState() != ObjectState.Added) {
            self.ObjectState(ObjectState.Modified);
        }

        return true;
    };
};



var extPropertyMapping = {
    'ExtProperties': {
        key: function (extProperty) {
            return ko.utils.unwrapObservable(extProperty.Id);
        },
        create: function (options) {
            return new ExtPropertyViewModel(options.data);
        }
    }
};

ExtPropertyViewModel = function (data) {
    var self = this;
    ko.mapping.fromJS(data, extPropertyMapping, self);

    self.flagExtPropertyAsEdited = function () {
        if (self.ObjectState() != ObjectState.Added) {
            self.ObjectState(ObjectState.Modified);
        }
        return true;
    };

};




EntityViewModel = function (data) {
    var self = this;
    ko.mapping.fromJS(data, entityBridgeMapping, self);
    ko.mapping.fromJS(data, extPropertyMapping, self);


    self.save = function () {


        $.ajax({
            url: "/Admin/Entity/Save/",
            type: "POST",
            data: ko.toJSON(self),
            contentType: "application/json",
            success: function (data) {
                if (data.entityViewModel != null)
                    ko.mapping.fromJS(data.entityViewModel, {}, self);

                if (data.newLocation != null)
                    window.location = data.newLocation;
            }
        });
    },

    self.flagEntityAsEdited = function () {

        if (self.ObjectState() != ObjectState.Added) {
            //alert("flagged!");
            self.ObjectState(ObjectState.Modified);
        }
        return true;
    },

    self.addEntityBridge = function () {
        var entityBridge = new EntityBridgeViewModel({ Id: 0, ChildId: "", ObjectState: ObjectState.Added });
        self.EntityBridges.push(entityBridge);
    };


    self.deleteEntityBridge = function (entityBridge) {
        self.EntityBridges.remove(this);

        if (entityBridge.Id() > 0 && self.EntityBridgesToDelete.indexOf(entityBridge.Id()) == -1)
            self.EntityBridgesToDelete.push(entityBridge.Id());
    };



    self.addExtProperty = function () {
        var extProperty = new ExtPropertyViewModel({ Id: 0, ExtPropertyTypeId: 0, ExtPropertyValue: "", ObjectState: ObjectState.Added });
        self.ExtProperties.push(extProperty);
    };


};

And here are the two tables on my .cshtml file:

<table class="table table-striped">
    <tr>
        <th>Additional Properties</th>
        <th>Property Value</th>
        <th><button data-bind="click: addExtProperty" class="btn btn-info btn-sm">Add</button></th>
    </tr>
    <tbody data-bind="foreach: ExtProperties">
        <tr>
            <td class="form-group"><select class="form-control" data-bind="options: $parent.ExtPropertiesForSelection, optionsText: 'ItemListLabel', optionsValue: 'Id', optionsCaption: 'Pick An Extended Property', value: ExtPropertyTypeId , event: { change: flagExtPropertyAsEdited } "></select></td>
            <td> <input class="form-control" data-bind="value: ExtPropertyValue"/></td>
        </tr>
    </tbody>
</table>

<table class="table table-striped">
    <tr>
        <th>Child Entities</th>
        <th>Child ID</th>
        <th>Parent ID</th>
        <th><button data-bind="click: addEntityBridge" class="btn btn-info btn-sm">Add</button></th>
    </tr>
    <tbody data-bind="foreach: EntityBridges">
        <tr>
            <td class="form-group"><select class="form-control" data-bind="options: $parent.BridgeEntitiesForSelection, optionsText: 'ItemListLabel', optionsValue: 'Id', optionsCaption: 'Pick An Child Entity', value: ChildId, event: { change: flagEntityBridgeAsEdited } "></select></td>
            <td class="form-group"><button class="btn btn-danger btn-sm" data-bind="click: $parent.deleteEntityBridge">Delete</button></td>
        </tr>
    </tbody>
</table>

0

There are 0 answers