In my view model I have a IsMale value that has the value true or false.
In my UI I wish to bind it to the following radio buttons:
<label>Male
<input type="radio" name="IsMale" value="true" data-bind="checked:IsMale"/>
</label>
<label>Female
<input type="radio" name="IsMale" value="false" data-bind="checked:IsMale"/>
</label>
The problem I think is checked expects a string "true" / "false". So my question is, how can I get this 2-way binding w/ this UI and model?
I know this is an old thread, but I was having the same problem and found out a much better solution that was probably added to knockout after this question was officially answered, so I'll just leave it for people with the same problem.
Currently there is no need for extenders, custom binding handlers or computeds.
Just provide a "checkedValue" option, it will use that instead of the html 'value' attribute, and with that you can pass any javascript value.
<input type="radio" name="a" data-bind="checked:IsChecked, checkedValue: true"/>
<input type="radio" name="a" data-bind="checked:IsChecked, checkedValue: false"/>
Or:
<input type="radio" name="b" data-bind="checked:Quantity, checkedValue: 1"/>
<input type="radio" name="b" data-bind="checked:Quantity, checkedValue: 2"/>
<input type="radio" name="b" data-bind="checked:Quantity, checkedValue: 3"/>
One option is to use a writeable computed observable.
In this case, I think that a nice option is to make the writeable computed observable a "sub-observable" of your IsMale observable. Your view model would look like:
var ViewModel = function() {
this.IsMale = ko.observable(true);
this.IsMale.ForEditing = ko.computed({
read: function() {
return this.IsMale().toString();
},
write: function(newValue) {
this.IsMale(newValue === "true");
},
owner: this
});
};
You would bind it in your UI like:
<label>Male
<input type="radio" name="IsMale" value="true" data-bind="checked:IsMale.ForEditing"/>
</label>
<label>Female
<input type="radio" name="IsMale" value="false" data-bind="checked:IsMale.ForEditing"/>
</label>
Sample: http://jsfiddle.net/rniemeyer/Pjdse/
This works for me:
http://jsfiddle.net/zrBuL/291/
<label>Male
<input type="radio" name="IsMale" value="1" data-bind="checked:IsMale"/>
</label>
<label>Female
<input type="radio" name="IsMale" value="0" data-bind="checked:IsMale"/>
</label>
ko.bindingHandlers['radiobuttonyesno'] = {
'init': function (element, valueAccessor, allBindingsAccessor) {
var stateHandler = function (property, allBindingsAccessor, key, value, checkIfDifferent) {
if (!property || !ko.isObservable(property)) {
var propWriters = allBindingsAccessor()['_ko_property_writers'];
if (propWriters && propWriters[key])
propWriters[key](value);
} else if (ko.isWriteableObservable(property) && (!checkIfDifferent || property.peek() !== value)) {
property(value);
}
};
var updateHandler = function () {
var valueToWrite;
if ((element.type == "radio") && (element.checked)) {
valueToWrite = element.value;
} else {
return; // "radiobuttonyesno" binding only responds to selected radio buttons
}
valueToWrite = (valueToWrite === "True") ? true : false;
var modelValue = valueAccessor(), unwrappedValue = ko.utils.unwrapObservable(modelValue); //can be true of false
stateHandler(modelValue, allBindingsAccessor, 'checked', valueToWrite, true);
};
ko.utils.registerEventHandler(element, "click", updateHandler);
// IE 6 won't allow radio buttons to be selected unless they have a name
if ((element.type == "radio") && !element.name)
ko.bindingHandlers['uniqueName']['init'](element, function () { return true });
},
'update': function (element, valueAccessor) {
var value = ko.utils.unwrapObservable(valueAccessor());
value = value ? "True" : "False";
if (element.type == "radio") {
element.checked = (element.value == value);
}
}
};
Use this binder instead of creating stupid ko computed observables.
Example:
<label>Male
<input type="radio" name="IsMale" value="True" data-bind="radiobuttonyesno:IsMale"/>
</label>
<label>Female
<input type="radio" name="IsMale" value="False" data-bind="radiobuttonyesno:IsMale"/>
</label>
Once you figure out that the initial match for the radio button wants to match only a string and wants to set the value to a string, it is simply a matter of converting your initial value to string. I had to fight this with Int values.
After you have setup your observables, convert the value to string and KO will do its magic from there. If you are mapping with individual lines, do the conversion in those lines.
In the example code, I'm using Json to map the whole Model in a single command.
Then letting Razor insert the value between the quotes for the conversion.
script type="text/javascript">
KoSetup.ViewModel = ko.mapping.fromJS(#Html.Raw(Json.Encode(Model)));
KoSetup.ViewModel.ManifestEntered("#Model.ManifestEntered"); //Bool
KoSetup.ViewModel.OrderStatusID("#Model.OrderStatusID"); //Int
</script>
I use a "Dump it all to the screen" at the bottom of my web page during development.
<h4>Debug</h4>
<pre data-bind="text: ko.toJSON($data, null, 2)"></pre>
Here are the data values, Before
"OrderStatusID": 6,
"ManifestEntered": true,
and, After
"OrderStatusID": "6",
"ManifestEntered": "True",
In my project, I didn't need to convert Bools, because I'm able to use a checkbox that doesn't have the same frustration.
Why not simply true and false instead of 1 and 0?
<label>Male
<input type="radio" name="IsMale" value="true" data-bind="checked:IsMale"/>
</label>
<label>Female
<input type="radio" name="IsMale" value="false" data-bind="checked:IsMale"/>
</label>
You can also use an extender so it's easy to reuse them for more observables:
ko.extenders.boolForEditing = function (target, allowNull) {
var result = ko.computed({
read: function () {
var current = target();
var newValue = null;
if (current === undefined || current === null || current === '') {
if (!allowNull) {
newValue = 'false';
}
} else {
newValue = current ? 'true' : 'false';
}
return newValue;
},
write: function (newValue) {
var current = target();
var valueToWrite = null;
if (newValue === undefined || newValue === null || newValue === '') {
if (!allowNull) {
valueToWrite = false;
}
} else {
valueToWrite = newValue === 'true';
}
// only write if it changed
if (valueToWrite !== current) {
target(valueToWrite);
} else {
if (newValue !== current) {
target.notifySubscribers(valueToWrite);
}
}
}
}).extend({
notify: 'always'
});
result(target());
return result;
};
Then use it like this:
this.IsMale.forEditing = this.IsMale.extend({boolForEditing: true});
The parameter provided to boolForEditing indicates whether the value may be null.
See http://jsfiddle.net/G8qs9/1/
After doing lot of research for older version of knockout prior to 3.0 there are possibly two best options
Create a knockout extender like
ko.extenders["booleanValue"] = function (target) {
target.formattedValue = ko.computed({
read: function () {
if (target() === true) return "True";
else if (target() === false) return "False";
},
write: function (newValue) {
if (newValue) {
if (newValue === "False") target(false);
else if (newValue === "True") target(true);
}
}
});
target.formattedValue(target());
return target;
};
To use the extender on your model, you’d do something like the following:
function Order() {
this.wantsFries= ko.observable(false).extend({ booleanValue: null });
}
<span>Do you want fries with that?</span>
<label>
<input type="radio" name="question" value="True"
data-bind="value: wantsFries.formattedValue" /> Yes
</label>
<label>
<input type="radio" name="question" value="False"
data-bind="value: wantsFries.formattedValue" /> No
</label>
source:http://www.timlabonne.com/2013/02/building-a-knockout-js-extender-for-boolean-values/
Related
I'm using vanilla js, and I'm stumped because there's very little code here, so I'm not sure where the problem lies. It may be a misunderstanding on my part on how the attribute works.
function changeState() {
const self = event.target
const parent = event.path[1]
if (self.type == "radio") {
console.log(self.id + " is " + self.checked)
}
}
<div id="usernames_buttons">
<input type="radio" name="usernames" id="usernames-bl" onclick="changeState()" checked>
<label for="usernames-bl">BL</label>
</input>
<input type="radio" name="usernames" id="usernames-wl" onclick="changeState()">
<label for="usernames-wl">WL</label>
</input>
<button data-toggle onclick="changeState()">OFF</button>
</div>
I paired everything down to just this code and ran it in a code pen to test, and the console.log will return true regardless of which option I am clicking. The expectation is that usernames-bl would return true and -wl would return false, but they return true whether the checked attribute is there or not.
You are invoking the changeState() on every click and i guess, as its a radio button, which will always give checked 'true' on click
I think you are doing it right minus the "Off" button should un-check both.. so I added this block:
if (self.type != "radio") {
document.getElementById("usernames-wl").checked = false;
document.getElementById("usernames-bl").checked = false;
}
function changeState() {
const self = event.target
const parent = event.path[1]
if (self.type != "radio") {
document.getElementById("usernames-wl").checked = false;
document.getElementById("usernames-bl").checked = false;
}
console.log("usernames-wl is " + document.getElementById("usernames-wl").checked)
console.log("usernames-bl is " + document.getElementById("usernames-bl").checked)
}
<div id="usernames_buttons">
<input type="radio" name="usernames" id="usernames-bl" onclick="changeState()" checked/>
<label for="usernames-bl">BL</label>
<input type="radio" name="usernames" id="usernames-wl" onclick="changeState()"/>
<label for="usernames-wl">WL</label>
<button data-toggle onclick="changeState()">OFF</button>
</div>
I have two input's(checkboxes). One is for a student and the other is for consultant. I want to require at least one or the other input checkboxes. I got them to switch if one is clicked. I just can't figure out how to get it to run inline with the rest of the form validation.
Student:
<input type="checkbox" class="userTypeClass" checked="checked" id="studentCheckbox" name="studentCheckbox" ng-true-value="1" ng-model="user.userType" ng-change="inputCheck(user)"/>
Consultant:
<input type="checkbox" class="userTypeClass" id="consultantCheckBox" name="consultantCheckBox" ng-true-value="2" ng-model="user.userType" ng-change="inputCheck(user)"/>
Signup Button:
<button class="col button-green" ng-disabled="signupForm.$invalid && !flag" ng-click="submit(user)">Continue</button>
JS Controller function:
$scope.inputCheck = function(user) {
var flag = false;
if (user.userType == false) {
flag = true;
} else {
flag = false;
}
console.log(flag);
};
There may be a better answer but here's an idea-
<input type="checkbox" class="userTypeClass" checked="checked" id="studentCheckbox" name="studentCheckbox" ng-true-value="1" ng-model="user.userType" ng-change="inputCheck(user)"/>
<input type="checkbox" class="userTypeClass" id="consultantCheckBox" name="consultantCheckBox" ng-true-value="2" ng-model="user.userType" ng-change="inputCheck(user)"/>
<input type="hidden" required ng-model="user.userType"/>
With the hidden input you're insuring at least one is checked. I don't know the purpose of your inputCheck method but here is a way it could be cleaned up-
$scope.inputCheck = function(user) {
var flag = user.userType === false;
console.log(flag);
};
// or if you dont need to store the flag:
$scope.inputCheck = function(user) {
console.log(user.userType === false);
};
Edit:
Hmm I didn't know false wouldn't work. You could try a hidden checkbox-
(untested, not sure if this will work)
<input type="checkbox" ng-hide="true" required ng-model="user.userType"/>
Or you can unset the value in inputCheck but it's a bit clunky.
$scope.inputCheck = function(user) {
if(user.userType === false) {
user.userType = undefined;
}
};
i'm rather new to js and i'd like to optimize my code.
I have a group of checkboxes and their boolean values are saved in an object for further calculations.
HTML:
<fieldset>
<input type="checkbox" id="checkbox1" onchange="checkbox1Changed()" value="checkbox1">
<input type="checkbox" id="checkbox2" onchange="checkbox2Changed()" value="checkbox2">
<input type="checkbox" id="checkbox3" onchange="checkbox3Changed()" value="checkbox3">
</fieldset>
JS:
//store values for further computation
var boxValues = {
box1: false,
box2: false,
box3: false,
}
//get checkboxvalues from view
var checkbox1 = document.getElementById("checkbox1");
var checkbox2 = document.getElementById("checkbox2");
var checkbox3 = document.getElementById("checkbox3");
//update values in boxValues
function checkbox1Changed() {
if (checkbox1.checked) {
boxValues.box1 = true;
} else {
boxValues.box1 = false;
}
}
function checkbox2Changed() {
if (checkbox2.checked) {
boxValues.box2 = true;
} else {
boxValues.box2 = false;
}
}
function checkbox3Changed() {
if (checkbox3.checked) {
boxValues.box3 = true;
} else {
boxValues.box3 = false;
}
}
Since i plan on having approximately 20 checkboxes in the view there would be a lot of repeating code.
Does anyone know a smarter way to do that?
Thanks in advance!
Vin
Add common class to all the checkboxes
Create an object for the values of all checkboxes
Bind event handler on the checkboxes using the common class
Update the status of clicked checkbox in event handler
Also, it is good practice to bind events in javascript instead of inline in the HTML.
var myObj = {
checkbox1: false,
checkbox2: false,
checkbox3: false
};
$('.myCheckbox').on('change', function() {
var thisId = $(this).attr('id');
myObj[thisId] = this.checked;
console.log(myObj);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.10.0/jquery.min.js"></script>
<fieldset>
<input type="checkbox" id="checkbox1" value="checkbox1" class="myCheckbox">
<input type="checkbox" id="checkbox2" value="checkbox2" class="myCheckbox">
<input type="checkbox" id="checkbox3" value="checkbox3" class="myCheckbox">
</fieldset>
You can bind the same function to every checkbox, and use the id of the checkbox as the key in your object:
function onCheckBoxChanged(e){
var sender = e.target;
boxValues[sender.id] = (sender.checked);
}
Playing around with this should save you a lot of typing :)
thank you for looking into this.
I have the following example built: http://jsfiddle.net/zm381qjx/5/
This is a menu list builder. When you add a menu, an edit form pops up. Using protectedObservable so that i can either commit or reset (as per code). One functionality, which i am having problems with, is there is radio button list (for TypeId), and depending on the value (10 = Url, 20 = Category, 30 = Page), you set the respective properties (10 = Url, 20 = CategoryId, 30 = PageId).
Flicking through the radio buttons, if Url is selected, another textbox should show (based on urlVisible) so user can enter the Url. I have added a span with text: TypeId.temp so i can see the temporary value. This is very irregular. Try to flick through several times.
Any help will be greatly appreciated.
My HTML
<a class="btn btn-primary" data-bind="click: addMenu">Add Menu</a>
<ul data-bind="foreach: Menus">
<li></li>
</ul>
<div class="panel panel-default" data-bind="slideIn: editMenuItem, with: editMenuItem">
<div class="panel-body">
<div class="form-group">
<label for="MenuName">Name: </label>
<input type="text" id="MenuName" data-bind="value: Name" class="form-control" />
</div>
<label class="radio-inline">
<input type="radio" name="MenuTypeId" value="10" data-bind="checked: TypeId" /> Url
</label>
<label class="radio-inline">
<input type="radio" name="MenuTypeId" value="20" data-bind="checked: TypeId" /> Category
</label>
<label class="radio-inline">
<input type="radio" name="MenuTypeId" value="30" data-bind="checked: TypeId" /> Page
</label>
<div class="form-group" data-bind="visible: urlVisible">
<label for="MenuUrl">Url: </label>
<input type="text" id="MenuUrl" data-bind="value: Url" class="form-control" />
</div>
<br />
<p>TypeId.temp = <span data-bind="text: TypeId.temp"></span></p>
<br /><br />
<input type="button" class="btn btn-success" value="Update" data-bind="click: commit" /> or
Cancel
</div>
</div>
My JS:
var vm = null;
//wrapper for an observable that protects value until committed
ko.protectedObservable = function (initialValue) {
//private variables
var _temp = ko.observable(initialValue);
var _actual = ko.observable(initialValue);
var result = ko.dependentObservable({
read: function () {
return _actual();
},
write: function (newValue) {
_temp(newValue);
}
});
//commit the temporary value to our observable, if it is different
result.commit = function () {
var temp = _temp();
if (temp !== _actual()) {
_actual(temp);
}
};
//notify subscribers to update their value with the original
result.reset = function () {
_actual.valueHasMutated();
_temp(_actual());
};
result.temp = _temp;
return result;
};
ko.bindingHandlers.slideIn = {
init: function (element) {
$(element).hide();
},
update: function (element, valueAccessor) {
var value = ko.utils.unwrapObservable(valueAccessor());
if (value) {
$(element).stop().hide().slideDown('fast');
} else {
$(element).stop().slideUp('fast');
}
}
};
var Menu = function (Id, Name, TypeId, CategoryId, PageId, Url) {
var self = this;
/* Core Properties */
self.Id = ko.observable(Id);
self.Name = ko.protectedObservable(Name);
self.TypeId = ko.protectedObservable(TypeId);
self.CategoryId = ko.protectedObservable(CategoryId);
self.PageId = ko.protectedObservable(PageId);
self.Url = ko.protectedObservable(Url);
/* Virtual Properties */
self.urlVisible = ko.computed(function () {
return self.TypeId.temp() == "10";
}, self);
/* Virtual Functions */
self.editMenu = function (data) {
if(vm.editMenuItem()) {
vm.editMenuItem(null);
}
vm.editMenuItem(data);
};
/* Core Functions */
self.commit = function () {
if (self.Name.temp() == '' || self.Name.temp() == null) {
alert('Please enter a name.'); return;
}
self.Name.commit();
self.TypeId.commit();
self.CategoryId.commit();
self.PageId.commit();
self.Url.commit();
vm.editMenuItem(null);
};
self.reset = function () {
self.Name.reset();
self.TypeId.reset();
self.CategoryId.reset();
self.PageId.reset();
self.Url.reset();
vm.editMenuItem(null);
};
};
var ViewModel = function() {
var self = this;
/* Core Properties */
self.Menus = ko.observableArray([]);
/* Virtual Properties */
self.editMenuItem = ko.observable(null);
self.addMenu = function(){
var menu = new Menu(0, "New Menu", "10", 0, 0, "");
self.Menus.push(menu);
self.editMenuItem(menu);
};
};
$(function () {
vm = new ViewModel();
ko.applyBindings(vm);
});
If you change your radio button binding to
<input type="radio" name="MenuTypeId" value="10" data-bind="checked: TypeId.temp" />
The temp id will be changed accordingly and radio button behaviour is consistent, but not with TypeId as value.
also the protectedObservable binding the radio button value is not playing nice
When you manually click the radio the TypeId value is never changed (as you are not committing the value) and I guess that as the radio button value never changes from 10 , it is not recognizing the subsequent manual clicks on Url radio button.
I updated the value using a button and it is changing accordingly; but then it will not move the value from that TypeId on subsequent radio button clicks
And the problem is still appearing for protectedObservable binding but not with a simple observable.
Code which explores this idea further: http://jsfiddle.net/zm381qjx/101/
Utilizing ASP with DotNetNuke to loop through listing of repeating radio buttons.
Utilizing JQuery
Currently displaying proper result value for weightCalculations function (ex: 3)
How do I combine the rbtCalculations results with the weightCalculations results?
Ex: IF rbtCalculations = Very High AND weightCalculations = High THEN
$('p.custom' + ID).text("5");
<input id="rbt_0" name="rbt" value="Very High" checked="checked" onclick="rbtCalculations(this,6559);" type="radio">
<input id="rbt_1" name="rbt" value="High" onclick="rbtCalculations(this,6559);" type="radio">
<input id="stakeholders_rbt_0" name="stakeholders_rbt" value="Very High" onclick="weightCalculations(this,6559);" type="radio">
<input id="stakeholders_rbt_1" name="stakeholders_rbt" value="High" checked="checked" onclick="weightCalculations(this,6559);" type="radio">
<input id="stakeholders_rbt_2" name="stakeholders_rbt" value="Low to Moderate" onclick="weightCalculations(this,6559);" type="radio">
<p class="custom6559">3</p>
<script type="text/javascript">
function weightCalculations(value, ID) {
if (value.value == "High") {
$('p.custom' + ID).text("3");
}
else {
$('p.custom' + ID).text("2");
}
}
I'd probably just add a class to the radio buttons to identify them and a wrapping element to associate all the parts together:
<div class="js-weight-calc-wrap">
<input type="radio" class="js-rbt" ... />
<input type="radio" class="js-rbt" ... />
<input type="radio" class="js-weight" ... />
<input type="radio" class="js-weight" ... />
<input type="radio" class="js-weight" ... />
<p class="js-result custom6559"></p>
</div>
<script>
jQuery(document).ready(function ($) {
$('.js-rbt, .js-weight').change(function () {
var $this = $(this),
$wrap = $this.closest('.js-weight-calc-wrap'),
rbtVal = $wrap.find('.js-rbt:checked').val(),
weightVal = $wrap.find('.js-weight:checked').val(),
result = rbtVal === 'VeryHigh' && weightVal === 'High'
? '5'
: rbtVal === 'VeryHigh' && weightVal === 'Low'
? '4'
: '0';
$wrap.find('.js-result').text(result)
});
});
</script>
I'd also probably end up create a jQuery plugin to contain all of that logic, so on your page it'd just be a call like this:
jQuery(function ($) {
$('.js-weight-calc-wrap').weightCalculator({
rbtSelector: '.js-rbt',
weightSelector: '.js-weight',
resultSelector: '.js-result'
});
});
UPDATE
I'd forgotten before that you need to filter the radio buttons when you select them, so that you get the checked one only (was confusing with a <select/> element). After adding :select to the selector, it works as expected. I cleaned it up a little more, and created a working jsFiddle.
function weightCalculations(value, ID) {
if (value.value === "High" && $('input[name="rbt"]').val() === "Very High") {
$('p.custom' + ID).text("5");
}
else if(value.value === "High"){
$('p.custom' + ID).text("3");
}
else {
$('p.custom' + ID).text("2");
}
}