Cascading select options in angular - angularjs

I cannot get cascading dropdown selects to work pas the first one in angular. On selecting the first option, if it has values for divisions I'd like to show them in the second dropdown, and if the latter has values for workplaces I'd like to show them in the third.
Here is my html
<div ng-controller="SectorController">
<select class="form-control" id="businessUnit" ng-model="divisionsSource"
ng-options="businessUnit.division as businessUnit.sectorName for businessUnit in businessUnits track by businessUnit.id">
<option value=''>Select</option>
</select>
<td>
<select class="form-control" id="division" ng-model="workplacesSource" ng-disabled="!divisionsSource"
ng-options="division.workplace as division.sectorName for division in divisionsSource track by division.id">
<option value=''>Select</option>
</select>
<select class="form-control" id="workplace" ng-disabled="!workplacesSource || !divisionsSource" ng-model="workplace">
<option value=''>Select</option>
<option ng-repeat="workplace in workplacesSource" value='{{workplace}}'>{{workplace}}</option>
</select>
and here is my json feed:
$rootScope.businessUnits = [
{
"id": 1,
"sectorName": "AAA",
"sectorLevel": 20
},
{
"id": 2,
"sectorName": "BBB",
"sectorLevel": 20
},
{
"id": 3,
"sectorName": "CCC",
"sectorLevel": 20
},
{
"id": 4,
"sectorName": "DDD",
"sectorLevel": 20,
"divisions": [
{
"id": 6,
"sectorName": "DDD1",
"sectorLevel": 30
},
{
"id": 7,
"sectorName": "DDD2",
"sectorLevel": 30
},
{
"id": 8,
"sectorName": "DDD3",
"sectorLevel": 30
},
{
"id": 9,
"sectorName": "DDD4",
"sectorLevel": 30,
"workplaces": [
{
"id": 12,
"sectorName": "DDD4 SUB1",
"sectorLevel": 40
},
{
"id": 13,
"sectorName": "DDD4 SUB2",
"sectorLevel": 40
}
]
},
{
"id": 10,
"sectorName": "DDD5",
"sectorLevel": 30
},
{
"id": 11,
"sectorName": "DDD6",
"sectorLevel": 30
}
]
},
{
"id": 5,
"sectorName": "EEE",
"sectorLevel": 20
}
]
Any input will be greatly appreciated.

Your ng-options aren't correct. Here's a plunkr which fixes it: http://plnkr.co/edit/GOIiGXAHnr7nUfv4NHVH?p=preview
Explanations:
businessUnit.division as businessUnit.sectorName
for businessUnit in businessUnits track by businessUnit.id
So, when an option is selected in this first select box, its model (divisionsSource) is set to the selected businessUnit's division. But a businessUnit doesn't have a field named division. It has a field named divisions.
So the code should be
businessUnit as businessUnit.sectorName
for businessUnit in businessUnits track by businessUnit.id
and the next select box should use
division as division.sectorName
for division in divisionsSource.divisions track by division.id

Related

Dynamic form using AngularJS, multiple values binding

I am looking for a best approach to convert a static form to an angular dynamic form. I am not sure how to bind multiple values to the same answer.
The static page is available at: https://jsfiddle.net/hvuq5h46/
<div ng-repeat="i in items">
<select ng-model="i.answer" ng-options="o.id as o.title for o in i.answersAvailable" ng-visible="y.TYPE = 'SINGLE'"></select>
<input type="checkbox" ng-model="i.answer" ng-visible="y.TYPE = 'MULTIPLE'" />
</div>
The JSON file
[
{
"id": 1,
"title": "Are you a student?",
"type": "SINGLE",
"answersAvailable": [
{
"id": 1,
"title": "Yes"
},
{
"id": 2,
"title": "No"
}
],
"answer": [
1
]
},
{
"id": 2,
"title": "Would you like to be an astronaut?",
"type": "SINGLE",
"answersAvailable": [
{
"id": 4,
"title": "Yes"
},
{
"id": 5,
"title": "No"
},
{
"id": 6,
"title": "I am not sure"
}
],
"answer": [
4
]
},
{
"id": 3,
"title": "What is your favourite planet?",
"type": "MULTIPLE",
"answersAvailable": [
{
"id": 7,
"title": "Earth"
},
{
"id": 8,
"title": "Mars"
},
{
"id": 9,
"title": "Jupiter"
}
],
"answer": [
7,
8
]
}
]
Things would be much simpler if you can use a multiple select, but I understand it might be difficult for user to interact (consider something like md-select, which transforms multiple select into a list of checkbox for you)
Multiple select:
<select multiple
ng-model="i.answer"
ng-options="o.id as o.title for o in i.answersAvailable"
ng-if="i.type == 'MULTIPLE'"></select>
Anyway it is completely ok to use HTML checkbox. To do that we would need to bind checkbox model into the data as usual, and then update the answer array simultaneously.
ng-model="o.selected"
ng-change="updateAnswer(i)"
Also, we'll need to copy existing data to model during init.
ng-init="initMultiple(i)"
Working code:
angular.module('test', []).controller('Test', Test);
function Test($scope) {
$scope.items = [{
"id": 1,
"title": "Are you a student?",
"type": "SINGLE",
"answersAvailable": [{
"id": 1,
"title": "Yes"
},
{
"id": 2,
"title": "No"
}
],
"answer": [
1
]
},
{
"id": 2,
"title": "Would you like to be an astronaut?",
"type": "SINGLE",
"answersAvailable": [{
"id": 4,
"title": "Yes"
},
{
"id": 5,
"title": "No"
},
{
"id": 6,
"title": "I am not sure"
}
],
"answer": [
4
]
},
{
"id": 3,
"title": "What is your favourite planet?",
"type": "MULTIPLE",
"answersAvailable": [{
"id": 7,
"title": "Earth"
},
{
"id": 8,
"title": "Mars"
},
{
"id": 9,
"title": "Jupiter"
}
],
"answer": [
7,
8
]
}
]
$scope.initMultiple = function(item) {
item.answersAvailable.forEach(function(option) {
option.selected = item.answer.indexOf(option.id) != -1;
});
}
$scope.updateAnswer = function(item) {
item.answer = item.answersAvailable.filter(function(option) {
return option.selected;
})
.map(function(option) {
return option.id;
});
}
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.8/angular.min.js"></script>
<div ng-app='test' ng-controller='Test'>
<div ng-repeat="i in items">
<select ng-model="i.answer[0]"
ng-options="o.id as o.title for o in i.answersAvailable"
ng-if="i.type == 'SINGLE'"></select>
<label ng-repeat="o in i.answersAvailable"
ng-if="i.type == 'MULTIPLE'"
ng-init="initMultiple(i)">
<input type="checkbox"
ng-model="o.selected"
ng-change="updateAnswer(i)" /> {{o.title}}
</label>
<div>{{i.answer}}</div>
</div>
</div>
Based on my experience, I will make a separation as two Angular models (or usually called services) for the form questions and another one which will collect the answers and eventually will be passed to the backend for further processing. This will provide me a flexibility to maintain both logic and presentation.
var myModule = angular.module('myModule', []);
myModule.factory('QuestionsFormService', function() {
var _question1;
var _question2;
var _question3;
function init(data){
//questions initiation
}
return init;
});
var myModule = angular.module('myModule', []);
myModule.factory('FormDataService', function() {
var _dataAnswer = {}
function init(){
//data initialization
}
function insertData(key, value){
_dataAnswer[key] = value
}
return init;
});
From the example of service models above, you need to make these available to your presentation through the Angular controller with Dependency Injection.
myModule.controller("MyCtrl", function($scope, FormDataService, QuestionsFormService) {
$scope.form_questions = QuestionsFormService.init();
$scope.form_answers = FormDataService.init()
//further logic to make these available on your view on your convenience
});
What you write on the HTML page as an Angular view is already close enough. You only need to change the binding to two models as I propose above. Thank you.

Angular create dynamic scope in controller(js) & bind in view(html)

I have created this plnkr to show what I'v tried.
$scope.myArray = [{
"productDetails": {
"productName": "productname1",
"qty": 5,
"pricePerPiece": 20
},
"vehiclecategory": "abcd"
},
...
]
Need to bind the values of each vehicle category for each record.
Have two records per row with Label & it's value that will be bind from key vehiclecategory for each object.
Label will remain as it is since its text will change depending upon internationalization, so it will be a constant that will be coming from properties file as per user location. There will be separate constants file for each location.
Currently have hard code the label value. Need to achieve below sample
(1)First Record : abcd1
(2)Second Record : abcd2
#JohnD answer is correct, you can display the item inside an array using ngRepeat but if you want to add ordinal numbers you can have a look in this post "Add st, nd, rd and th (ordinal) suffix to a number"
case 1 )
http://plnkr.co/edit/sA85huMV3nYUJME8tSVx?p=preview
you know the name of label property in your data (the key)
<div class="width50" ng-repeat="item in myArray track by $index">
<label>{{item.label}} - {{$index}}</label> : {{item.vehiclecategory}}
</div>
Javascript :
I added a label attribute to your $scope.myArray.
As JohnD explain, you have to use ng-repeat to iterate over an array and not use "$scope.first, $scope.second ..." (imagine if you have 100)
$scope.myArray = [{
"productDetails": { "productName": "productname1", "qty": 5, "pricePerPiece": 20 },
"vehiclecategory": "abcd1",
"label" : "My Data",
}, {
"productDetails": {
"productName": "productname1", "qty": 5, "pricePerPiece": 20
},
"vehiclecategory": "abcd2",
"label" : "Your Info",
}, {
"productDetails": { "productName": "productname1", "qty": 5, "pricePerPiece": 20 },
"vehiclecategory": "abcd3",
"label":"adresse"
}, {
"productDetails": { "productName": "productname1", "qty": 5, "pricePerPiece": 20 },
"vehiclecategory": "abcd4",
"label": "street"
}, {
"productDetails": { "productName": "productname1", "qty": 5, "pricePerPiece": 20 },
"vehiclecategory": "abcd5",
"label" : "city",
}, {
"productDetails": { "productName": "productname1", "qty": 5, "pricePerPiece": 20 },
"vehiclecategory": "abcd6",
"label":"etc"
}];
case 2 )
Maybe the name of the label attribute is not always the same like this :
$scope.myArray = [
{
"productDetails": { "productName": "productname1","qty": 5, "pricePerPiece": 20 },
"vehiclecategory": "abcd1",
"My Data" : "My Data",
}, {
"productDetails": { "productName": "productname1", "qty": 5, "pricePerPiece": 20 },
"vehiclecategory": "abcd2",
"Your Info" : "Your Info",
}, {
"productDetails": { "productName": "productname1", "qty": 5, "pricePerPiece": 20 },
"vehiclecategory": "abcd3",
"label":"adresse"
}, {
"productDetails": { "productName": "productname1", "qty": 5, "pricePerPiece": 20 },
"vehiclecategory": "abcd4",
"street": "street"
},
...
];
// this array contain all the possible label name
var listoflabel = ["etc","adresse","city","street","Your Info","My Data"];
// search on item if a label key exist and return its value
$scope.getLabel = function(item){
for(var l in listoflabel){
if(item[ listoflabel[l] ]){
return item[ listoflabel[l] ];
}
}
return "label";
}
HTML with function call
<div class="width50" ng-repeat="item in myArrayVariable track by $index">
<label>{{getLabel(item)}}</label> : {{item.vehiclecategory}}
</div>
I'm not sure I completely understand your question, but if I do, you might be able to solve it with ngRepeat. Check out the following example:
<div class="width50" ng-repeat="item in myArray track by $index">
<label>({{$index}}) {{item.vehiclecategory}}</label> : {{item.productDetails.productName}}
</div>

What would be angular ng-option equivalent of this select?

I am struggling to get this into an ng-option. Is it even possible?
<select ng-model="detail_type_id">
<optgroup ng-repeat="type in data.detailTypes" label="{{type.name}}">
<option ng-repeat="t in type.children" value="{{t.id}}">{{t.name}}</option>
</optgroup>
</select>
DetailTypes looks like this:
[
{"id":7,
"parent_id":null,
"name":"Contact",
"children":[
{"id":8,
"parent_id":7,
"name":"Address",
"children":[]
},
{"id":12,
"parent_id":7,
"name":"Something else",
"children":[]
}
]},
{"id":16,
"parent_id":null,
"name":"Other",
"children":[
{"id":10,
"parent_id":16,
"name":"Remarks",
"children":[]}
]
}
]
Child id needs to be selected. Nesting cannot be deeper.
The ngOptions directive does not work with multidimensional objects. So you need to flatten your array to use it.
I wrote a filter for that:
app.filter('flatten' , function(){
return function(array){
return array.reduce(function(flatten, group){
group.children.forEach(function(child){
child.groupName = group.name;
flatten.push(child)
})
return flatten;
},[]);
}
})
And the HTML part would be like this:
<select ng-model="detail_type_id"
ng-options="item.id as item.name
group by item.groupName for item
in data.detailTypes | flatten track by item.id">
</select>
Plunker (version #1 with filter):
https://plnkr.co/edit/dxi7j8oxInv2VRJ1aL7F
I also modified your object to be like this:
[{
"id": 7,
"parent_id": null,
"name": "Contact",
"children": [{
"id": 8,
"parent_id": 7,
"name": "Address",
"children": []
}, {
"id": 12,
"parent_id": 7,
"name": "Something else",
"children": []
}]
}, {
"id": 16,
"parent_id": null,
"name": "Other",
"children": [{
"id": 10,
"parent_id": 16,
"name": "Remarks",
"children": []
}]
}]
EDIT:
After suggestion I wrote another version without the filter, but flattening the array inside the controller.
Additional Controller JS:
$scope.flattenDetailTypes = flattenDetailTypes($scope.data.detailTypes);
function flattenDetailTypes(array){
return array.reduce(function(flatten, group){
group.children.forEach(function(child){
child.groupName = group.name;
flatten.push(child)
})
return flatten;
},[]);
}
Markup:
<select ng-model="detail_type_id"
ng-options="item.id as item.name group by item.groupName for item in flattenDetailTypes track by item.id"></select>
Plunker (version #2 without filter):
https://plnkr.co/edit/D4APZ6

angularjs post Radio Button group collection (Save every group checked choice in array Or Post choices to the server)

" I'm new in angularjs "
1- Scenario Description:
- We are Making Something like Survey ,
- Many Questions , every Question contains many Answers ,
- The Questions and it's answers rendered automatically from database using angularjs
- When user finish the Survey just click Save to post selected choices to the server
2- The problem :
- Using "angularjs" How can i Collect the user choices in array or post the selected choices to the server.
Code : https://jsfiddle.net/6kxx2vLu/
Result Image
===================== angularjs And HTML =====================
<div ng-app="massApp" ng-controller="massCtrl">
<ul>
<li ng-repeat="question in Questions">
<h1>{{question.questionAR}}</h1>
<ul>
<li ng-repeat="answerChoice in question.answerChoices">
<div ng-if="answerChoice.answerChoiceTypeId == '1' ">
<label for="elmnt{{question.questionId}}">
{{answerChoice.answerChoiceAr}}
</label>
<input type="radio" name="elmnt{{question.questionId}}" value="{{answerChoice.answerChoiceId}}" />
</div>
</li>
</ul>
</li>
</ul>
<input ng-click="saveAnswers()" type="button" value="Save" />
</div>
<script type="text/javascript">
(function () {
//------
var moCdataForTest = [{
"questionId": 20,
"questionAR": "How you Know Our Services?",
"surveyId": 12,
"qyestionAnswersTypeId": 1,
"answerChoices": [
{
"answerChoiceId": 1,
"questionId": 20,
"answerChoiceAr": "From web or Google Serach",
"answerChoiceTypeId": 1,
},
{
"answerChoiceId": 2,
"questionId": 20,
"answerChoiceAr": "From Frind",
"answerChoiceTypeId": 1,
},
{
"answerChoiceId": 3,
"questionId": 20,
"answerChoiceAr": "Newspaper ads",
"answerChoiceTypeId": 1,
}
]
},
{
"questionId": 21,
"questionAR": "What is your satisfaction level?",
"surveyId": 12,
"qyestionAnswersTypeId": 1,
"answerChoices": [
{
"answerChoiceId": 4,
"questionId": 21,
"answerChoiceAr": "Good",
"answerChoiceTypeId": 1,
},
{
"answerChoiceId": 5,
"questionId": 21,
"answerChoiceAr": "Very Good",
"answerChoiceTypeId": 1,
},
{
"answerChoiceId": 6,
"questionId": 21,
"answerChoiceAr": "Excellent",
"answerChoiceTypeId": 1,
}
]
},
{
"questionId": 23,
"questionAR": "What is Visit Rate?",
"surveyId": 12,
"qyestionAnswersTypeId": 1,
"answerChoices": [
{
"answerChoiceId": 4,
"questionId": 23,
"answerChoiceAr": "1Star",
"answerChoiceTypeId": 1,
},
{
"answerChoiceId": 5,
"questionId": 23,
"answerChoiceAr": "2Star",
"answerChoiceTypeId": 1,
},
{
"answerChoiceId": 6,
"questionId": 23,
"answerChoiceAr": "3Star",
"answerChoiceTypeId": 1,
}
]
}]
//-------
var massApp = angular.module("massApp", []);
//-------
massApp.controller("massCtrl", function ($scope) {
$scope.Questions = moCdataForTest;
$scope.saveAnswers = function () {
alert('Data Saved');
}
});
//-------
})();
</script>
You just need to use ng-model.
I see you are using Angular 1.2. In this version ng-model does not support getter/setter feature so it cannot add/remove from array.
So, you'll need to change userAnswers to be an object instead of an array.
$scope.userAnswers = {};
Of course you can $watch the object to sync it to an array, or you can do it only on submit click for example.
This will allow you to set ng-model="userAnswers[question.questionId]", like:
<input type="radio"
ng-model="userAnswers[question.questionId]"
ng-value="answerChoice.answerChoiceId"
name="elmnt{{question.questionId}}" />
Working example:
https://jsfiddle.net/Meligy/6kxx2vLu/8/
See the shape of userAnswers just after the save button.
Also click on the save button and scroll to the end to see an example of converting it to an array.
Update:
You don't even need the name when you use ng-model. Angular will handle grouping the inputs for you based on similar ng-model. It wasn't working with the label anyway because Angular doesn't support interpolation (expressions) in the name of form controls.
The easiest way to get the label to work is to make it wrap the input as well, like:
<label>
{{answerChoice.answerChoiceAr}}
<input type="radio"
ng-model="userAnswers[question.questionId]"
ng-value="answerChoice.answerChoiceId" />
</label>
Example: https://jsfiddle.net/xqc284xt/
Another approach is to use ng-change. However in this case you'll need to check the array if it has an object with given question ID or not, to add or update based on that. I wouldn't bother, but comment if you want a code sample showing it.
You an use form input arrays <input name="foo[]" ... >
so in your case it will be something like this:
<input type="radio" name="answers[{{question.questionId}}]" value="{{answerChoice.answerChoiceId}}" />
I think, you wanna to handle the onclick event in the radio buttons to collect all selected answers for related questions,
<input type="radio" name="elmnt{{question.questionId}}" value="{{answerChoice.answerChoiceId}}" onclick="setValue(event)" />
and in js your code :
var answer= function(name,value){
this.name=name;
this.value=value;
};
var questionsAnswers=[];
function setValue(event){
var evSrc = event.target;
var arrLength = questionsAnswers.length;
questionsAnswers[arrLength]=new answer(evSrc.name,evSrc.value);
}
By trying this you get the selected answers and questions in this array questionsAnswers
So you can handle it in saveAnswers function , like that:
$scope.saveAnswers = function () {
for (var i = 0; i < questionsAnswers.length; i++) {
alert(questionsAnswers[i].name +' = ' +questionsAnswers[i].value);
}
}
wish this will help.
Try this one,
I changed data format which seems to be more logical.
you need to handle ng-click for radio input and set the appropriate option as I have set it in each question.
In save function I collected all questionIds and their respective selected answer in an array.
please see this fiddle, I created for you.
Hope it will help you!
simply you need a little changes in your code :
To use Radio button you need a model property of field to hold your choice value and to do that use the ng-model directive at the radio button which will be the same value in each group of radio buttons
The steps:
1- add new property to your array named AnswerNumber at the first level of your array out side the nested array answerChoices
var moCdataForTest = [{
"questionId": 20,
"questionAR": "How you Know Our Services?",
"surveyId": 12,
"qyestionAnswersTypeId": 1,
"AnswerNumber" :"",
"answerChoices": [
{
"answerChoiceId": 1,
"questionId": 20,
"answerChoiceAr": "From web or Google Serach",
"answerChoiceTypeId": 1,
},
{
"answerChoiceId": 2,
"questionId": 20,
"answerChoiceAr": "From Frind",
"answerChoiceTypeId": 1,
},
{
"answerChoiceId": 3,
"questionId": 20,
"answerChoiceAr": "Newspaper ads",
"answerChoiceTypeId": 1,
}
]
},
{
"questionId": 21,
"questionAR": "What is your satisfaction level?",
"surveyId": 12,
"qyestionAnswersTypeId": 1,
"AnswerNumber" :"",
"answerChoices": [
{
"answerChoiceId": 4,
"questionId": 21,
"answerChoiceAr": "Good",
"answerChoiceTypeId": 1,
},
{
"answerChoiceId": 5,
"questionId": 21,
"answerChoiceAr": "Very Good",
"answerChoiceTypeId": 1,
},
{
"answerChoiceId": 6,
"questionId": 21,
"answerChoiceAr": "Excellent",
"answerChoiceTypeId": 1,
}
]
},
{
"questionId": 23,
"questionAR": "What is Visit Rate?",
"surveyId": 12,
"qyestionAnswersTypeId": 1,
"AnswerNumber" :"",
"answerChoices": [
{
"answerChoiceId": 4,
"questionId": 23,
"answerChoiceAr": "1Star",
"answerChoiceTypeId": 1,
},
{
"answerChoiceId": 5,
"questionId": 23,
"answerChoiceAr": "2Star",
"answerChoiceTypeId": 1,
},
{
"answerChoiceId": 6,
"questionId": 23,
"answerChoiceAr": "3Star",
"answerChoiceTypeId": 1,
}
]
}]
2- add the ng-model directive to the radio button and set it's value with the name of the new property AnswerNumber
<input type="radio" ng-value="answerChoice.answerChoiceId" name="elmnt{{question.questionId}}"
ng-model="question.AnswerNumber" />
3- finally at your scope function saveAnswers iterate over the the scope array variable Questions to display or add the answers to $scope.userAnswers variable
$scope.saveAnswers = function () {
var values = "";
for (i = 0; i < $scope.Questions.length; i++) {
values += $scope.Questions[i].AnswerNumber + "\n";
$scope.userAnswers[i] = $scope.Questions[i].AnswerNumber;
}
alert($scope.userAnswers);
//alert(values);
alert('Data Saved');
};
I already updated you online code with the answer
I've update your jsfiddle
https://jsfiddle.net/6kxx2vLu/7/
I've added ng-model to the input[radio] and bound it to the question.answer
<input type="radio" ng-value="answerChoice.answerChoiceId"
name="elmnt{{question.questionId}}" ng-model="question.answer" />
and on save I map the questions array like this
$scope.answers = $scope.Questions.map(function(q){return q.answer;});

Ng-option directive cant get access to nested array value

Help
I'm trying to output different class options(economy/business) with ng-options.
Here is the syntax and json information .
Cant get it working
<select ng-model="mySelect" ng-options="item for item in items ">
</select>
$scope.items = [
{
"id": 2,
"codeName": "class",
"friendlyName": "Class",
"options":
[
{
"id": 15,
"displayOrderNo": 0,
"optionName": "Economy"
},
{
"id": 16,
"displayOrderNo": 1,
"optionName": "Business"
},
{
"id": 36,
"displayOrderNo": 2,
"optionName": "First Class "
}
]
}
];

Resources