Search code examples
knockout.jsknockout-mapping-plugin

Why is my Knockout Mapping killing my $.change function?


I have the following Javascript and Html code that works fine in an MVC4 application.

@model string
<script src="@this.Url.Content("~/Scripts/jquery-1.7.1.min.js")" type="text/javascript"> </script>
<script src="@this.Url.Content("~/Scripts/jquery.signalR.js")" type="text/javascript"> </script>
<script src="@this.Url.Content("~/Scripts/knockout.js")" type="text/javascript"> </script>
<script src="@this.Url.Content("~/Scripts/knockout.mapping-latest.js")" type="text/javascript"> </script>
<script type="text/javascript" src="/signalr/hubs"> </script>
<script type="text/javascript">
    $(function() {
        function outingDataViewModel() {
            var self = this;
            self.hub = $.connection.outings;
            self.ID = ko.observable();
            self.OutingNumber = ko.observable();
            self.OutingName = ko.observable();
            //Initializes the view model
            self.init = function () {
                self.hub.getOuting('@this.Model');
            };
            self.hub.updateOuting = function (data) {                    
                self.ID(data.ID);
                self.OutingName(data.OutingName);
                self.OutingNumber(data.OutingNumber);
            };

            self.updateOuting = function () {
                var outing = { "ID": self.ID(), "OutingNumber": self.OutingNumber(), "OutingName": self.OutingName() };
                self.hub.saveOuting(outing);
            };
        }

        var vm = new outingDataViewModel();
        ko.applyBindings(vm);
        // Start the connection
        $.connection.hub.start(function () { vm.init(); });
        $('.data').change(function () { vm.updateOuting();} );
    });
</script>

<div  id="OutingSummary">
    <div data-bind="text:OutingNumber"></div>
    <input data-bind="value:OutingName" class="data"/>
</div>

But when I try to implement mapping by doing the following my $('.data').change(function () { vm.updateOuting();} ); never gets called. Chrome Developer tools don't seem to catch any errors and I am at a loss. The information gets binded to the html, just when I tab out of the text box nothing fires. (Same script references as before)

    $(function() {
            function outingDataViewModel() {
                var self = this;
                self.hub = $.connection.outings;
                self.Outing = ko.observable();
                //Initializes the view model
                self.init = function () {
                    self.hub.getOuting('@this.Model');
                };
                self.hub.updateOuting = function (data) {
                    self.Outing(ko.mapping.fromJS(data));
                };

                self.updateOuting = function () {
                    var outing = { "ID": self.Outing.ID(), "OutingNumber": self.Outing.OutingNumber(), "OutingName": self.Outing.OutingName() };
                    self.hub.saveOuting(outing);
                };
            }

            var vm = new outingDataViewModel();
            ko.applyBindings(vm);
            // Start the connection
            $.connection.hub.start(function () { vm.init(); });
            $('.data').change(function () { vm.updateOuting();} );
        });
    </script>

    <div data-bind="with:Outing" id="OutingSummary">
        <div data-bind="text:OutingNumber"></div>
        <input data-bind="value:OutingName" class="data"/>
    </div>

Solution

  • It's hard to tell exactly what the problem is, maybe a JSFiddle would help? Outing doesn't seem to get populated before you bind so at the moment you call applyBindings, Outing().OutingName is undefined, are you receiving any js errors in the console?

    As an aside, The knockout value binding internally uses the change event to update its observable. Therefore it is unnecessary to use Jquery change events in most cases. Since you are already value binding to OutingName why not replace your change() method with this inside your outingDataViewModel.

    self.Outing().OutingName.subscribe(function() { 
        self.updateOuting();
    });
    

    EDIT

    Here is a simplified version that works. I noticed that your updateOuting method wasn't calling the Outing observable before accessing it's name. This would cause an error typically. I didn't include the signal R stuff as I've never used it before.

    http://jsfiddle.net/madcapnmckay/gDu94/

    Hope this helps,