knockout checked binding - only one checked - javascript

I have a list of Admins with a check box. I want to be able to select only one Admin.
HTML:
<tbody data-bind="foreach: people">
<tr>
<td>
<input type="checkbox" data-bind="attr: { value: id }, checked: $root.selectedAdmin">
<span data-bind="text: name"/>
</td>
</tr>
</tbody
JS:
function Admin(id, name) {
this.id = id;
this.name = name;
}
var listOfAdmin = [
new Admin(10, 'Wendy'),
new Admin(20, 'Rishi'),
new Admin(30, 'Christian')];
var viewModel = {
people: ko.observableArray(listOfAdmin),
selectedAdmin: ko.observableArray()
};
ko.applyBindings(viewModel);
For Example if Admin id 10 is selected the other admins should be deselected.
Is that Possible to do with Knockout?

You should really use radio buttons if you only want to allow multiple selection.
However if you still want to use checkboxes then on solution would be to combine the checked and the click binding:
Use the checked to check only when the current id equal to the selectedAdmin property and use the click binding to set the selectedAdmin.
So you HTML should look like this:
<input type="checkbox" data-bind="attr: { value: id },
checked: $root.selectedAdmin() == id,
click: $parent.select.bind($parent)" />
And in your view model you just need to implement the select function:
var viewModel = {
people: ko.observableArray(listOfAdmin),
selectedAdmin: ko.observableArray(),
select: function(data) {
this.selectedAdmin(data.id);
return true;
}
};
Demo JSFiddle.
Notes:
the return true; at the end of the select function. This is required to trigger the browser default behavior in this case to check the checkbox.
the .bind($parent) is needed to set the this in the select function to be the "parent" viewModel object.

Related

ng-change event firing only once inside ng-repeat

Here in my form, I am using two radio buttons and would like to check the selected values. But this does not work as expected. I would like to determine the individual radio selection value and display Big/Small in the Result column
<div ng-controller="CityCtrl">
<table>
<tr>
<th>Name</th>
<th>Big</th>
<th>Small</th>
<th>Result</th>
</tr>
<tr ng-repeat="city in cities">
<td>{{ city.name }}</td>
<td>
<input type="radio" name="{{city.name}}-{{$index}}" ng-model="big" value="big_value" ng-change="check(city.name, big)">
</td>
<td>
<input type="radio" name="{{city.name}}-{{$index}}" ng-model="small" value="small_value" ng-change="check(city.name, small)">
</td>
<td>***</td>
</tr>
</table>
</div>
Controller:
var myApp = angular.module('cityApp', []);
function CityCtrl($scope) {
$scope.big = '';
$scope.small = '';
$scope.cities = [{
id: 1,
name: 'Tokyo'
}, {
id: 2,
name: 'Guangzhou'
}, {
id: 3,
name: 'Seoul'
}];
$scope.check = function(city, value) {
console.log("City->" + city + " ::: Value->" + value);
};
}
From other answers I did infer a few things:
Understand Prototypical Inheritance in JS ?!
ng-repeat creates child scope that affects these radios inside ng-repeat
use $parent on ng-modal in the radio
I did played around, but couldn't make this work, I guess I am making some obvious mistake here.
Here is the fiddle:
http://jsfiddle.net/mw2us37h/
NOTE:
I am not using ng-value but passing a hardcoded value.
Changing to ng-click doesnt work either
Flagged this as a duplicate, but it seems like the solution is to use ng-click instead of ng-change when working with radio buttons in angularjs.
Here is an edit of your fiddle that works: http://jsfiddle.net/mw2us37h/1/
I'm not sure I understand what your trying. I think you like to display the radio button value in the result column and/or have a function on click where the value is passed.
Display the radio button value in result column
I would change the ngModel value so it binds to something city specific. So change:
<input type="radio" name="{{city.name}}-{{$index}}" ng-model="big" value="big_value">
to:
<input type="radio" name="{{city.name}}-{{$index}}" ng-model="city.size" value="big_value">
and
<input type="radio" name="{{city.name}}-{{$index}}" ng-model="small" value="small_value">
to
<input type="radio" name="{{city.name}}-{{$index}}" ng-model="city.size" value="small_value">
So the model points to the same variable city.size, this is where the selected value will be saved. Then change *** in the result column to city.size
See changes fiddle: http://jsfiddle.net/u2kxuyr7/
Get value on ngClick
Use ngChange as you allready do I would do it a bit different for simplicity. Change:
ng-change="check(city.name, whatEverValue)"
to:
ng-change="check(city)"
and the check function would look like:
$scope.check = function(city) {
console.log("City->" + city.name + " ::: Value->" + city.size);
};
See fiddle: http://jsfiddle.net/Lmszxj8s/
Then ngChange is fired on each change.

Remove items from Knockout observable array

I have the below structure for knockout model. It contains an observable array which in turn contains an object.
function ViewModel() {
var self = this;
self.newItem = ko.observable({
manufacturer: ko.observable(),
itemnumber: ko.observable(),
itemDescription: ko.observable()
});
self.AllItems = ko.observableArray();
self.addItem = function() {
self.newItem().manufacturer("test");
self.newItem().itemDescription("data");
self.AllItems.push(self.newItem);
};
self.removeItem = function(data) {
self.AllItems.remove(data);
};
}
First issue:Through this script I am entering a new itemnumber in the textbox and then clicking on add item to have the new item with the itemnumber from the textbox added to the observable array but when I change the item number and hit add it changes all the itemnumber inside the array. How can i have unique data inside the array.
Second issue: I need to remove the specific items from the array but it's not deleting it. Can someone please tell me how I can delete items from the observable array based on say the itemnumber property.
<input type="text" data-bind="value: newItem().itemnumber"/>
<div>
Items: <button data-bind="click: addItem">Add Item</button>
</div>
<div>
<table>
<tbody data-bind="template: { name: 'itemTemplate', foreach: AllItems }"></tbody>
</table>
</div>
<script type="text/html" id="itemTemplate">
<tr>
<td>
<input data-bind="value: itemnumber" />
Remove Item
</td>
</tr>
</script>
I have created this fiddle for quick view of the issue. Just started learning knockout so any help is appreciated.
http://jsfiddle.net/N3JaW/138/
Try the following for adding new item, which will solve your first issue:-
HTML code
<input type="text" id="textBox" data-bind="value : textBoxVal"/>
<div>
Items: <button data-bind="click: addItem">Add Item</button>
</div>
<div>
<table>
<tbody data-bind="template: { name: 'itemTemplate', foreach: AllItems }"></tbody>
</table>
</div>
<script type="text/html" id="itemTemplate">
<tr>
<td>
<input data-bind="value: itemnumber" />
Remove Item
</td>
</tr>
</script>
JS code:-
function ViewModel() {
var self = this;
self.newItem = ko.observable({
manufacturer: "",
itemnumber: "",
itemDescription: ""
});
self.textBoxVal = ko.observable();
self.AllItems = ko.observableArray();
self.addItem = function() {
self.newItem().manufacturer= "test";
self.newItem().itemDescription= "data";
self.newItem().itemnumber = self.textBoxVal();
self.AllItems.push(self.newItem);
};
self.removeItem = function(data) {
self.AllItems.remove(data);
};
}
$(document).ready(function() {ko.applyBindings(new ViewModel()); });
Your first issue was because, each time you are trying to add a new item, you were changing the value of itemNumber, which is an observable.
Observable value will be changed every where it is binded, when it's value is changed.
Instead you need to create new object and do push into the observableArray.
Refer doc to know more about observableArray.
For your second problem change removeItem as given below:-
self.removeItem = function(data) {
var dtIndex = self.AllItems.indexOf(data); //Get the index of the object you want to remove.
self.AllItems.splice(dtIndex, 1); //Then do splice
};
You can refer the above doc, to know how to use splice.
EDIT based on the suggestion in the comment :-
For working code of edited answer click here.
Hope this will solve your problem.

binding multi dropdown list in knockout.js

I have a multi dropdown list and I need to do the following:
1. Make sure that when selecting value in one dropdown list it won't appear in the others (couldn't find a proper solution here).
2. When selecting the value "Text" a text field (<input>) will apear instead of the Yes/no dropdown.
3. "Choose option" will appear only for the first row (still working on it).
4. Make sure that if "Text" is selected, it always will be on the top (still working on it).
JSFiddle
HTML:
<div class='liveExample'>
<table width='100%'>
<tbody data-bind='foreach: lines'>
<tr>
<td>
Choose option:
</td>
<td>
<select data-bind='options: filters, optionsText: "name", value: filterValue'> </select>
</td>
<td data-bind="with: filterValue">
<select data-bind='options: filterValues, optionsText: "name", value: "name"'> </select>
</td>
<td>
<button href='#' data-bind='click: $parent.removeFilter'>Remove</button>
</td>
</tr>
</tbody>
</table>
<button data-bind='click: addFilter'>Add Choice</button>
JAVASCRIPT:
var CartLine = function() {
var self = this;
self.filter = ko.observable();
self.filterValue = ko.observable();
// Whenever the filter changes, reset the value selection
self.filter.subscribe(function() {
self.filterValue(undefined);
});
};
var Cart = function() {
// Stores an array of filters
var self = this;
self.lines = ko.observableArray([new CartLine()]); // Put one line in by default
// Operations
self.addFilter = function() { self.lines.push(new CartLine()) };
self.removeFilter = function(line) { self.lines.remove(line) };
};
ko.applyBindings(new Cart());
I will appeaciate your assist here! Mainly for the first problem.
Thanks!
Mike
If you want to limit the options based on the options that are already selected in the UI, you'll need to make sure every cartLine gets its own array of filters. Let's pass it in the constructor like so:
var CartLine = function(availableFilters) {
var self = this;
self.availableFilters = availableFilters;
// Other code
// ...
};
You'll have to use this new viewmodel property instead of your global filters array:
<td>
<select data-bind='options: availableFilters,
optionsText: "name",
value: filterValue'> </select>
</td>
Now, we'll have to find out which filters are still available when creating a new cartLine instance. Cart manages all the lines, and has an addFilter function.
self.addFilter = function() {
var availableFilters = filters.filter(function(filter) {
return !self.lines().some(function(cartLine) {
var currentFilterValue = cartLine.filterValue();
return currentFilterValue &&
currentFilterValue.name === filter.name;
});
});
self.lines.push(new CartLine(availableFilters))
};
The new CartLine instance gets only the filter that aren't yet used in any other line. (Note: if you want to use Array.prototype.some in older browsers, you might need a polyfill)
The only thing that remains is more of an UX decision than a "coding decision": do you want users to be able to change previous "Choices" after having added a new one? If this is the case, you'll need to create computed availableFilters arrays rather than ordinary ones.
Here's a forked fiddle that contains the code I posted above: http://jsfiddle.net/ztwcqL69/ Note that you can create doubled choices, because choices remain editable after adding new ones. If you comment what the desired behavior would be, I can help you figure out how to do so. This might require some more drastic changes... The solution I provided is more of a pointer in the right direction.
Edit: I felt bad for not offering a final solution, so here's another approach:
If you want to update the availableFilters retrospectively, you can do so like this:
CartLines get a reference to their siblings (the other cart lines) and create a subscription to any changes via a ko.computed that uses siblings and their filterValue:
var CartLine = function(siblings) {
var self = this;
self.availableFilters = ko.computed(function() {
return filters.filter(function(filter) {
return !siblings()
.filter(function(cartLine) { return cartLine !== self })
.some(function(cartLine) {
var currentFilterValue = cartLine.filterValue();
return currentFilterValue &&
currentFilterValue.name === filter.name;
});
});
});
// Other code...
};
Create new cart lines like so: self.lines.push(new CartLine(self.lines)). Initiate with an empty array and push the first CartLine afterwards by using addFilter.
Concerning point 2: You can create a computed observable that sorts based on filterValue:
self.sortedLines = ko.computed(function() {
return self.lines().sort(function(lineA, lineB) {
if (lineA.filterValue() && lineA.filterValue().name === "Text") return -1;
if (lineB.filterValue() && lineB.filterValue().name === "Text") return 1;
return 0;
});
});
Point 3: Move it outside the foreach.
Point 4: Use an if binding:
<td data-bind="with: filterValue">
<!-- ko if: name === "Text" -->
<input type="text">
<!-- /ko -->
<!-- ko ifnot: name === "Text" -->
<select data-bind='options: filterValues, optionsText: "name", value: "name"'> </select>
<!-- /ko -->
<td>
Updated fiddle that contains this code: http://jsfiddle.net/z22m1798/

knockout.js checkboxes used to control enabled/disabled state of input fields

I am trying to bind a checkbox to each line in a list of objects, in a very similar fashion to a question asked/answered here: Binding a list of objects to a list of checkboxes
Essentially, as follows:
<ul data-bind="foreach: phones">
<li>
<input type='text' data-bind="attr: {value:phone}, disable: $root.selectedPhones"/>
<input type="checkbox" data-bind="attr: {value:id}, checked: $root.selectedPhones" />
</li>
</ul>
<hr/> selected phones:
<div data-bind="text: ko.toJSON($root.selectedPhones)"></div>
<hr/> phones:
<div data-bind="text: ko.toJSON($root.phones)"></div>
with js as follows:
function Phone(id,phone) {
this.id = id;
this.phone = phone;
}
var phones_list = [
new Phone(1, '11111'),
new Phone(2, '22222'),
new Phone(3, '33333')
];
var viewModel = {
phones: ko.observableArray(phones_list),
selectedPhones: ko.observableArray()
};
ko.applyBindings(viewModel);
The idea being that in the initial state, all of the input boxes are disabled and that clicking a checkbox will enable the input box in that row.
The data is coming from a fairly deeply nested object from the server-side so I'd like to avoid 'padding' the data with an additional boolean ie avoiding new Phone(1,'xx', false)
(a) because it's probably unnecessary (b) because the structure is almost certainly going to change...
Can the selectedPhones observable be used by the enable/disable functionality to control the status of fields in that 'row'?
Hope someone can help....
I have a jsfiddle here
You can create a small helper function which checks that a given id appers in the selectedPhones:
var viewModel = {
phones: ko.observableArray(phones_list),
selectedPhones: ko.observableArray(),
enableEdit: function(id) {
return ko.utils.arrayFirst(viewModel.selectedPhones(),
function(p) { return p == id })
}
};
Then you can use this helper function in your enable binding:
<input type='text' data-bind="attr: {value:phone}, disable: $root.enableEdit(id)"/>
Demo JSFiddle.

How to get selected checkboxes on button click in angularjs

I want to do something like this
<input type="checkbox" ng-model="first" ng-click="chkSelect()"/><label>First</label>
<input type="checkbox" ng-model="second" ng-click="chkSelect()"/><label>Second</label>
<input type="checkbox" ng-model="third" ng-click="chkSelect()"/><label>Third</label>
<input type="checkbox" ng-model="forth" ng-click="chkSelect()"/><label>Forth</label>
<button>Selected</button>
On button click I want to display selected checkbox labelname.
$scope.chkSelect = function (value) {
console.log(value);
};
Because the checkboxes are mapped, you can reference $scope.first, $scope.second, etc in your chkSelect() function. It's also possible to have a set of checkboxes mapped as a single array of data instead of having to give each checkbox a name. This is handy if you are generating the checkboxes, perhaps from a set of data.
I agree with Bublebee Mans solution. You've left out a lot of detail on why you're trying to get the label. In any case if you REALLY want to get it you can do this:
$scope.chkSelect = function (value) {
for(var key in $scope){
var inputs = document.querySelectorAll("input[ng-model='" + key + "']");
if(inputs.length){
var selectedInput = inputs[0];
var label = selectedInput.nextSibling;
console.log(label.innerHTML);
}
};
};
You can mess around with it to see if it's indeed selected.
fiddle: http://jsfiddle.net/pzz6s/
Side note, for anybody who knows angular please forgive me.
If you are dealing with server data, you might need isolated html block and deal with data in controller only.
You can do it by creating array in controller, maybe your data from response, and use ngRepeat directive to deal independently in html code.
Here is what I am telling you:
HTML:
<form ng-controller="MyCtrl">
<label ng-repeat="name in names" for="{{name}}">
{{name}}
<input type="checkbox"
ng-model="my[name]"
id="{{name}}"
name="favorite" />
</label>
<div>You chose <label ng-repeat="(key, value) in my">
<span ng-show="value == true">{{key}}<span>
</label>
</div>
</form>
Javascript
var myApp = angular.module('myApp',[]);
function MyCtrl($scope) {
$scope.names = ['pizza', 'unicorns', 'robots'];
$scope.my = { };
}
You want to have something like the following in your controller (untested, working from memory):
$scope.checkBoxModels = [ { name: 'first', checked: false }, { name: 'second', checked: false }, { name: 'third', checked: false }, { name: 'fourth', checked: false } ];
Then in your view:
<input ng-repeat"checkboxModel in CheckBoxModels" ng-model="checkBoxModel.checked" ng-click="chkSelect(checkBoxModel)" /><label>{{checkBoxModel.name}}</label>
Then update your function:
$scope.chkSelect = function (checkBoxModel) {
console.log(checkBoxModel.name);
};

Categories

Resources