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
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
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.