Checkboxes with non-boolean values underneath them

556 views
Skip to first unread message

luv2hike

unread,
Jun 23, 2011, 4:04:20 PM6/23/11
to KnockoutJS
I have a situation where the data model contains a field that can
either contain a string 0 or string 1 but is bound to a view element
that is a checkbox requiring a true/false for KO to work with it.
Obviously the logic to convert back & forth is a very simple equality
test, but just getting my feet wet into this powerful library, I'm not
sure where to do said conversion and also have the screen stay in sync
with the model using JQuery Mobile which requires the
checkboxradio('refresh') call to be made.
(see my similar threads on the latter issue as it pertains to radio
buttons which has been solved)

I've put together a working example and pasted it below, but it
requires much manual intervention to draw the screen and has lots of
repetitive code since there are actually lots of these checkbox
fields. I'm just showing one here to save space. However, my
solution has to repeat the block of script for each 0/1 field. There
must be a more automated way to do this with KnockoutJS binding
handlers or subscribe, or something I've not even thought of?

var viewModel = {
parent: ko.observable({
child: ko.observable({
bigCheck:ko.observable("0")
})
})
};

// this block must be repeated and updated for each of these type
fields in the data model!
viewModel.bigCheckBool = ko.dependentObservable({
read: function() {
// convert to boolean for screen checkbox
return (this.parent().child().bigCheck() == '1');
},
write: function(value) {
// convert back to string [0,1] for underlying data
this.parent().child().bigCheck(value ? '1' : '0');
// relationships
this.parent().child().fieldOne(1);
this.parent().child().fieldTwo(0);
this.parent().child().fieldThree(0);
// update radios (only needed since we're not using actual
binding per the radios jqmChecked method)
$('input:radio[name*="pageone"]').attr('disabled',
value).checkboxradio("refresh");
},
owner: viewModel
});


<input type="checkbox" name="bigCheck" id="bc" data-bind="checked:
bigCheckBool" />
<label for="bc">Big Checkbox</label>

<fieldset data-role="controlgroup">
<legend><b>Field 1:</b></legend>

<input type="radio" name="fieldOne" id="carry-radio-choice-6.5"
value="0" data-bind="jqmChecked: parent().child().fieldOne"/>
<label for="carry-radio-choice-6.5">0</label>

<input type="radio" name="fieldOne" id="carry-radio-choice-6.4"
value="1" data-bind="jqmChecked: parent().child().fieldOne"/>
<label for="carry-radio-choice-6.4">1</label>

<input type="radio" name="fieldOne" id="carry-radio-choice-6.3"
value="2" data-bind="jqmChecked: parent().child().fieldOne"/>
<label for="carry-radio-choice-6.3">2</label>

<input type="radio" name="fieldOne" id="carry-radio-choice-6.2"
value="3" data-bind="jqmChecked: parent().child().fieldOne"/>
<label for="carry-radio-choice-6.2">3</label>
</fieldset>

<fieldset data-role="controlgroup">
<legend><b>Field 2:</b></legend>

<input type="radio" name="fieldTwo" id="carry-radio-choice-2.3"
value="0" data-bind="jqmChecked: parent().child().fieldTwo" />
<label for="carry-radio-choice-2.3">First choice</label>

<input type="radio" name="fieldTwo" id="carry-radio-choice-2.2"
value="1" data-bind="jqmChecked: parent().child().fieldTwo" />
<label for="carry-radio-choice-2.2">Second choice</label>
</fieldset>

<fieldset data-role="controlgroup">
<legend><b>Field 3:</b></legend>

<input type="radio" name="fieldThree" id="carry-radio-choice-2.3"
value="0" data-bind="jqmChecked: parent().child().fieldThree" />
<label for="carry-radio-choice-2.3">First choice</label>

<input type="radio" name="fieldThree" id="carry-radio-choice-2.2"
value="1" data-bind="jqmChecked: parent().child().fieldThree" />
<label for="carry-radio-choice-2.2">Second choice</label>
</fieldset>

luv2hike

unread,
Jun 23, 2011, 4:10:44 PM6/23/11
to KnockoutJS
One other thing related to the above post and possibly caused by my
messy solution: All the radio buttons need to be disabled when the
checkbox is checked. I tried the following:

<input type="radio" name="fieldOne" id="foot-radio-choice-1.3"
value="1" data-bind="jqmChecked: parent().child().fieldOne, disabled:
bigCheckBool" />

but it did not work. Hence the need for the:

attr('disabled', value)

in my posted code. While it works, it seems wrong to mix so much of
the view into my model.

luv2hike

unread,
Jun 23, 2011, 5:08:29 PM6/23/11
to KnockoutJS
Okay, unless someone has a better "KnockoutJS" way of doing it to
teach a newbie like me, I have a cleaner solution that works, though I
STILL have to incorporate view logic into the model script to disable
the radios when the checkbox is checked for some reason. I'd love to
know a solution that doesn't require this. But I now use a regular
checkbox with the jqmChecked binding handler as discussed earlier,
then use the subscribe to handle the inter-field relationships which
was also discussed in another thread. The difference now is I convert
the data upon loading and before saving so the model always uses
booleans for the checkboxes but still handles and supplies 0 or 1
strings for communicating with the existing back-end.

var viewModel = {
parent: ko.observable({
child: ko.observable({
bigCheck:ko.observable(false),
fieldOne:ko.observable(""),
fieldTwo:ko.observable(""),
fieldThree:ko.observable("")
})
})
};

// checkbox & radio buttons handler
ko.bindingHandlers.jqmChecked = {
init: ko.bindingHandlers.checked.init,
update: function(element, valueAccessor) {
ko.bindingHandlers.checked.update(element, valueAccessor);
$(element).checkboxradio("refresh");
}
};

// relationships
viewModel.parent().child().bigCheck.subscribe(function(newValue) {
if(newValue) {
viewModel.parent().child().fieldOne(1);
viewModel.parent().child().fieldTwo(0);
viewModel.parent().child().fieldThree(0);
}
// need to update radios for some reason
$('input:radio[name*=field]').attr('disabled',
newValue).checkboxradio("refresh");
});

// then where data is loaded via Ajax
viewModel.parent().child().bigCheck(data.subdata.bigCheck=='1'?
true:false);

// and where it is prepped to save back to the server
var tmp = ko.toJSON(viewModel.parent().child);
tmp.bigCheck = bigCheck?'1':'0';

============

<input type="checkbox" name="bigCheck" id="bc" data-bind="jqmChecked:
parent().child().bigCheck" />
<label for="bc">Big Checkbox</label>

<fieldset data-role="controlgroup">
<legend><b>Field 1:</b></legend>

<!-- the disabled binding does not work here even when applied to
all -->
<input type="radio" name="fieldOne" id="carry-radio-choice-6.5"
value="0" data-bind="jqmChecked: parent().child().fieldOne, disabled:
parent().child().bigCheck" />

rpn

unread,
Jun 23, 2011, 5:28:34 PM6/23/11
to knock...@googlegroups.com
Hello-
I did not quite digest all of your code yet, but seems reasonable to convert the value back and forth.  

I think that your disabled problem is that the binding is "disable" and not "disabled".   I think that anyone that has used the binding has probably got that wrong a time or two.

For the checkbox, what you did seems okay.  Another option would be to use a writable dependentObservable that you set via AJAX and send back to the server, while using a boolean for your checkbox.  Would be like:

var viewModel {
    checkedForBindingko.observable(false)    
};

viewModel.checked ko.dependentObservable({
    readfunction({
        return this.checkedForBinding("1" "0";
    },
    writefunction(newValue{
       this.checkedForBinding(newValue === "1");
    },
    ownerviewModel 
});

You can then subscribe to either one for doing the defaulting of the other fields.

Brad Grant

unread,
Jun 23, 2011, 5:36:13 PM6/23/11
to knock...@googlegroups.com
Thanks again!  So it is encouraging that I may be on the right track with what I've done.  If you get a chance to look over my last code post, please feel free to correct me where I may be misguided.  KnockoutJS is powerful but will take some practice to become proficient.  Also, thank you for setting me straight on the disable vs. disabled.  Seems like I may have seen it with the 'd' on the end in one of the doc pages or live examples?

rpn

unread,
Jun 23, 2011, 5:47:54 PM6/23/11
to knock...@googlegroups.com
Seems like it could be a jQuery mobile thing with the refresh.  Try putting the disable binding before your jqmChecked binding, just for a test.  That way the refresh will get called after the disable has run.

Brad Grant

unread,
Jun 23, 2011, 5:52:11 PM6/23/11
to knock...@googlegroups.com
You sir, are a genius!  That did fix it.  Well, now we know something about KO with JQM.
Reply all
Reply to author
Forward
0 new messages