Knockout change event-handler

michaeldd picture michaeldd · Mar 21, 2014 · Viewed 22k times · Source

I am spending hours trying to get a simple event call working correctly in my durandal/knockout app.

Context

I have a list of languages that that the user can select from a select-box:

    <select class="form-control select2"
        data-bind="event: { change: app.languageChanged }, options:languages,
        optionsText:'label',
        optionsValue:'code',
        value:app.selectedLanguage"></select>

The property app.selectedLanguage is a ko.observable. I know that this works because the correct item gets pre-selected.

    this.selectedLanguage = ko.observable(options.defaultLanguage);

I also have an event-handler which listens for changes on that select box, so that I can send a message to other parts of the application that need to be informed:

    languageChanged : function(data, event) {
        console.log(data);
        console.log(event);
        console.log(this.selectedLanguage());

        app.trigger('language:change', this.selectedLanguage());
    },

The problem

  1. the first parameter 'data' does not contain the selected item, but instead contains all items (actually, it seems to be the complete current view-model).
  2. If 1. does not work, then it would be an alternative to at least get hold of the new value from the observable 'selectedLanguage'. Unfortunately that always seems to have the old value. So whenever I change the selectbox option, I always get the previously selected value.

Question

So the question is: what could I be doing wrong? I am sure that this normally works correctly and I must be missing something somewhere.

I thought I had finally understood how knockout works, but now I have come across the next issue. I would be very grateful if someone could help me on this.

EDIT [SOLVED]

Thanks to xdumaine, here is the (nice and simple) solution:

In my html template, I removed the change-event:

    <select class="form-control select2"
        data-bind="options:languages,
        optionsText:'label',
        optionsValue:'code',
        value:app.selectedLanguage"></select>

In my App view-model (that I require everywhere), I now subscribe to the ko.observable instead of listening to the event-handler:

    define([ 'durandal/app', 'underscore', 'knockout', 'myapp/myapp' ], function(app, _, ko, myapp) {

        "use strict";

        function App(options) {

            if (!(this instanceof App)) {
                throw new TypeError("App constructor cannot be called as a function.");
            }

            this.options = options || {};

            // Set the initial language.
            this.selectedLanguage = ko.observable(options.defaultLanguage);
                    // *** Subscribes to the observable ***
            this.selectedLanguage.subscribe(function(newValue) {
                console.log(newValue);
                app.trigger('language:change', newValue);
            });

            _.bindAll(this, 'getSelectedLanguage');
        }

        App.prototype = {
            constructor : App,
            getSelectedLanguage : function() {
                return this.selectedLanguage();
            }
        }

        return App;
    });

This code has therefore been removed and is no longer needed:

languageChanged : function(data, event) {
    console.log(data);
    console.log(event);
    console.log(this.selectedLanguage());

    app.trigger('language:change', this.selectedLanguage());
},

Best regards, Michael

Answer

xdumaine picture xdumaine · Mar 21, 2014

Why bind to the select change event instead of just subscribing to the selectedLanguage?

var self = this;
self.selectedLanguage = ko.observable();
self.selectedLangauge.subscribe(function(newValue) {
    console.log(newValue);
    app.trigger('language:change', newValue);
});

If you want to do it like you have it, know this: event bindings in knockout always get a reference to the viewModel as the first parameter, and the event data as the second, so you could would have to inspect the event to get the target and extract the value if you're doing it that way. The reason 2 is not working is that your change event is firing before the knockout observable is notified, so you get timing issues. This could have different behavior in different browsers.

I'd recommend sticking to observable subscriptions, instead of using DOM events, whenever possible.