Create a dynamic multiple dropdown select - javascript

I'm fetching a list of objects from a database, say articles, that have a category attribute, and I'll be adding a filtering capability to my angularjs app where I can select multiple articles based on the subcategories, grouped by category
I'm trying to do as follows in my html:
<select multiple >
<option value="" disabled selected>Choose your option</option>
<optgroup ng-repeat="category in categories" label="{{category.category}}">
<option ng-repeat="subcategory in category.subcategories" value="{{subcategory}}">{{subcategory}}</option>
</optgroup>
</select>
but the categories and subcategories can be diverse and I don't want to hardcode it on my app, rather, grouping that information from all the articles I retrieve from the database, so in my Controller, in the function I use to fetch all the articles I have the following
function getAllArticles(){
var promise = article.getAll();
promise.then(function( articles ){
$scope.articles = articles.data
var result = $scope.articles.map(function(a) {return a.category.category;});
var res = arrayUnique(result);
for(var i = 0; i < res.length; i++){
$scope.categories[i] = {'category': res[i] }
var result2 = $scope.articles.map(function(a) {
if (a.category.category === res[i]) {
return a.category.subcategory;
}
});
$scope.categories[i]['subcategories']
$scope.categories[i]['subcategories'] = arrayUnique(result2);
}
});
}
var arrayUnique = function(a) {
return a.reduce(function(p, c) {
if (p.indexOf(c) < 0 && c != undefined) p.push(c);
return p;
}, []);
};
In a way I'm using map/reduce to get the categories and subcategories, but my problem is that with all of these, in my html, the ng-repeat doesn't show anything, as if the $scope.categories is still empty, even we I console.log it I get the following result:
{ 0: {category: "category1",
subcategories: [{subcategory: "sub1"},{subcategory: "sub2"}]
},
1: {category: "category2",
subcategories: [{subcategory: "sub1"},{subcategory: "sub2"}]
}, ...
}
EDIT:
when I do the following:
<div ng-repeat="category in categories">
{{category.category}}
<div ng-repeat="subcategory in category.subcategories">{{subcategory}}</div>
</div>
It prints as it should the list of categories and subcategories, the main difference is that I'm using <div> instead of <optgroup> <option>

If your console.log is accurate then you are using ng-repeat over an object not an array. This is doable but requires special syntax such as:
<div ng-repeat="(key, value) in myObj"> ... </div>
you can find the documentation here.
Otherwise, try translating the results into an array before using them in ng-repeat.
As a side note, angular offers the ng-options directive for ng-select. That way you can assign a data model to it instead of hard coding a template.

The problem was with materializecss select. It was instantiating before updating $scope.categories, which was empty
The solution can be
$(document).ready(function() {
$('select').material_select();
$('input.select-dropdown').click(function(e){
$('select').material_select();
});
});
solves the problem but it's a horrible hack, but I'll be moving to a timeout solution or instantiating after updating $scope.categories

Related

Angular Populate dropdown options dynamically while selecting dropdown in runtime

I have a JSON object containing names of dropdown and looks something like this -
$scope.obj = {
"dp1" :{},
"dp2" :{}
}
Objects dp1 and dp2 correspond to respective dropdowns. These objects will be referred by dropdown to populate their options tag.
Next I have a REST call function like this -
$scope.getData = function(category, type) {
var params = { "dp1" : category, "dp1__type": type};
PromiseService.getPromiseData("GET", "/api/get_data/?" + $httpParamSerializer(params)).then(function(response) {
$scope.obj.dp1= response;
});
}
I am able to assign the response to $scope.obj.dp1 The reponse object looks like this-
{ "id" : 1, "name" : "john" }
Finally, my dropdown looks like this -
<select id="d1" ng-model="d1">
    <option ng-repeat="opt in obj.dp1" t>{{opt.id}}_{{opt.name}}</option>
</select>
I want to populate the obj JSON object based on response, which I able to. Then I want to go to sub object in obj and apply ng-repeat on that to populate the options tag.
Current implementation of ng-repeat gives me undefined undefined .
try this-
$scope.obj = {
"dropdown1" :{id:"dp2", data:"", options:[]},
"dropdown2":{id:"dp1", data:"", options:[]}
}
$scope.getData = function(category, type){
var params = { "dp1" : category, "dp1__type": type};
PromiseService.getPromiseData("GET", "/api/get_data/?" + $httpParamSerializer(params)).then(function(response){
$scope.obj.dropdown1.options = response;
});
}
html page -
<div ng-repeat="dropdown in obj">
<select id="{{dropdown.id}}" ng-model="dropdown.data" ng-options="option.name for option in dropdown.options">
</div>

Cascading selects AngularJS filter not display data in second time

Considering the code below:
<html ng-app="app">
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.3/angular.min.js"></script>
</head>
<body ng-controller="appCtrl">
<div class="filters">
<div>
Color: <select id="color" ng-model="colorfilter" ng-options="car.color as car.color for car in cars | unique:'color'" ng-change="changeColor()">
</select>
Model: <select id="model" ng-disabled="!colorfilter" ng-model="modelfilter" ng-options="car.model as car.model for car in cars | filter:{color:colorfilter} | unique:'model'" ng-change="changeModel()">
</select>
</div>
</div>
<div class="list">
<p class="car" ng-repeat="car in cars | filter:colorfilter | filter:modelfilter">{{car.make}} :: {{car.model}} | {{car.color}}</p>
</div>
</body>
</html>
And the controller.js:
angular.module('app', [])
.filter('unique', function() {
return function(input, key) {
var unique = {};
var uniqueList = [];
for(var i = 0; i < input.length; i++){
if(typeof unique[input[i][key]] == "undefined"){
unique[input[i][key]] = "";
uniqueList.push(input[i]);
}
}
return uniqueList;
};
})
.controller('appCtrl', function($scope) {
// define list of cars
$scope.cars = [
{make:"Dodge", color:"Blue", model:"Dakota"},
{make:"Chevy", color:"Black", model:"Aveo"},
{make:"Honda", color:"Black", model:"Accord"},
{make:"Toyota", color:"Red", model:"Corolla"}
//... other lines
];
// initialize filter object
$scope.filter = {};
});
The list of cars is displayed complete below the select fields. When I filter at the first time, the first select normally filters both the list of cars (considering the selected color) and the second select data (only models that have the selected color). And the second select also filters the list of cars considering the selected model. That sounds perfect!
However, when I try to perform a new filter, when I choose another color, it does not display any records in the list of cars, but it usually works by filtering the data of the second select. By choosing one of the models available in the second select, the list is displayed, exactly the combination of the two selections (such as an AND of the selects).
It seems that only the list data that is currently being viewed on the screen are considered for the first select, needing to wait for the second select to display the result. But my intend is to display the results to each filter, always searching for the complete list of items.
My full code on Codepen.
I think this may be what you're looking for. Based on your ng-disabled logic, I figured you want the user to always go in the order of selecting color then model.
https://codepen.io/Cameron64/pen/YZbrqW?editors=1010
To maintain this order I set the color dropdown to reset when the model dropdown is selected from. Additionally I grouped the filter into one object so that it isn't so verbose when passed to the repeater
$scope.changeColor = function() {
$scope.filter.model = undefined;
$scope.filter.color = $scope.filter.color || undefined;
}
Update:
You need to set the null value for modelFilter to reset the filter.
Your code is working fine however when you select a value in second dropdown, you are setting the value to modelfilter which is causing the issue.
Try to insert an empty dropdown option in both the dropdowns so that when you select first dropdown it will reset to default state and modelFilter won't apply.
Working Demo:
CodePen

Angular ng-options selected object

why is this not working?
HTML:
<div ng-controller="MyCtrl">
<select class="rp-admin-rooms-selected-select" name="teacherSelect" id="teacher"
ng-model="teacherSel"
ng-options="teacher.name for teacher in teachers "
>
</select>
{{teacherSel}}
</div>
JS:
var myApp = angular.module('myApp',[]);
//myApp.directive('myDirective', function() {});
//myApp.factory('myService', function() {});
function MyCtrl($scope) {
$scope.teachers = [
{name:'Facundo', id:1},
{name:'Jorge', id:3},
{name:'Humberto', id:5},
{name:'David', id:7},
]
$scope.teacherSel = {name:'Facundo', id:1};
}
I would expect to be the selected element be Facundo
The thing is, I know that its possible to do this via teacherSel = id
and ng-options="teacher.name as teacher.id"...
But I have the object yet their, and I need the new object. not just the id.
jsfiddle:
http://jsfiddle.net/Lngv0r9k/
Michael Rose got it right. Another option would be force angular to do the comparison by value using a track by statement, as follows:
ng-options="teacher.name for teacher in teachers track by teacher.ID"
This works in angular 1.2 and after.
Updated fiddle: http://jsfiddle.net/Lngv0r9k/3/.
The issue is that the comparison for the selected entry is done via reference, not value. Since your $scope.teacherSel is a different object than the one inside the array - it will never be the selected one.
Therefore you need to find the selected entry inside the array and then use this as follows: $scope.teacherSel = $scope.teachers[indexOfSelectedEntry].
See at the bottom of your updated jsfiddle: http://jsfiddle.net/Lngv0r9k/1/
On your example you dont give a teacher object to the room.teacher , so the ng-options cant match anything to the ng-model.
As you see on the screen below, value=? means that it cant find correct value to match up.
you could try for example:
$scope.room = {
teacher: $scope.teachers[an item index];
};
OR
$scope.room = {
teacher: {
"ID": "1",
"name": "Adolf Ingobert",
"short": "AD",
"display": "Adolf I.",
"sectionFK": "2",
"invisible": "0"
};
};

How to programmatically select ng-option value?

I have a view that is filled with dropdownlists to filter a report. I also have a view of saved filters that are displayed as links. When a user clicks on their saved filters, I want the appropriate values of the dropdownlists to be selected. The drop downs are being populated properly. On the saved filter link there is an ng-click that will call a function that iterates through the collection of saved filter values and automatically selects the correct one. I cannot figure out how to programmatically set the selected option. Any help is much appreciated!
<select uid="locSelect"
class="span12"
ng-model="reportDetail.selectedLoc"
ng-options="loc.dbid as loc.serviceName for loc in reportDetail.locList | orderBy:'name'">
<option uid="unselectedLocOption" value="">-- Select One --</option>
</select>
Here is the list of saved filters:
<div class=" well fixed-search" style="overflow-x: hidden;overflow-y: auto;">
<div class="well-header">
Saved Filters
</div>
<div ng-if="!hasSavedFilters">
<span>No saved filters</span>
</div>
<ul ng-if="hasSavedFilters" class="nav nav-list dashboard-list">
<li ng-repeat="filter in reportDetail.savedFilters">
<a uid="savedFilter" href="" ng-click="reportDetail.loadSavedFilters(filter.filters)">
<span ng-bind="filter.title"></span>
</a>
</li>
</ul>
And here is my controller
(function(){
'use strict';
var ReportDetailController = function(ReportsService, $scope){
var _locList = {};
var _hospitalStatusList = {};
var _providerStatusList = {};
var _savedFilters = [];
var _sourceTypeList = {};
var _dateRangeList = {};
var _init = function(){
ReportsService.getCurrentReportSavedFilters().then(function(data){
$scope.reportDetail.savedFilters =data;
$scope.hasSavedFilters = ReportsService.hasSavedFilters();
});
ReportsService.getLOCListForDDL().then(function(data){
$scope.reportDetail.locList = data;
//$scope.reportDetail.selectedLoc = $scope.reportDetail.locList[0];
});
ReportsService.getSelectListData()
.then(function(data){
$scope.reportDetail.sourceTypeList = data.CONNECTION_TARGET_STATUS;
$scope.reportDetail.hospitalStatusList = data.CONNECTION_SOURCE_STATUS;
});
ReportsService.getDateRangesForDDL()
.then(function(data){
$scope.reportDetail.dateRangeList = data;
});
$scope.reportDetail.providerStatusList = ReportsService.getProviderStatusForDDL();
};
var _loadSavedFilters = function(filters){
for(var i = 0, l = $scope.reportDetail.locList.length; i<l; i++){
if($scope.reportDetail.locList[i].serviceName == filters.levelOfCare){
$scope.reportDetail.selectedLoc = $scope.reportDetail.locList[i];
console.log($scope.reportDetail.selectedLoc);
}
}
}
var _isActive = function(filter){
for(var i = 0, l = $scope.reportDetail.savedFilters.length; i<l; i++){
if(filter.title == $scope.reportDetail.savedFilters[i].title){
return true;
}
return false;
}
}
var _generateReport = function(){
return ReportsService.generateReport();
};
$scope.reportDetail = {
init: _init,
selectedLoc: null,
isActive: _isActive,
locList: _locList,
selectedHospitalStatus: 'NOTIFIED',
hospitalStatusList: _hospitalStatusList,
selectedProviderStatus: 'NEW',
providerStatusList: _providerStatusList,
selectedSourceType: 'CONNECTED',
sourceTypeList: _sourceTypeList,
selectedDateRange: '',
dateRangeList: _dateRangeList,
savedFilters: _savedFilters,
loadSavedFilters: _loadSavedFilters,
generateReport: _generateReport
};
$scope.reportDetail.init();
};
app.controller('ReportDetailController', ['ReportsService', '$scope', ReportDetailController]);
})();
You just need to set the ng-model to whatever it should be, so in this case you would set reportDetail.selectedLoc to whatever loc.dbid it should be.
For example: http://jsfiddle.net/uWLua/1/
Note: Make sure they have the same type, so in your example make sure they are either both integers, or both strings, it will not know they are the same if you have one as 5073 and one as "5073"
I updated the fiddle to show that the string and number do not do the same thing.
The ng-model and the expression feeding ng-options -must- match in order for Angular to compare values and see what option is 'selected'. Just as 'dave' indicated.
Due to time constraints I ended up going a different route. I created an event bus of sorts in my service layer and subscribe to the even in my controller, updating the model, and used ng-repeat with ng-selected.
I'm still interested to understand why this was not working with ng-options. The model and ng-options types matched, and everything appeared to be wired up correctly. When I have more time i'll re-address the original issue. Thanks for all who responded!
You need custom directive, or something similar to this two approaches
<div ng-controller="MyCtrl">
<h1>Approach 1</h1>
<span ng-repeat="val in dbs">
<input type="checkbox" ng-model="val.checked">{{val.name}}
</span>
<hr/>
<h1>Approach 1</h1>
<select multiple>
<option ng-repeat="val in dbs" name="val.name" value="val.name" ng-selected="val.checked">{{val.name}}</option>
</select>
<h4>Source (note scope changes)</h4>
{{dbs}}
</div>
also you can use ng-change to do some complex ops
If I understand, in summary, you have a select filled with a list, and you want to programmatically set one of those to be selected, type it as the default right?
If so, you can easily solve this with ng-options, just associate your controller instance with scope and assign the position of the list you want to the model of select, for example:
Select HTML
<select ng-model="vm.aluno_id" name="aluno_id" ng-options="aluno.nome for aluno in alunos">
Controller
app.controller("auxiliarController", function( $scope){
//instancia controller;(Controller instance;)
var vm = this;
$scope.vm = vm;
//carregando lista no scope, que será utilizado pelo angular no select
//Loading list in scope, which will be used by angular in select
$scope.alunos = [{id: 1, nome: "aa"}, {id: 2, nome: "bb"}];
//setando item default no select
$scope.vm.aluno_id = $scope.alunos[0];
});
I hope I have helped

Jquery - Array manipulation from a select box

I'm creating a tag suggestions function, depending of a category. So, I have a select box with a bunch of categories, when I select a category, I want to display the sub-categories (using an array obviously) in a list. Here's what I have now:
<select id="categorySelect">
<option value="6">Animal</option> //the value here is the category id
<option value="12">Music</option>
</select>
<ul id="suggestedTags">
</ul>
my JSON array:
var tagsMakers= [
{ category: 'Animal', suggestedTags: [
{ name: 'cat'},
{ name: 'dog' },
{ name: 'rabbit'}
]},
{ category: 'Music', suggestedTags: [
{ name: 'rock' },
{ name: 'rap' }
]}
];
$("#categorySelect").change(function(){
});
I'm still learning array manipulations, and I don't know where to start!
In words the logic is:
When I select a category, I want to display every suggested tags for that category in the li below. I also want to be able to chose multiple categories, so if I select both categories, I want the suggested tags for both to show.
Anyone have a little time to help?
Yeah, first bind an event to the select tag, and based on the value, display the list. Also remove the JSON structure, and build a hash instead:
tagMakers = {'Animal': ['Cat','Dog','Rabbit'], 'Music': ['rock','rap']}
$("#categorySelect").change(function() {
$("#suggestedTags").empty();
$(this).find(":selected").each(function() {
var selected = $(this).text();
$.each(tagMakers[selected].function(i,n) {
$("#suggestedTags").append("<li>"+n+"</li>");
});
});
});
Your array of tags doesn't have the category id, so I'll use the category name here. But, as a good practice, better if you put the category id in the categories array. Better yet if you use a object hash instead of an array.
function getCategoryByName(name){
//search in array.
for(var i = 0, len = tagsMakers.length; i < len; i++)
{
if (tagsMakers[i].category === name)
{
// found.
return tagsMakers[i];
}
}
// do not exists
return;
}
$("#categorySelect").change(function(){
// get the selected value
var current = $(this).val(),
suggestedTags = $("#suggestedTags"),
category;
// we do not have the category id on 'tagsMakers', so
// we need the category name.
current = $("option[value="+current+"]", this).text();
//search in array.
category = getCategoryByName(current);
//populate the suggested tags
suggestedTags.empty()
$.each(category.suggestedTags, function(i, tag) {
$("<li>" + tag.name + "</li>").appendTo(suggestedTags);
});
});​
jsFiddle: http://jsfiddle.net/vcZnu/
EDIT: If you can change your categories array by an object hash (better), so you can use the solution provided by #CupidVogel, otherwise use this, which resolve your problem as asked in your question.

Categories