Angualrjs: How to manage showing cascaded select - javascript

I have this models:
var data = [
{
id: 1,
level:1,
name:"X1",
subChildren:[{
id:3,
level:2,
name:"X1-1",
subChildren:[...]
}]
},
{
id: 2,
level:1,
name:"X2",
subChildren:[{
id:4,
level:2,
name:"X2-2",
subChildren:[...]
}]
}....];
var levels=[{ level:1,.. },{ level:2,.. },...];
What I want is to show the data in html <select> element, and cascade them depending on the selected parent. i.e:
level 1:
<select>
<option>X1</option>
<option>X2</option>
</select>
-
level 2: (subChildren)
<select>
<option>X1-1</option>
</select>
-
level 3 (sub subChildren)
<select>
<option>X1-1-1</option>
</select>
This is only a non-functional demonstration for what I want to show, when you choose one of the parents it will cascade the next <select> which should be the next level (and contains the sub children) and so on..
The problem is I don't know the number of levels or the number of parents or subChildren, it's all dynamic, and don't know how to do this without writing a lot of native code.
The whole idea is I want to iterate over levels array and create the <select> tags.

This solution works only if you know the maximum number of subchildren you will encounter.Please check the plunkr Plnkr
// Code goes here
var app = angular.module('Dropdown', []).controller('DpdController', function($scope) {
$scope.data = [{
id: 1,
level: 1,
name: "X1",
subChildren: [{
id: 3,
level: 2,
name: "X1-1",
subChildren: [{
id: 4,
level: 3,
name: "X1-2"
}]
}]
}];
$scope.logValue = function(value) {
console.log(value);
}
});
<!DOCTYPE html>
<html ng-app="Dropdown">
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.0-rc.2/angular.min.js"></script>
<link rel="stylesheet" href="style.css" />
<script src="script.js"></script>
</head>
<body ng-controller="DpdController">
<select name="repeatSelect" id="repeatSelect" ng-model="selectedValue" ng-change="logValue(selectedValue)" ng-options="option.name for option in data">
</select>
<span ng-if='selectedValue'>
<select name="repeatSelect1" id="repeatSelect1" ng-model="selectedValue1"
ng-change="logValue(selectedValue1)"
ng-options="option.name for option in selectedValue.subChildren">
</select>
</span>
</body>
</html>

Related

What is the closest solution to this Idea/Problem I have with forms in HTML?

I want to make a select form in HTML that checks that displays a secondary select group if certain options in the first select group are selected
<body>
<form name="Test">
<!-- all the factors to account for when calculating odds-->
<div>
<label>
<select name="FirstList" id="FirstListID">
<option value="1">First option</option>
<option value="2">Second option</option>
<option value="3">Third option</option>
</select><br>
<!-- SecondList would be visible if "1" is selected-->
<label name="SecondList" style="display:none">List for "1":
<select name="SecondListSelect" id="SecondListSelectID">
<option value="3">Placeholder</option>
</select><br>
</label>
<!-- ThirdList would be visible if "2" is selected-->
<label name="ThirdList" style="display:none">List for "2":
<select name="ThirdListSelect" id="ThirdListSelectID">
<option value="4">Placeholder</option>
</select><br>
</label>
<!-- No secondary select form appears if "3" is selected-->
</div>
</form>
</body>
I've tried using AddEventListeners but the code doesn't appear that maintainable since I plan on adding more options in the primary drop down menu so I would need to constantly add what secondary select groups appear based on what primary option is selected. How could I go about coding this in JS?
Give each <label> an identifier tying it to the value for which it should be visible. A data attribute would work. Then, whenever the <select> changes, iterate over all such labels and hide them. Take the .value from the select and use string concatenation to construct a selector for the element with that in the dataset, and then you can select that element and show it.
For example, with labels like
<label data-option=1>
you could have
for (const label of document.querySelectorAll('[data-option]')) {
label.style.display = 'none';
}
document.querySelector(`[data-option=${select.value}]`).style.display = 'block';
inside the change listener.
Store the options of the second <select> element in an object. Make sure the keys match the value attribute value of the options in the first <select>.
Listen for a change event on the first <select>. Whenever a change happens, empty the options of the second <select>, get the new options from the object and create new <option> elements with that data.
Now you have a dynamic <select> element that is easy to scale.
const optionData = {
'1': [
{
label: 'Foo',
value: 'foo',
}
],
'2': [
{
label: 'Bar',
value: 'bar',
},
{
label: 'Baz',
value: 'baz',
}
],
'3': [
{
label: 'Hello',
value: 'hello',
},
{
label: 'World',
value: 'world',
}
]
};
const firstList = document.querySelector('#first-list');
const secondList = document.querySelector('#second-list');
function removeOptions(selectElement) {
for (let i = selectElement.options.length - 1; i >= 0; i--) {
selectElement.remove(i);
}
}
// Update the second `<select>` based on the first's selection.
firstList.addEventListener('change', event => {
const value = event.target.value;
const options = optionData[value];
removeOptions(secondList);
for (const { label, value } of options) {
const option = new Option(label, value);
secondList.add(option);
}
});
<label for="first-list">List One</label>
<select name="first-list" id="first-list">
<option value="" selected disabled>Make a selection</option>
<option value="1">First option</option>
<option value="2">Second option</option>
<option value="3">Third option</option>
</select>
<label for="second-list">List Two</label>
<select name="second-list" id="second-list">
<option value="" selected disabled>No options yet</option>
</select>
You could build the selectors from your js file:
const optionGroups = {
"first": [
{ "text": "for first - 1", "value": 1 }
],
"second": [
{ "text": "for second - 1", "value": 1 },
{ "text": "for second - 2", "value": 2 }
],
"third": [
{ "text": "for third - 1", "value": 1 },
{ "text": "for third - 2", "value": 2 },
{ "text": "for third - 3", "value": 3 }
]
},
mainSelect = document.querySelector('#mainSelect'),
secondarySelect = document.querySelector('#secondarySelect');
async function startWithMainSelect()
{
for (let key of Object.keys(optionGroups))
{
const option = new Option(key);
mainSelect.appendChild(option);
}
onChangeMainSelect();
}
function onChangeMainSelect()
{
while (secondarySelect.firstChild)
secondarySelect.firstChild.remove();
for (let _option of optionGroups[mainSelect.value])
{
const option = new Option(_option.text, _option.value);
secondarySelect.appendChild(option);
}
}
mainSelect.addEventListener('change', onChangeMainSelect);
startWithMainSelect();
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>replit</title>
<link href="style.css" rel="stylesheet" type="text/css" />
</head>
<body>
<h4>Main:</h4>
<select id="mainSelect"></select>
<br>
<h4>Secondary:</h4>
<select id="secondarySelect"></select>
<script src="https://replit.com/public/js/replit-badge.js" theme="blue" defer></script>
<script src="script.js"></script>
</body>
</html>
If there were a lot of options, you could move them into a json file and request them from there. Here's an example.

How to use v-model and v-select dynamically

I have a group of selects but want them all to work differently with v-model without creating separate data properties for them all.
In the example, select 1 and select 2 are one group and select 3 and select 4 are another.
When one select select 1, the expected behavior should be that, it does not affect select 2, etc.
My guess is may be a computed property for this? but can't get my head around the proper implementation
Js fiddle: https://jsfiddle.net/uxn501h2/8/
Code snippet:
new Vue({
el: '#app',
template: `
<div>
Select 1: <select v-model="selectedPerson">
<option v-for="person in people" :value="person.key">{{person.name}}</option>
</select>
Select 2: <select v-model="selectedPerson">
<option v-for="person in people" :value="person.key">{{person.name}}</option>
</select>
<h4>Selected 1 person key : {{selectedPerson}}</h4>
<h4>Selected 2 person key: {{selectedPerson}}</h4>
<br/>
Select 3: <select v-model="selectedPersonTwo">
<option v-for="person in people" :value="person.key">{{person.name}}</option>
</select>
Select 4: <select v-model="selectedPersonTwo">
<option v-for="person in people" :value="person.key">{{person.name}}</option>
</select>
<h4>Selected 3 person Two key: {{selectedPersonTwo}}</h4>
<h4>Selected 4 person Two key: {{selectedPersonTwo}}</h4>
</div>
`,
data: {
people: [{
key: 1,
name: "Carl"
},
{
key: 2,
name: "Carol"
},
{
key: 3,
name: "Clara"
},
{
key: 4,
name: "John"
},
{
key: 5,
name: "Jacob"
},
{
key: 6,
name: "Mark"
},
{
key: 7,
name: "Steve"
}
],
selectedPerson: "",
selectedPersonTwo: ""
}
});
.required-field > label::after {
content: '*';
color: red;
margin-left: 0.25rem;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.0.3/vue.js"></script>
<body>
<div id="app"></div>
</body>
It looks to me that you'd like to use two-way binding, but with more control between them in the middle. My suggestion would be:
Do not use <select v-model="selectedPerson">
Instead split them to <select :value="selectedPersonValue" #input="selectedPersonOutput> (which is basically the same thing, but you can specify different input and outputs), where selectedPersonValue can be a computed property or a regular property in data(), and selectedPersonOutput should be a method that will be called when select value changes.
This way you can directly decide what happens on each step.
PS: If you want to influence your property selectedPersonValue from a method, you might consider changing it to data() property and add a watch to it. See what works best for you.

Angularjs: select not working when ng-model a json object

Once I use ng-options for a select I am unable to get dafault selection to work on nested json objects.
once I have a bit more complicated json and the select should handle a child object my select does not default select the proper value.
Given test = {"id":3,"title":"Test","product":{"id":4,"name":"Test1"}} as my ng-model test.product and
[{
"id": 4,
"name": "Test1"
}, {
"id": 5,
"name": "Test2"
}]
as my selection option. (see http://embed.plnkr.co/mpnislw77UBSEdHl4UKN/)
I seem to be unable to figure out how to facilitate default selection.
If you use track by item.id it works - http://embed.plnkr.co/mpnislw77UBSEdHl4UKN. The marked answer was not very obious since the ng-model is nested in iself. but it contains the correct information.
The only problem with your code is that you've assigned a new object to $scope.test.product and you're using it as the ng-model of the dropdown.
This makes AngularJS unable to find it inside the possible values, which are $scope.testarray. AngularJS will compare two objects by their reference, which you broke when you assigned a new object to $scope.test.product.
To make it working, change $scope.test as follows:
$scope.test = {
"id": 3,
"title": "Test",
"product": $scope.testarray[1]
}
This is how to select with ngOptions and setting a default value
Example
angular.module('defaultValueSelect', [])
.controller('ExampleController', ['$scope', function($scope) {
$scope.data = {
availableOptions: [
{id: '1', name: 'Option A'},
{id: '2', name: 'Option B'},
{id: '3', name: 'Option C'}
],
selectedOption: {id: '3', name: 'Option C'} //This sets the default value of the select in the ui
};
}]);
<html>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.4/angular.min.js"></script>
</head>
<body ng-app="defaultValueSelect">
<div ng-controller="ExampleController">
<form name="myForm">
<label for="mySelect">Make a choice:</label>
<select name="mySelect" id="mySelect"
ng-options="option.name for option in data.availableOptions track by option.id"
ng-model="data.selectedOption"></select>
</form>
<hr>
<tt>option = {{data.selectedOption}}</tt><br/>
</div>
</body>
</html>
try this, hope it will help
<option value="" ng-if="false"></option>

How to output selected object properthy name to paragraph in angularJs

I have three variables in my maincontroller, one for select person, one for genre associated with selected person name, and an array of objects with data. I bind the data together in select element enviroment, but now my goal is to output selected items in paragraph for further manipulation, i managed to output selected person but it appears in vertical aligment. Can someone show me how to output for example in my case let's say this, I select Chris and in my paragraph I want to get "Genre that Chris listen are Indie, Drumstep, Dubstep and Electro"
myApp.controller('mainController', function($scope){
$scope.selectedPerson = 0;
$scope.selectedGenre = null;
$scope.people = [
{ id: 0, name: 'Leon', music: [ 'Rock', 'Metal', 'Dubstep', 'Electro' ] },
{ id: 1, name: 'Chris', music: [ 'Indie', 'Drumstep', 'Dubstep', 'Electro' ] },
{ id: 2, name: 'Harry', music: [ 'Rock', 'Metal', 'Thrash Metal', 'Heavy Metal' ] },
{ id: 3, name: 'Allyce', music: [ 'Pop', 'RnB', 'Hip Hop' ] }
];
});
HTML Template
<!DOCTYPE html>
<head>
<title>Learning AngularJS</title>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.min.js" type="text/javascript"></script>
<script type="text/javascript" src='app.js'></script>
<script type="text/javascript" src='maincontroller.js'></script>
</head>
<body>
<div id='content' ng-app='myAng' ng-controller='mainController'>
<select ng-model='selectedPerson' ng-options='list.name for list in people'></select>
<select ng-model='selectedGenre'>
<option ng-repeat='genre in people[selectedPerson.id].music'>{{genre}}</option>
</select>
<p ng-model='people' ng-repeat='person in people[selectedPerson.id].name'>{{person}}</p>
</div>
</body>
</html>
i managed to output selected person but it appears in vertical aligment.
You run ng-repeat directive on single String. this is a reason why you get something like:
C
h
r
i
s
Maybe something like:
<p>Genre that {{people[selectedPerson.id].name}} listen are {{people[selectedPerson.id].music.join(', ')}}<p>
Demo
<select ng-model='selectedPerson' ng-options='list as list.name for list in people'>
<option value="">-- Select Person --</option>
</select>
<select ng-model='selectedGenre'>
<option ng-repeat='genre in people[selectedPerson.id].music'>{{genre}}</option>
</select>
<p>Genre that {{people[selectedPerson.id].name}} listen are {{people[selectedPerson.id].music.join(', ')}}<p>

Dependent Select Angular JS

I have hierarchical data set. There is one fixed root unit.
What I want to do is to make this tree browsable with dependent selects.
I have created a simple plunkr example with a fixed dataset.
http://plnkr.co/edit/Bz5A1cbDLmcjoHbs5PID?p=preview
The data format in the example mimics the format I would get from a server request in "real" life.
This working fine in this simple first step. What is missing is, that when a user changes a selection somewhere in the middle, the select boxes and the ng-model binding below the new selection need to be destroyed.
So when I select Europe->France->Quimper and change "Europe" to "Asia" - then there should be "Asia" as the first select box and a second one the Asia countries.
Is there an "Angular" way to deal to deal with this? Any other hint is appreciated also ;)
<!DOCTYPE html>
<html ng-app="app">
<head>
<link data-require="bootstrap#3.3.5" data-semver="3.3.5" rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" />
<script src="https://code.angularjs.org/1.3.17/angular.js" data-semver="1.3.17" data-require="angular.js#1.3.17"></script>
</head>
<body>
<div ng-controller="Ctrl">
<select ng-repeat="select in selects track by $index" ng-model="$parent.boxes[$index]">
<option ng-repeat="child in select.children" ng-click="expandSelects(child)">{{child.name}}</option>
</select>
<ul>
<li ng-repeat="item in boxes">{{ item }}</li>
</ul>
</div>
<script>
var app = angular.module('app', []);
app.controller('Ctrl', ['$scope', function($scope) {
var data = {
'europe': {
name: 'europe',
children: [{
name: 'france',
parent: 'europe'
}, {
name: 'italy',
parent: 'europe'
}],
},
'asia': {
name: 'asia',
children: [{
name: 'japan',
parent: 'asia'
}, {
name: 'china',
parent: 'asia'
}],
},
'france': {
name: 'france',
children: [{
name: 'paris',
parent: 'france'
}, {
name: 'quimper',
parent: 'france'
}]
}
};
var root = {
name: 'world',
children: [{
name: 'europe',
parent: 'world'
}, {
name: 'asia',
parent: 'world'
}, ]
};
$scope.selects = [root];
$scope.expandSelects = function(item) {
var select = data[item.name];
if (select) {
$scope.selects.push(select);
}
}
$scope.$watch('boxes', function(item, old) {
}, true);
}]);
</script>
</body>
</html>
This is a classic example of cascading dropdowns, with the added challenge of an unknown number of levels in the cascade. I combined the data set into one object for simplicity, added labels for the dropdowns, and simplified the select element.
This solution allows for any number of levels, so if you needed data below the city level, you could add it without changing any code, as illustrated by the "Street" example I added to Paris.
select {
display: block;
}
<!DOCTYPE html>
<html ng-app="app">
<head>
<link data-require="bootstrap#3.3.5" data-semver="3.3.5" rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" />
<script src="https://code.angularjs.org/1.3.17/angular.js" data-semver="1.3.17" data-require="angular.js#1.3.17"></script>
</head>
<body>
<div ng-controller="Ctrl">
<div ng-repeat="select in selects track by $index" ng-if="select.children">
<label>{{ select.optionType }}</label>
<select ng-model="selects[$index + 1]" ng-options="child.name for child in select.children" ng-change="clearChildren($index)"></select>
<hr />
</div>
</div>
<script>
var app = angular.module('app', []);
app.controller('Ctrl', ['$scope', function($scope) {
var data = {
optionType: 'Continent',
name: 'World',
children: [
{
optionType: 'Country',
name: 'Europe',
children: [
{
optionType: 'City',
name: 'France',
children: [
{
optionType: 'Street',
name: 'Paris',
children: [
{
name: 'First'
},
{
name: 'Second'
}
]
},
{
name: 'Quimper'
}
]
},
{
name: 'Italy'
}
]
},
{
optionType: 'Country',
name: 'Asia',
children: [
{
name: 'Japan'
},
{
name: 'China'
}
]
}
]
};
$scope.selects = [data]
$scope.clearChildren = function (index) {
$scope.selects.length = index + 2;
};
}]);
</script>
</body>
</html>
To go to the children in your hierachy is not as hard as it may seem. If you set up your select with angular and let it do most of the selection for you (for example using ng-options instead of ng-repeating the tag itself), and tell it what options there are, then the list of children you are trying to render just becomes a standard ng-repeat of the children that were picked from the select above.
I modified your plunker to show you how you could accomplish that a slightly different way.
http://plnkr.co/edit/zByFaVKWqAqlR9ulxEBt?p=preview
Main points I changed were
$scope.expandSelects = function() {
var select = data[$scope.selected.name];
if (select) {
console.log('changed');
console.log(select);
$scope.chosen = select;
}
}
Here i just grab the chosen item which the will use. Then the ends up looking like.
<ul>
<li ng-repeat="item in chosen.children">{{ item.name }}</li>
</ul>
The only other set up that was really needed was setting up the with ng-options and giving it a model to bind to.
<select ng-options="child.name for child in selects.children"
ng-model="selected" ng-change="expandSelects()">
</select>
Use can use a filter on the second select to filter de options based on the previous selection.
For example, you can have a first selection to choose the continent:
<select ng-options="c for c in continents" ng-model="selectedContinent" ></select>
and a second selection for the coutries:
<select ng-options="c.name for c in countries | filter : {parent:selectedContinent}" ng-model="selectedCountry" ></select>
Made a fiddle with a simplified data structured just to show how the filter works: http://jsfiddle.net/marcosspn/oarL4n78/

Categories