Given that I have a list of questions, with control types (i.e. text box, select list, check box etc.) in a model that is being built with ko.mapping from JSON retrieved via an asmx web method, how do I set the answer to a question based off multiple DOM elements?
Say I have the following question:
{
text:"This is the question text",
answer:null,
controlType:"picker"
}
Then I have created a picker template, that looks like this:
<select data-bind="options: ['Large','Medium','Small'], optionsCaption: 'Select size...'" />
<select data-bind="options: ['Black','Red','Green'], optionsCaption: 'Select colour...'" />
I need to set the answer to this question when (and only when) both SELECT elements have a value selected.
I also need to persist the answer, so if the question comes back from the server with the answer populated (saved form submissions etc.) the respective values are selected in the SELECT elements, i.e.:
{
text:"This is the question text",
answer:"Large Red",
controlType:"picker"
}
This would select [Large] and [Red] from the elements for me.
Here's what I've tried so far.
ko.computed on my observableEDIT: Here's a working jsFiddle for this idea.
I've looped around the questions in my view model and set a ko.computed on each one, that accesses the DOM via ID, so my template becomes:
<select data-bind="attr: { 'id':'leftOption' }, options: ['Large','Medium','Small'], optionsCaption: 'Select size...'" />
<select data-bind="attr: { 'id':'rightOption' }, options: ['Black','Red','Green'], optionsCaption: 'Select colour...'" />
And my computed value becomes:
question.computedAnswer = ko.computed({
read: function () {
return question.answer();
},
write: function (value) {
var left = $('#leftOption').val();
var right = $('#rightOption').val();
question.answer(left + ' ' + right);
}
});
This just screams oh god, please, no! at me though because I'm accessing the DOM in a very messy way (and having recently learned a good practice of not accessing the DOM like this, feels all kinds of wrong!!).
Caveat: I'm still getting to grips with custom handlers
To restrict access to the DOM via a binding handler, I created one for this purpose... but can't seem to get it working. This idea is in the provided jsFiddle but it's not working in that it's not doing anything!
Essentially I'm trying to do this in the handler:
ko.bindingHandlers.computedAnswer = {
update: function (element, value, all, model, context) {
switch (model.controlType()) {
case 'picker': {
var $parent = $(element).parent();
var first = $parent.find('select:first').val();
var second = $parent.find('select:last').val();
if (first && second) {
model.answer(first + ' ' + second);
} else {
model.answer(null);
}
}
}
}
};
And trying to activate this on my template like this:
<select data-bind="computedAnswer: $data, options: ['Large','Medium','Small'], optionsCaption: 'Select size...'" />
<select data-bind="computedAnswer: $data, options: ['Black','Red','Green'], optionsCaption: 'Select colour...'" />
Does anyone have any idea how to get this working?
The view is the wrong place to store data. It's also not very flexible. What if your picker questions stored their own options? That would not only allow each question to have different options, but a different number of options.
It also keeps data of of the view, and simplifies your HTML and viewmodel code, and keeps the DOM access out of the viewmodel without requiring a custom binding (not that you should avoid custom bindings at all costs, I just don't think this is a good case for them).
A picker question
}, {
text: "dependent on master question",
answer: null,
controlType: "picker",
pickers: [
{ name: "Size", options: ['Large', 'Medium', 'Small'], value: '' },
{ name: "Color", options: ['Red', 'Blue', 'Green'], value: '' }
]
}
View
<script type="text/html" id="picker">
<!-- ko foreach: pickers -->
<label data-bind="text: name"></label>
<select data-bind="options: options, value: value, optionsCaption: 'Choose...'"></select>
<!-- /ko -->
</script>
The computed answer
viewModel.questions().forEach(function (question) {
if (question.controlType() === 'picker') {
question.answer = ko.computed(function() {
var answers = [];
question.pickers().forEach(function(i) {
if (i.value())
answers.push(i.value());
});
return answers.length === question.pickers().length ?
answers.join(' ') : '';
});
}
});
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With