angularjs ng-model and ng-select strange behaviour - javascript

I have a list of persons objects that I want to bind to a select element.
Controller implementation:
// Person controller
function PersonCtrl($scope) {
$scope.persons = [{id: 1, name: 'alex'}, {id: 2, name: 'jhon'}];
$scope.selectedPerson = {id: 2, name: 'jhon'};
}
Html markup:
<select ng-model="selectedPerson" ng-options="p.name for p in persons"></select>
The problem is that the binding seems to work only from the HTML to the scope and not the other way around. If I select an item from the drop down, it will correctly update the $scope.selectedPerson. But, if I initially set the value of the $scope.selectedPerson variable to one of the objects in the persons list, the value will not be reflected on the select control.
I also have fiddle that shows exactly what the problem is: http://jsfiddle.net/nE3gt/
Thank you in advance!

You need to set the selectedPerson to an element in the persons array, not a new object - otherwise Angular has no idea they are the same thing.
// Person controller
function PersonCtrl($scope) {
$scope.persons = [{id: 1, name: 'alexx'}, {id: 2, name: 'jhon'}];
$scope.selectedPerson = $scope.persons[1];
$scope.submit = function () {
//console.log(JSON.stringify($scope.Person));
$("#obj").text(JSON.stringify($scope.Person));
};
}
Updated fiddle: http://jsfiddle.net/nE3gt/2/
edit
If I'm understanding you correctly, you want to do something along these lines:
// Person controller
function PersonCtrl($scope) {
//serverData is the result of some http request...
var serverData = {persons: [{id: 1, name: 'alexx'}, {id: 2, name: 'jhon'}], selectedPerson: {id: 2, name: 'jhon'}};
$scope.persons = serverData.persons;
$scope.selectedPerson = null;
//find obj in persons that equals selectedPerson
for(var i = 0, l = serverData.persons.length; i < l; i++)
{
if(serverData.persons[i].id === serverData.selectedPerson.id)
{
$scope.selectedPerson = serverData.persons[i];
}
}
$scope.submit = function () {
//console.log(JSON.stringify($scope.Person));
$("#obj").text(JSON.stringify($scope.Person));
};
};
Even if your data comes back as different objects you need to make sure you're setting selectedPerson to an item that is in the persons array.
new fiddle: http://jsfiddle.net/nE3gt/4/

Set it to the index of the array you're binding to:
$scope.selectedPerson = $scope.persons[0];
Demo: http://jsfiddle.net/nE3gt/3/

Related

How to remove duplicate values from dynamic select in Javascript?

trying to figure this out with no such luck. Basically we populating a select with values from the service that it's being retrieved from. But there are duplicates in the select. Here's the code that's doing it. I'd like to remove the duplicates from what's getting returned that are in the "theProduct.name". I know this question has been asked before but I can't figure this out. The image attached is the select where it's happening. Thanks
function populateSearchProducts(data) {
var theData = data.data.results;
$field.empty().append('<option value=""> </option>');
for (var p in theData) {
var theProduct = theData[p];
$field.append('<option value="'+theProduct.id+'">'+theProduct.name+'</option>');
}
}
Try a filter to remove duplicates from the input data:
function populateSearchProducts(data) {
var theData = data.data.results.filter(function(item, pos, self) {
return self.indexOf(item) == pos;
});
$field.empty().append('<option value=""> </option>');
for (var p in theData) {
var theProduct = theData[p];
$field.append('<option value="'+theProduct.id+'">'+theProduct.name+'</option>');
}
}
function populateSearchProducts(data) {
data = data.data.results;
const dupes = new Set();
for(const {name, id} of Object.values(data)){
if(dupes.has(name)) continue;
$field.append(`<option value='${id}' > ${name} </option>`);
dupes.add(name);
}
}
You can keep track of the items already added to the DOM and then use a filter before adding new elements.
In the code below, the filter is looking at the id of each element to filter them out. If you want, you could use name (or any other attribute) to detect the duplicates and filter them out.
Something along the lines of:
var dataArray = [
{id: 1, name: 'one'},
{id: 2, name: 'two'},
{id: 3, name: 'three'},
{id: 4, name: 'four'},
{id: 2, name: 'two'}, // dupe
{id: 5, name: 'five'},
{id: 3, name: 'three'} // dupe
]
function populateSearchProducts(data, $field) {
//var theData = data.data.results;
var theData = data;
$field.empty().append('<option value=""> </option>');
// to keep track of the ones already in the drop-down
var alreadyAdded = [];
for (let p of theData) {
if(alreadyAdded.filter( item => item.id === p.id ).length <= 0 ){
$field.append('<option value="'+p.id+'">'+p.name+'</option>');
alreadyAdded.push(p);
}
}
}
// when docuemnt ready, populate the field
$(function(){
var field = $("#selOptions");
populateSearchProducts(dataArray, field);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<select id="selOptions" />
updated based on comments

Bind ng-options in ng-model on loading (like a bidirectional binding)

The situation:
I have the following select:
<select ng-model="model" ng-options="o as o.name for o in options track by o.code">
<option value="">- Default -</option>
</select>
My options datas are like this:
$scope.options = [{id: 1, code: 'foo', name: 'Foo!'}, {id: 2, code: 'bar', name: 'Bar!'}];
What I want to do:
I want to have my select with a pre-selected value. My constraint is that I only know the code attribute of my object. With the help of the track by notation I can do that simply like this:
$scope.model = {code: 'bar'};
And it works, the selected value of the select is "Bar!"
The problem:
When I send this data to my backend, I need to send the id attribute of my object. The data sent is {code: 'bar'} but not {id: 2, code: 'bar', name: 'Bar!'} as I want.
For me it is the normal behavior... because I stored in my model {code: 'bar'} and I did not change the value selected.
The conclusion:
Is there a way to tell to AngularJS to copy the value from the options list to model when there is a default value in model (when there is a match using the track by notation) ?
Info: I know that I can do something like this $scope.model = $scope.options[2] (with some logic to determine the index...) but I would like something more magical... If it is possible... :D
OP Info: I know that I can do something like this $scope.model = $scope.options[2] (with some logic to determine the index...) but I would like something more magical... If it is possible... :D
I have 3 magic's for your 3 objects
Magic 1 :
$scope.model = $scope.options.filter(function(item) {
return item.code=== 'bar';
})[0];
Magic 2:
app.filter('getById', function() {
return function(input, id) {
var i=0, len=input.length;
for (; i<len; i++) {
if (+input[i].id == +id) {
return input[i];
}
}
return null;
}
});
$scope.model = $filter('getById')($scope.options, 2);
Magic 3
angular.forEach($scope.options, function(option) {
$scope.model = option.name == "name";
if($scope.model !=null){break}
});
You need to add a ng-change on the <select> and then create a function that will look for the selected option and its corresponding JSON model. Use this
HTML
<div ng-app='app' ng-controller='mainCtrl'>
<select ng-model="model" ng-options="o as o.name for o in options track by o.code"
ng-change='getValue(this)'>
<option value="">- Default -</option>
</select>
</div>
CONTROLLER
angular.module('app',['QuickList']).controller('mainCtrl', function($scope){
$scope.options = [{id: 1, code: 'foo', name: 'Foo!'}, {id: 2, code: 'bar', name: 'Bar!'}];
$scope.model = {code: 'bar'};
$scope.getValue = function(item){
console.log($scope.selectedOption = item.model);
}
});
Whenever you will select the option in the <select> box you will see the actual JSON object for that option printed in the console.
Here is the working JSFIDDLE

Knockout Js Array.removeAll() not working

var x=ko.observableArray([....]);
x.removeAll();
Situation is 'x' is holding the selected values from dropdowns which are dynamically generated using knockout. I want to clear all the selections made by the user.
The above code is not working in my situation, so i have to 'undefine' each element in the array using a for loop - which is a very bad way(i feel). i want to know if there any better way of doing this?
I also tried this:
x([]);
Which didn't work either.
My code Looks something like this
var data_Main = [
{[a,b,c] },{[d,e,f]},{[g,h,i]}
];
var selectedKoArray= ko.observableArray([]);
var Lst=ko.observableArray([
{Txt: 'aaa', Val: 'a'},
{Txt: 'bbb', Val: 'b'},
{Txt: 'ccc', Val: 'c'}
]);
var dataArray = ko.observableArray([Lst:Lst(), Lst:Lst1(), Lst:Lst2()]);
var selectedHFilters = ko.observableArray([]);
for (var i = 0; i < dataArray.length - 1; i++) {
if (selectedKoArray[i])
selectedKoArray[i] = undefined;
}
-----------------------------------------------------HTML-------------------------------
<div data-bind="foreach:dataArrayKO ">
<select class="select2-offscreen filter-select" data-bind="options:Lst, optionsText: 'Txt', optionsValue: 'Val', optionsCaption: '-Select-', value:$root.selectedHFilters[$index()]"></select>
</div>
This is not valid Javascript and will error, which may be the root of your problem:
var dataArray = ko.observableArray([Lst:Lst(), Lst:Lst1(), Lst:Lst2()]);
I suspect you wanted something like this (though Lst(), Lst1() and Lst2() are not defined in the code you've supplied, again this will error).
var dataArray = ko.observableArray([Lst.Lst(), Lst.Lst1(), Lst.Lst2()]);
The KO code for removing elements you have supplied is fine.

AngularJs Filter an array by another array

I have three arrays in a controller:
$scope.allUsers containing all users by :id and :name. E.g. smth like this.
$scope.job.delegated_to containing information related to job delegation. Looks like this.
$scope.job.delegated_to = [
{id: 5, user_id:33, user_name:"Warren", hour_count:4},
{id: 5, user_id:18, user_name:"Kelley", hour_count:2},
{id: 5, user_id:10, user_name:"Olson", hour_count:40},
{id: 5, user_id:42, user_name:"Elma", hour_count:2},
{id: 5, user_id:45, user_name:"Haley", hour_count:4},
{id: 5, user_id:11, user_name:"Kathie", hour_count:3}
]
$scope.freeUsers which has to contain all the users, not delegated to the job.
I added a watch
$scope.$watch('job.delegated_to.length', function(){
$scope.freeUsers = filterUsers( $scope.allUsers, $scope.job.delegated_to );
});
but have not been able to construct a working filter.
Your filterUsers function would be like:
var filterUsers = function(allUsers, jobs) {
var freeUsers = allUsers.slice(0); //clone allUsers
var jobUserIds = [];
for(var ind in jobs) jobUserIds.push(jobs[ind].user_id);
var len = freeUsers.length;
while(len--){
if(jobUserIds.indexOf(allUsers[len].id) != -1) freeUsers.splice(len, 1);
}
return freeUsers;
}
Checkout fiddle http://jsfiddle.net/cQXBv/
I would suggest using a library like Lo-Dash, which has many useful utility functions. You could then write your filter function as:
function filterUsers(allUsers, delegatedTo) {
var delegatedIndex = _.indexBy(delegatedTo, 'user_id');
return _.reject(allUsers, function(user) {return user.id in delegatedIndex});
}

ngOptions: add tooltip to selection

using the following json collection:
$scope.searchscope = [
{ id: 0, name: "Base", description: "Search only the specified base object. The value is equal to 0." },
{ id: 1, name: "OneLevel", description: "Search the child objects of the base object, but not the base object itself.The value is equal to 1" },
{ id: 2, name: "Subtree", description: "Search the base object and all child objects. The value is equal to 2" }
];
and the following markup
<select data-ng-model="mySelections[0].SearchFilterMembershipsScope" data-ng-options="i.id as i.name for i in searchscope" data-ng-required="true"></select>
I have successfully generated a select with the 3 options. What would be the best angular way to display a tooltip of the currently selected items "description"? Also would it be possible to do this whilst the user is navigating the dropdown, i.e. making a choice, verses actually had made the choice?
You would need a map from id → description. One way to populate it would be:
$scope.idToDescription = null;
$scope.$watch("searchscope", function(newval) {
if( newval != null ) {
$scope.idToDescription = {};
var i;
for( i=0; i < newval.length; i++ ) {
$scope.idToDescription[newval[i].id] = newval[i].description;
}
}
});
And in your template use it as:
<select ...
title="{{ idToDescription[mySelections[0].SearchFilterMembershipsScope] }}">
If you are using Twitter Bootstrap, you might also consider looking at Tooltips in
UI Bootstrap and if you want fancier select boxes consider using ui-select2 based on Select2.

Categories

Resources