I don't know what else to do. I want to Create a set of cascading select lists, where the selection of an item from the first list determines which list to display in the second list.I would like to know how to do it if someone could give me a clearer explanation please
let data = [
{ item: 'Fruits', subitems: ['apple', 'banana', 'pineapple', 'watermelon'] },
{ item: 'Meals', subitems: ['chicken', 'bacon', 'pork', 'beef'] },
{ item: 'Animals', subitems: ['cat', 'rabbit', 'mouse', 'lion'] },
{
item: 'Brands Laptops',
subitems: ['Dell', 'HP', 'Apple', 'Sony'],
},
];
window.onload = function() {
var itemSel = document.getElementById("item");
var subitemSel = document.getElementById("subitems");
for (var x in data) {
itemSel.options[itemSel.options.length] = new Option(x, x);
}
itemSel.onchange = function() {
//empty
subitemSel.length = 1;
//display correct values
for (var y in itemObject[this.value]) {
subitemSel.options[subitemSel.options.length] = new Option(y, y);
}
}
}
<!DOCTYPE html>
<html lang="en-CA">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script defer type="module" src="./js/main.js"></script>
</head>
<body>
<header>
<h1>Moshe Brito</h1>
<h2>41088278</h2>
</header>
<main>
<!-- content will be built here by javascript -->
<div>
<label for="first">First List</label>
<select id="first" name="first">
<option value="" selected="selected">Please select first</option>
</select>
</div>
<div>
<label for="second">Second List</label>
<select id="second" name="second">
<option value="" selected="selected">Please select second</option>
</select>
</div>
</main>
</body>
</html>
You use invalid value for getElementById().
Those must refer to real HTML id attribute but you refer all of them to not exists.
The result in console showing error TypeError: itemSel is null.
The select text in Option() is in first argument. See reference.
If you use just x in data then x will be index number. It is okay to use it in value attribute but select text should be readable. So, it should be data[x].item in the first Option() argument.
Now go to inside the onchange event. You use undeclared itemObject variable and when first select box has been changed, it will be error here.
This variable must be replace with data[this.value].subitems if you want to list sub items property.
Full code.
let data = [{
item: 'Fruits',
subitems: ['apple', 'banana', 'pineapple', 'watermelon']
},
{
item: 'Meals',
subitems: ['chicken', 'bacon', 'pork', 'beef']
},
{
item: 'Animals',
subitems: ['cat', 'rabbit', 'mouse', 'lion']
},
{
item: 'Brands Laptops',
subitems: ['Dell', 'HP', 'Apple', 'Sony'],
},
];
window.onload = function() {
var itemSel = document.getElementById("first");
var subitemSel = document.getElementById("second");
for (var x in data) {
itemSel.options[itemSel.options.length] = new Option(data[x].item, x);
}
itemSel.onchange = function() {
//empty
subitemSel.length = 1;
//display correct values
for (var y of data[this.value].subitems) {
subitemSel.options[subitemSel.options.length] = new Option(y, y);
}
}
}
<header>
<h1>Moshe Brito</h1>
<h2>41088278</h2>
</header>
<main>
<!-- content will be built here by javascript -->
<div>
<label for="first">First List</label>
<select id="first" name="first">
<option value="" selected="selected">Please select first</option>
</select>
</div>
<div>
<label for="second">Second List</label>
<select id="second" name="second">
<option value="" selected="selected">Please select second</option>
</select>
</div>
</main>
Related
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.
I am a complete beginner making a site for fun/skill development. I saw this code snippet online for cascading dropdowns, but when I run the below code, the form value for "First" comes through as the index number for the value I see in the dropdown (eg. Meals comes through as a 1).
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<script>
let data = [{
item: 'Fruits',
subitems: ['apple', 'banana', 'pineapple', 'watermelon']
},
{
item: 'Meals',
subitems: ['chicken', 'bacon', 'pork', 'beef']
},
{
item: 'Animals',
subitems: ['cat', 'rabbit', 'mouse', 'lion']
},
{
item: 'Brands Laptops',
subitems: ['Dell', 'HP', 'Apple', 'Sony'],
},
];
window.onload = function() {
var itemSel = document.getElementById("first");
var subitemSel = document.getElementById("seconda");
var subitem2Sel = document.getElementById("secondb")
for (var x in data) {
itemSel.options[itemSel.options.length] = new Option(data[x].item, x);
}
itemSel.onchange = function() {
//empty
subitemSel.length = 1;
subitem2Sel.length = 1;
//display correct values
for (var y of data[this.value].subitems) {
subitemSel.options[subitemSel.options.length] = new Option(y, y);
}
for (var z of data[this.value].subitems) {
subitem2Sel.options[subitem2Sel.options.length] = new Option(z, z);
}
}
}
</script>
</head>
<body>
<h1>Cascading Dropdown Example</h1>
<form name="form1" id="form1" action="/action_page.php">
First:
<select name="first" id="first">
<option value="" selected="selected">Select type</option>
</select>
<br><br> Second A:
<select name="seconda" id="seconda">
<option value="" selected="selected">Please select first</option>
</select>
<br><br> Second B:
<select name="secondb" id="secondb">
<option value="" selected="selected">Please select first</option>
</select>
<br><br>
<input type="submit" value="Submit">
</form>
</body>
I know how to alter the code to get the value of First to come through as the text, but that then messes up the cascading dropdowns. How can I get the form, upon hitting submit, to give me the text value from "First" and not the index number of whatever is selected?
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>
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>
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/