Reputation: 25551
I am using the Twitter Bootstrap button group along with Knockout. I feel like I am overlooking something very simple, but, I haven't been able to get the checked
binding working in this instance.
I have a jsFiddle reproducing the problem here: http://jsfiddle.net/n5SBa/.
Here is the code from the fiddle:
HTML
<div class="form-group">
<div class="btn-group" data-toggle="buttons">
<label class="btn btn-primary" data-bind="click: ClickScore.bind($data, '0'), css: { active: Score() == '0' }">
<input type="radio" name="score" value="0" data-bind="checked: Score" /> 0
</label>
<label class="btn btn-primary" data-bind="click: ClickScore.bind($data, '1'), css: { active: Score() == '1' }">
<input type="radio" name="score" value="1" data-bind="checked: Score" /> 1
</label>
<label class="btn btn-primary" data-bind="click: ClickScore.bind($data, '2'), css: { active: Score() == '2' }">
<input type="radio" name="score" value="2" data-bind="checked: Score" /> 2
</label>
<label class="btn btn-primary" data-bind="click: ClickScore.bind($data, '3'), css: { active: Score() == '3' }">
<input type="radio" name="score" value="3" data-bind="checked: Score" /> 3
</label>
<label class="btn btn-primary" data-bind="click: ClickScore.bind($data, '4'), css: { active: Score() == '4' }">
<input type="radio" name="score" value="4" data-bind="checked: Score" /> 4
</label>
<label class="btn btn-primary" data-bind="click: ClickScore.bind($data, '5'), css: { active: Score() == '5' }">
<input type="radio" name="score" value="5" data-bind="checked: Score" /> 5
</label>
</div>
</div>
<pre data-bind="text: ko.toJSON($data, null, 2)"></pre>
Javascript
function SurveyViewModel() {
var self = this;
self.Score = ko.observable();
//Events
self.ClickScore = function (score) {
self.Score(score);
};
//Computations
self.RecommendationLabel = ko.computed(function () {
if (self.Score() < 8) {
return "Some question?";
} else {
return "Some other question?";
}
});
self.DOMSelectedScore = ko.computed(function() {
if ($('input[name=score]:checked').val()) {
return $('input[name=score]:checked').val();
} else {
return 'no value!';
}
});
};
var surveyViewModel = new SurveyViewModel();
ko.applyBindings(surveyViewModel);
In the example, I'm unable to get the actual radio button selected in the DOM so that it can be properly submitted in my form.
Upvotes: 2
Views: 9067
Reputation: 89241
The DOMSelectedScore
computed observable doesn't reference any other observable, so it never gets recalculated.
self.DOMSelectedScore = ko.computed(function() {
self.Score(); // subscribe to Score, even if we don't use it.
var val = $('input[name=score]:checked').val();
return val || 'no value!';
});
Fixing that, it appears that the DOM isn't updated until after the function returns, so the value lags behind one click. To fix this, we need to delay until the DOM has been updated:
self.DOMSelectedScore = ko.computed(function() {
self.Score(); // subscribe to Score, even if we don't use it.
var val = $('input[name=score]:checked').val();
return val || 'no value!';
}).extend({throttle:1}); // Wait for the DOM update to complete
To simplify the binding of numbers with knockout, you could define an extension method for observables:
ko.observable.fn.asString = function () {
var source = this;
if (!source._asString) {
source._asString = ko.computed({
read: function () {
return String(source());
},
write: function (newValue) {
source(Number(newValue));
}
});
}
return source._asString;
};
And then
<input type="radio" name="score" value="2" data-bind="checked: Score.asString()"/>
Upvotes: 4
Reputation: 16688
The values of the checkboxes are strings ("0"
, "1"
, etc.), but you are setting the value of the observable to a number. The checked
binding uses strict equality when doing the comparison and doesn't consider the number 1
to equal the string "1"
.
You can fix this by setting the value of the observable to strings:
data-bind="click: ClickScore.bind($data, '1')"
http://jsfiddle.net/mbest/n5SBa/2/
Upvotes: 9