I want to be able to have a list of items and to select one using a checkbox:
<div data-ng-repeat="device in devices">
<div class="col-sm-offset-2 col-sm-10">
<div class="checkbox">
<label>
<input type="checkbox"> {{ device.name }}
</label>
</div>
</div>
</div>
If this can be done using a custom directive that would also be cool!
So the idea, that when a checkbox is checked the device would go into an ng-model and all the other checkboxes would be disabled.
I have a feeling there needs to be a custom model created, something like:
devices = [{
name: "LED",
checked: false,
id: "98"
},{
name: "LED 2",
checked: false,
id: "8"
},{
name: "LED 3",
checked: false,
id: "78"
}]
Just need some function to fire each time one checkbox is checked.
I expect that it can be done with a ng-click on the checkbox? And a two way data binding on the model for canBeChecked
devices = [{
name: "LED",
checked: false,
id: "98",
canBeChecked: true
},{
name: "LED 2",
checked: false,
id: "8",
canBeChecked: true
},{
name: "LED 3",
checked: false,
id: "78",
canBeChecked: true
}]
Iterate over your collection and display a checkbox for each:
<div ng-repeat="device in devices">
<label>
{{ device.name }}
<input type="checkbox" ng-model="device.checked" ng-click="change(device)">
</label>
</div>
Note that the checkbox also has the ng-click directive. This is what you want to trigger each time a checkbox is clicked. The triggered function clears all checkboxes and only checks the clicked one. The checkboxes should now behave like radio buttons.
Your controller might look like this:
app.controller("MyCtrl", ["$scope", function($scope) {
$scope.devices = [{
name: "LED",
checked: false
}, {
name: "LED 2",
checked: false
}, {
name: "LED 3",
checked: false
}];
$scope.change = function(device) {
angular.forEach($scope.devices, function(item) {
item.checked = false;
});
device.checked = true;
};
}]);
It is not necessary to create the canBeChecked property you mention.
Here's the full working example: http://jsfiddle.net/zxdr8/
If you must use checkboxes, here is how you would do it.
Markup:
<div data-ng-repeat="device in devices">
<div class="col-sm-offset-2 col-sm-10">
<div class="checkbox">
<label>
<input type="checkbox" ng-model="device.checked" ng-change="checkDevice(device)"> {{ device.name }}
</label>
</div>
</div>
</div>
Controller:
$scope.devices = [{
name: "LED",
checked: false,
id: "98"
},{
name: "LED 2",
checked: false,
id: "8"
},{
name: "LED 3",
checked: false,
id: "78"
}];
$scope.checkDevice = function (device) {
for (var i = 0, len = $scope.devices.length; i < len; ++i) {
if ($scope.devices[i] !== device)
$scope.devices[i].checked = false;
}
});
Your checked and canBeChecked properties seems like merely an UI thing. In my opinion, you should not be creating a custom data models and duplicating unnecessary properties just to do that. Believe me, I did things like that too when started using Angular, but there are much better ways.
Consider storing selected data in other location (model, service, controller, whatever). And maybe if you can store just an ID (primitive property), you can do your "checkbox-radio-like-element" like this:
<div ng-repeat="device in devices">
<label>
<input type="checkbox" ng-true-value="{{device.id}}" ng-false-value="" ng-model="some.storage">
{{device.name}}
</label>
</div>
And thats all you need, no background code needed. When Angular team implements interpolation support for ngTrueValue and ngFalseValue directives, you will be able to store the whole objects and reset model to e.g. null.
Related
Plunker link
I am loading form from JSON.
I need help on below issue.
Issue : On click of save button i need to get the radio button values either true or false
but now i am getting both radio button values as 'true in console'
Users can click both the buttons, since it is a radio group, i need one value as true and another one as false vice versa
{name: "rado 1", type: "radio", id: "rOne", checked: true}
{name: "radio 2", type: "radio", id: "rTwo", checked: true}
My component HTML code
<div class="col-md-12 pb-2 pt-4">
<form class="form-horizontal">
<div *ngFor="let record of records">
<div *ngFor="let radioButton of record.wrapper">
<label>
//hidden element
<input type="hidden" name="{{radioButton.id}}" [(ngModel)]="radioButton.checked">
<input
name="radiogroup"
type="radio"
id="{{radioButton.id}}"
[checked]="radioButton.checked"
(change)="radioButton.checked = $event.target.checked"
[value]="radioButton.checked"
>
{{radioButton.name}}
</label>
</div>
</div>
<button class=" btn btn-primary " href="# " (click)="save() ">Save</button>
</form>
</div>`
Expected: I need radio button values true or false vice versa, it can not be both true or false.
values should be sent to hidden variables.
Two way binding
My Json (i have very complex json but here making it simple one)
[
{
"wrapper": [
{
"name": "rado 1",
"type": "radio",
"id": "rOne",
"checked": false
},
{
"name": "radio 2",
"type": "radio",
"id": "rTwo",
"checked": false
}
]
}
]
Edited
<label>
<input
name="radiogroup"
type="radio"
id="{{radioButton.id}}"
[value] = "radioButton.id"
[(ngModel)]="selectedRadioId" >
{{radioButton.name}}
</label>
ts :
// On click of save button show console log with values
save(): void {
this.records[0].wrapper.forEach((wrapper)=>{
wrapper.checked = wrapper.id == this.selectedRadioId;
})
console.log(this.records[0].wrapper[0]);
console.log(this.records[0].wrapper[1]);
}
Plunker
In my application i have select that i bind with options and the user selected saved data on page load.
Fiddle Link for issue
<div ng-app ng-controller="QuestionController">
<ul ng-repeat="question in Questions">
<li>
<div>{{question.Text}}</div>
<select ng-model="Answers['{{question.Name}}']" ng-options="option for option in question.Options">
</select>
<select ng-model="OptSelected" ng-options="option for option in question.Options">
</li>
</ul>
</div>
And in my angular controller
function QuestionController($scope) {
$scope.Answers = {};
$scope.Questions = [{ "Text": "Gender?", "Name": "GenderQuestion",
"Options": [{1,"Male"}, {2,"Female"}],
"OptSelected": [{1,"Male"}]},{ "Text": "Favorite color?","Name": "ColorQuestion",
"Options": [{1,"Red"}, {2, "Blue"}, { 3,"Green"}],"OptSelected": [{ 2, "Blue"}] }];
angular.forEach($scope.Questions, function(q) {
var propModel = "Answers['" + q.Name + "']";
$scope[propModel] = q.OptSelected;
})
In my application I am successful at binding data to select but i canot set the user saved value to select.
I have tried to recreate the issue with fiddle without much success but I think it will provide you all better understanding of what I am trying to do
I would recommend binding to just the Questions array and avoid the complexity of trying to bind to the corresponding question in an Answers array. You can always extract what you need from the Questions array either after a selection has been made or all together through some controller level action.
That said, part of your problem is that you don't have a well formed array of objects.
Here's a simplified, working version:
var app = angular.module('myApp', []);
app.controller('QuestionController', function($scope) {
$scope.Questions = [{
Text: "Gender?",
Name: "GenderQuestion",
Options: [{
id: 1,
desc: "Male"
}, {
id: 2,
desc: "Female"
}],
OptSelected: {
id: 1,
desc: "Male"
}
}, {
Text: "Favorite color?",
Name: "ColorQuestion",
Options: [{
id: 1,
desc: "Red"
}, {
id: 2,
desc: "Blue"
}, {
id: 3,
desc: "Green"
}],
OptSelected: {
id: 2,
desc: "Blue"
}
}];
});
<html ng-app="myApp">
<head>
<meta charset="utf-8" />
<script>
document.write('<base href="' + document.location + '" />');
</script>
<script data-require="angular.js#1.5.x" src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.11/angular.min.js" data-semver="1.5.11"></script>
</head>
<div ng-controller="QuestionController">
<ul ng-repeat="question in Questions">
<li>
<div>{{question.Text}}</div>
<select ng-model="question.OptSelected" ng-options="option as option.desc for option in question.Options track by option.id">
</select>
</li>
</ul>
</div>
</html>
I have this JSON data with me which has some options to build a form and I'm trying to create a dynamic form with each question per screen along with next/prev button. This is what I have tried so far. Can someone help me with creating form with this JSON along with form validation and prev/next functionality?
Plunker: https://plnkr.co/edit/p2qVI7kqOnqoKjYr6yb3?p=preview
JSON
var questions = [
{
text: "What is your name?",
type: "text",
required: true,
model: "name"
},
{
text: "Tell us something about yourself.",
type: "textarea",
required: true,
model: "about"
},
{
text: "Explain your job profile",
type: "textarea",
required: false,
model: "profile"
},
{
text: "Which is the largest country in the world by population?",
offeredAnswers: [
{"value": "Yes"},
{"value": "No"}
],
type: "radio",
required: true,
model: "radio1"
},
{
text: "Would you be available to work in shifts?",
offeredAnswers: [
{"value": "Yes"},
{"value": "No"}
],
type: "radio",
required: true,
model: "radio2"
},
{
text: "How long have you been working at your current job?",
offeredAnswers: [
{"value": "Not long, it's part time"},
{"value": "1-2 yrs"},
{"value": "3-4 yrs"},
{"value": "5+ yrs"}
],
type: "radio",
required: true,
model: "radio3"
}
];
HTML
<form name="mmSurvey">
<ul id="options">
<li ng-show="question">
<label>
<div ng-if="(question.type == 'radio' && question.options)">
<p ng-repeat="option in question.options">
<input type="{{option.type}}" name="" ng-change="updateAnswerList(option.value)" ng-required="{{option.required}}" />
</p>
</div>
<div ng-if="(question.type == 'text')">
<input type="{{question.type}}" ng-required="{{question.required}}" />
<!-- show an error if this isn't filled in -->
<p ng-show="mmSurvey.mm.$error.required">Please enter your name.</p>
</div>
<div ng-if="(question.type == 'textarea')">
<textarea ng-required="{{question.required}}"></textarea>
</div>
</label>
</li>
</ul>
</form>
Hmm.. Assuming you are asking about the prev/next part.
It can be done by assigning your current question to a variable.
//init
var currentIndex = 0;
//update question object according to index
$scope.$watch(function() {
return currentIndex;
}, function() {
$scope.question = questions[currentIndex];
});
$scope.next = function() {
// validation here
// hint: you can use formname.$invalid for first check
// increase question number
currentIndex++;
}
// similar for prev
By the way you should put type="button" in every button so it won't attempt to submit the form every time you click it.
I have 'recommendations', an array of recommendation like:
{Id: 1, Label: "option 1"},
{Id: 2, Label: "option 2"}
And 'items', an array of item like:
{Id: 1, Name: "Name 1", Recommend: recommendations[0]},
{Id: 2, Name: "Name 2", Recommend: recommendations[1]}
Now I like to show a list of items, for each item, show item name and radio button group to let user select one of possible 'Recommend' options.
My html looks like:
<div ng-repeat="i in viewModel.items">
<div>
{{i.Name}}
</div>
<div class="form-group">
<label class="control-label col-md-3">Recommend this?</label>
<div class="col-md-9">
<div class="radio" ng-repeat="r in viewModel.recommendations">
<label>
<input type="radio" ng-model="i.Recommend" ng-value="r"/>
{{r.Label}}
</label>
</div>
</div>
{{i.Name}}, {{i.Recommend.Label}}
</div>
</div>
{{viewModel.items | json}}
When I click one of options for each item, I see corresponding Recommend.Label is changing properly so it seems to be bound to model ok.
My problem is that, on initial page load, radio button group of each item is not showing the current option even though 'items' seems to have one of possible Recommend object. (All radio buttons are not checked).
What am I missing?
You should set the item's "Recommend" value to bound to $scope.recommendations at the beginning.
angular.module('test', []).controller('testCtrl', function($scope) {
$scope.viewModel = {};
$scope.viewModel.recommendations = [{Id: 1, Label: "option 1"}, {Id: 2, Label: "option 2"}];
$scope.viewModel.items = [{Id: 1, Name: "Name 1", Recommend: $scope.viewModel.recommendations[0]}, {Id: 2, Name: "Name 2", Recommend: $scope.viewModel.recommendations[1]}]; });
jsFiddle: http://jsfiddle.net/rkgetptj/
I have a web page with check boxes & radio buttons within nested ng-repeats. When I am clicking the check boxes the underlying view model is getting updated properly, but when I click on the radio buttons, the view model is not getting updated properly. Within a group, when I select an option the selected model property gets updated to true but the other one doesn't change to false.
e.g. when I click on the radio buttons against chicken one by one, all of them becomes true. When I select any one, I want the other ones to become false
My view model is given below.
$scope.itemGroups = [{
"name": 'Non Veg',
"items": [{
"selected": false,
"name": 'Chicken',
"Portions": [{
"selected": false,
"name": '1 Cup'
}, {
"selected": false,
"name": '2 Cups'
}, {
"selected": false,
"name": '3 cups'
}]
}, {
"selected": true,
"name": 'Egg',
"Portions": [{
"selected": false,
"name": '1 Cup'
}, {
"selected": false,
"name": '2 Cups'
}, {
"selected": false,
"name": '3 cups'
}]
}]
}, {
"name": 'Veggie',
"items": [{
"selected": false,
"name": 'Potato',
"Portions": [{
"selected": false,
"name": '1 Cup'
}, {
"selected": false,
"name": '2 Cups'
}, {
"selected": false,
"name": '3 cups'
}]
}, {
"selected": false,
"name": 'Tomato',
"Portions": [{
"selected": false,
"name": '1 Cup'
}, {
"selected": false,
"name": '2 Cups'
}, {
"selected": false,
"name": '3 cups'
}]
}]
}];
The way I bind to the html:
<div ng-repeat="itemGrp in itemGroups">
<h1>{{itemGrp.name}}</h1>
<div ng-repeat="item in itemGrp.items">
<input type="checkbox" ng-model="item.selected" />{{item.name}}
<label ng-repeat="portion in item.Portions">{{portion.name}}
<input type="radio" name="radio_{{itemGrp.name}}" ng-model="portion.selected" ng-value="true" />
</label>
</div>
</div>
Fiddle: http://jsfiddle.net/awqv0rb0/16/
Can you please guide me on what can be the issue here? Is there a better way of achieving what I am trying to do here? I need to loop through the JSON and get the values of the selected items.
This is a trivial issue. I too, faced it an earlier stages.
If you are looping over a group of items and each item has a set of radio buttons, then all the radio buttons for a given item must have the 'name' attribute value as the item name.
Ex. If the item name is 'Chicken', all it's radio buttons labelled as '1 Cup', '2 Cups', '3 Cups' should have their name='{{item.name}}' i.e. 'Chicken'.
Change Point in your Code:
<input type="radio" name="{{item.name}}" ng-model="portion.selected" ng-value="true" />
Here is the JSFiddle demo
The code you have posted here doesn't quite match the fiddle you provided, but your issue is the result of giving each radio button it's own name, and therefore, it's own group. This allows all radio buttons to essentially function as check boxes and all be set to true instead of the desired behavior of allowing only one.
Instead, you should give all radio buttons under that item the same group name, like:
name="{{item.name}}"
Your nested ng-repeats should look something like this when you're done:
<div ng-repeat="item in itemGrp.items">
<input type="checkbox" ng-model="item.selected" />{{item.name}}
<label ng-repeat="portion in item.Portions">{{portion.name}}
<input type="radio" name="{{item.name}}" ng-model="portion.selected" ng-value="true" />
</label>
</div>
Updated per your Comment
To update the selected:true value of each of your portions when you change one, you'll need a little bit more code. This is a little messy, so it may not be the best solution--but I was able to get it to work.
In addition to the ng-value="true" you should also add an ng-change="toggleRadio(itemGrp,item,portion)"to your radio buttons. In your js, you should add the following function:
$scope.toggleRadio = function(itemGrp, item, obj) {
var indexOfItemGrp = $scope.itemGroups.indexOf(itemGrp);
var indexOfItem = $scope.itemGroups[indexOfItemGrp].items.indexOf(item);
var indexOfPortion = $scope.itemGroups[indexOfItemGrp].items[indexOfItem].Portions.indexOf(obj);
angular.forEach($scope.itemGroups[indexOfItemGrp].items[indexOfItem].Portions, function(value,key) {
if(indexOfPortion != key) {
value.selected = false;
}
});
};
Basically, this code will iterate through all of the portion options inside the item inside the itemGroup and update their value to false unless they were the selected option.
You can see a working example in a fork of your original fiddle that I created: here.
One More Update
Have a look at this question: AngularJS Binding Radio Buttons To Booleans Across Multiple Objects. If you can change the model of your json object, this might be a viable solution. If you can't change the model, manually updating the other values is your cleanest option.