Reputation: 6020
Here's an overview of my model structure in JSON form
var json = JSON.stringify([{
text: 'Enter your name',
controlType: 'TextBox',
answer: null
}, {
text: 'Choose some of these',
controlType: 'Label',
answer: null
}, {
text: 'Item one',
controlType: 'CheckBox',
answer: null
}, {
text: 'Item two',
controlType: 'CheckBox',
answer: null
}, {
text: 'Item three',
controlType: 'CheckBox',
answer: null
}, {
text: 'Choose from multiple elements',
controlType: 'Multiple',
answer: null
}, ]);
I have a list of questions (detailed above), each with its respective text
and controlType
properties. I've created a custom binding handler to draw the input on the form and another handler to process the answer
based on the DOM structure of the controls created.
For example, for one particular control type I have Multiple as a type, which will create the following markup:
<select data-bind="options: ['Jan','Feb','Mar'...], optionsCaption: ' - '"></select>
<input type="text" size="5" />
This essentially allows a user to submit an answer for a single question as Jan 2013 but I'm struggling to get the binding handler to work.
When I first started doing this, I extended each question with computed observables - so in this case I'd extend the question
with the following computeds:
question.answer.month = ko.computed({
read: function () {
if (this.answer()) {
return this.answer().split(' ')[0]; // returns the month part
}
return null;
},
write: function (value) {
if (value) {
this.answer(value);
}
},
owner: question
});
But I started getting into all sorts of circular reference hell - so I thought I'd take it up a level and set my answer
observable from the DOM elements instead.
Does anyone have any ideas / tips to get this working?
Further info
Seems I didn't explain well enough before, so I'll try a little harder.
Each of my question
objects has a property called controlType
. This denotes what DOM elements should be drawn on the page. So, for multiple (as an example) I should have the following DOM elements:
<div class="question">
<label class="question-label" data-bind="text: $data.text"></label>
<select data-bind="value: $data.answer.month, options: [month names]"></select>
<input type="text" data-bind="value: $data.answer.year" />
</div>
This will let a user answer a single question - given that the question text could be something like when were you born? then the above DOM structure allows them to submit an answer of month and year i.e. Jan 1990. Now, in the background I need to grab the values from these two DOM elements and merge them as a single answer to set the question.answer
property. I tried having two computed observables question.answer.month
and question.answer.year
but as my control types get more complicated I enter circular reference hell i.e. my answer is dependent on its counter-parts and each one the counter-parts are dependent on a parsed answer.
In short - I'm trying to set an observable's value based on an unspecified number of DOM elements, in this (the simplest) case it's a SELECT
and an INPUT
element.
Upvotes: 0
Views: 937
Reputation: 63699
This answer suggests an alternate solution.
First, replace the usage of the custom binding handlers by use of the template binding. Your templates would look like this:
<script type="text/html" id="Label">
<h4 data-bind="value: answer"></h4>
</script>
<script type="text/html" id="TextBox">
<input type="text" data-bind="value: answer" />
</script>
<script type="text/html" id="CheckBox">
<input type="checkbox" data-bind="checked: answer, processControl: $data" />
</script>
<script type="text/html" id="Multiple">
<select data-bind="value: answer, options: opts, optionsCaption: '-'"></select>
<input type="text" data-bind="value: answerAddition" />
</script>
This is probably the main issue and the main question here. Below are some thoughts on the "multiple" question type you have, but I strongly suggest working some more on that and if you're having trouble ask an isolated question about that, as SO tends to work best if you have one question at a time.
PS. See all this at work in this fiddle. Notice that jQuery is hardly used anymore, which usually is a good sign (at least a sign you're using KO the way it was meant to be used).
I think you may need to work some more on the "complex" / multiple question type you have there. The above is a step in the right direction IMO, requiring also some changes in your data and view model:
The data would contain the options, e.g.
opts: ['Jan', 'Feb', 'Mar' /*etc*/]
The view model would contain something like
self.opts = ko.observableArray(data.opts || []);
The view model would also contain the extra observable:
self.answerAddition = ko.observable('');
As well as possibly a read only computed that returs:
self.answer() + ' ' + self.answerAddition()
However, I think you'd benefit from taking this quite a few steps further, perhaps create some prototype-based inheritance and create a specialized Question constructor function to represent "month+year".
Upvotes: 1