select(name).option(value) selects wrong option? - angularjs

I've a got a strange issue. When I try to select a date the select() function selects the wrong option.
Ng-Model:
days = ['01', '02', '03', '04', ..., '31'];
Markup:
<select ng-model="day" id="day" name="day" ng-options="day for day in days">
<option value="" disabled="disabled"></option>
</select>
e2e test:
it('should select correct date', function () {
select('day').option('30');
expect(element('#day option:selected').text()).toEqual('30');
});
So my question is: Why does select('day').option('30') select the 31th day when select('day').option('02') works as expected?
So I was wondering what value it's targeting as it's working fine in other places.
The documentation is very sparse so either it's a feature or a bug :)
What I think is going on is the select() tries to select the value in some sort of order. I.E. it first tries to select the option by value then it tries to select it by model value, or something along those lines.

So I looked up the implementation of the select function.
From github:
/**
* Usage:
* select(name).option('value') select one option
* select(name).options('value1', 'value2', ...) select options from a multi select
*/
angular.scenario.dsl('select', function() {
var chain = {};
chain.option = function(value) {
return this.addFutureAction("select '" + this.name + "' option '" + value + "'",
function($window, $document, done) {
var select = $document.elements('select[ng\\:model="$1"]', this.name);
var option = select.find('option[value="' + value + '"]');
if (option.length) {
select.val(value);
} else {
option = select.find('option').filter(function(){
return _jQuery(this).text() === value;
});
if (!option.length) {
option = select.find('option:contains("' + value + '")');
}
if (option.length) {
select.val(option.val());
} else {
return done("option '" + value + "' not found");
}
}
select.trigger('change');
done();
});
};
return function(name) {
this.name = name;
return chain;
};
});
So the "problem" is that select tries to select from the values in the DOM element, that is the <option value="THIS VALUE"> then it tries to find the value by what's being displayed <option>THIS VALUE</option> and then it tries to do a contains on the value. It doesn't actually use the model value at any point.
So select('day').option('02') worked because it was matching the displayed text, where as select('day').option('30') was matching the option value which has an offset.
Keep in mind the generated HTML is:
<select ng-model="day" id="day" name="day" ng-options="day for day in days">
<option value="" disabled="disabled"></option>
<option value="0">01</option> <-- note it starts at 0 not 1
<option value="1">02</option> <-- select('day').option('02') matches display text '02' as no value 02 exists.
<option value="2">03</option>
<option value="29">30</option>
<option value="30">31</option> <-- select('day').option('30') matches value 30 before display text 30 with value 29.
</select>
To "solve" this issue a new function needs to be created (or alter the existing).
angular.scenario.dsl('selectModel', function() {
var chain = {};
chain.option = function(value) {
return this.addFutureAction("select '" + this.name + "' option '" + value + "'",
function($window, $document, done) {
var $ = $window.$; // jQuery inside the iframe
var select = $document.elements('select[ng\\:model="$1"]', this.name);
var option = select.find('option').filter(function(){
return $(this).text() === value;
});
if (!option.length) {
option = select.find('option:contains("' + value + '")');
}
if (option.length) {
select.val(option.val());
} else {
return done("option '" + value + "' not found");
}
select.trigger('change');
done();
});
};
return function(name) {
this.name = name;
return chain;
};
});
This does the trick.

The Angular created option tags have indices as value, when the source model is a list:
$scope.listItems = [
"day 1",
"day 2",
"day 3",
"day 4",
"day 5"
];
and
<select ng-model="listItem" ng-options="item for item in listItems"></select>
create the following HTML:
<select ng-options="item for item in listItems" ng-model="listItem"
class="ng-pristine ng-valid">
<option value="?" selected="selected"></option>
<option value="0">day 1</option>
<option value="1">day 2</option>
<option value="2">day 3</option>
<option value="3">day 4</option>
<option value="4">day 5</option>
</select>
While the option tags have keys as value, when the source model is a map:
$scope.objItems = {
"day 1":"1",
"day 2":"2",
"day 3":"3",
"day 4":"4",
"day 5":"5"
};
and
<select ng-model="objItem2" ng-options="value as key for (key, value)
in objItems"></select>
create:
<select ng-options="value as key for (key, value) in objItems"
ng-model="objItem2" class="ng-valid ng-dirty">
<option value="day 1" selected="selected">day 1</option>
<option value="day 2">day 2</option>
<option value="day 3">day 3</option>
<option value="day 4">day 4</option>
<option value="day 5">day 5</option>
</select>
However this never poses a problem because when the user selects an option, Angular looks up at the indexed position inside the source and assigns that value to the model.
I am not quite familiar with e2e testing but I think the reason you are getting a different value everytime you select an option is that you are trying to access the value stored in the option tag and not what Angular has stored in the model for you.
Fiddle

Related

Trying to display value in my select option

When I try to display my value the select box is always empty. The value is a integer 0 1 2 3 . In this case it should show 0, but the box is empty. What am I doing wrong?
vmDialog.dataModel.requestType value is equal to a 0 integer, but not displaying.
<select class="form-control " name="requestType" ng-model="vmDialog.dataModel.requestType">
<option value=0>0</option>
<option value="{{0}}">0</option>
<option value="0">0</option>
</select>
Firstly there is a simple HTML issue, you need to ensure that each option has a unique value, so far you have used the value 0 for all the options, so when the requestType has a value of 0, the UI cannot pick which of the items should be selected.
If the value supposed to be one of 0,1,2,3 then you need to make sure that there is an option with a corresponding value that can be selected.
What you probably want to do is bind the options to an array that is in the view model:
// on vmDialog
var requestTypeOptions = [
{ name = 'Zero', value = 0 },
{ name = 'One', value = 1 },
{ name = 'Two', value = 2 },
{ name = 'Three', value = 3 },
];
Then in the view: (using ng-options)
<select class="form-control"
name="requestType"
ng-model="vmDialog.dataModel.requestType"
ng-options="o.value as o.name for o in vmDialog.requestTypeOptions track by o.value | orderby: 'value'"
class="form-control input-sm">
</select>
A good write-up for ng-options here: https://stackoverflow.com/a/30507651/1690217
You could also use ng-repeat if you need to customise the template:
<select class="form-control " name="requestType" ng-model="vmDialog.dataModel.requestType">
<md-option ng-value="o.value" ng-repeat="oin vmDialog.requestTypeOptions | orderBy: 'value'">{{o.name}}</md-option>
</select>

binding two selects angular

I have two selects
The first one is for regions
and the second one for cities
The first one is filled with regions
Now i have a methode that brings cities by the id of the region .
I dont know how to get the idregion and put it into the params function
This is my first select
<div class="form-group">
<label for="">Region</label>
<select class="form-control"
[(ngModel)]="region" name="Region">
<option></option>
<option *ngFor="let region of
objectKeys(dropDownList2)|keys"
[value]="dropDownList2[region].idRegion">
{{dropDownList2[region].nomRegion}}
</option>
</select>
</div>
My services :
getville() {
return this.http.get(this.urltest + 'Villes');
}
getRegion() {
return this.http.get(this.urltest + 'Regions');
}
GetRegionVille(idRegion) {
return this.http.get(this.urltest + 'Villes/GetRegionVille/' + idRegion);
}
My component.ts :
getidregion() {
this.res.getRegion().subscribe(R => {
this.dropDownList2 = R as any;
});
}
getregionville() {
this.res.GetRegionVille(****i don t know how to get the idregion**** )
.subscribe(response => {
this.dropDownList = response as any;
});
}
this is my function
// GET: api/Villes
[HttpGet("GetRegionVille/{id}")]
public async Task<IActionResult> GetRegionVille([FromRoute] int id)
{
var req = from v in _context.Villes
join r in _context.Regions
on v.IdRegion equals r.IdRegion
where r.IdRegion == id
select new {v.NomVille };
return Ok(req);
}
My select of the cities :
<div class="form-group">
<label for="">Ville</label>
<select class="form-control" name="Ville"
[(ngModel)]="ville">
<option></option>
<option *ngFor="let ville of objectKeys(dropDownList) |
keys"
[value]="dropDownList[ville].idVille">
{{dropDownList[ville].nomVille}}
</option>
</select>
The selected value will be in region variable as you state [(ngModel)]="region" and you can use (ngModelChange)="onChange($event)" to get an event when value changes
I am not sure if using two way data binding with (ngModelChange) is efficient, so please see this answer How can I get new selection in "select" in Angular 2?

setting first item as selected for hash type

Using angularjs while creating a select element, I can set the first value as selected using
ng-init="item.state = stateArray[0]"
How to set the first item selected for key/value type array?
var States = { "AL": "Alabama", "AK": "Alaska", "AZ": "Arizona" }
html code:
<select ng-model="item.state" ng-options="k as v for (k,v) in States" class="form-control"></select>
I also tried
ng-init="item.state = Object.keys(States)[0]"
For ng-options:
Demo: http://jsfiddle.net/U3pVM/17821/
HTML:
<select ng-init="o = getFirst();" ng-model="o" ng-options="k as v for (k,v) in States" class="form-control"></select>
JS:
$scope.States = {
"AL": "Alabama",
"AK": "Alaska",
"AZ": "Arizona"
};
$scope.getFirst = function () {
var v = Object.keys($scope.States)[0];
return v;
};
For ng-repeat:
You can use $first for selecting the first option in case of ng-repeat.
ng-selected="$first?'selected':''"
try this
ng-init="item.state= stateArray['AL']"
Try this way.
In your controller,
$scope.States = { "AL": "Alabama", "AK": "Alaska", "AZ": "Arizona" };
var arrProp = Object.getOwnPropertyNames($scope.States);
$scope.item = {};
$scope.item.state = arrProp[0];
And HTML as it is,
<select ng-model="item.state" ng-options="k as v for (k,v) in States" class="form-control">
<option value="">Select </option>
</select>

Angulars: ng-options linked

I have this data:
{
"resources":{
"animals":[
{
"id":"1",
"value":"dog",
"names":[
{
"id":"1",
"value":"Carl"
},
{
"id":"2",
"value":"July"
}
]
},
{
"id":"2",
"value":"cat",
"names":[
{
"id":"1",
"value":"Peter"
},
{
"id":"2",
"value":"Frank"
}
]
},
]
}
}
I want a user select: first an animal and then select a name (filtering animal id).
Getting the ids in the result.
I thought this example:
<select ng-model="select.id_1" ng-options="a.id as a.value for a in resources.animals">
<option value="">-- choose animal --</option>
</select>
<select ng-model="select.id_2" ng-options="...">
<option value="">-- choose name --</option>
</select>
For example(Selection Cat and Frank):
select =
{
id_1: '2',
id_2: '2'
}
I don't know what code put in the second ng-options.
In this example there are two levels, how it would be with n levels?
ngOptions doesn't support multi-dimensional arrays.
Try this:
<select ng-model="select.id_1" ng-options="a.id as a.value for a in resources.animals" ng-change="firstChange()">
<option value="">-- choose animal --</option>
</select>
<select ng-model="select.id_2" ng-options="...">
<option value="">-- choose name --</option>
</select>
In JS,
$scope.firstChange = function(){
$scope.Subarray = [];
for(i=0; i<$scope.resources.animals.length; i++){
if(select.id_1 == $scope.resources.animals[i].id){
$scope.Subarray = $scope.resources.animals[i].names;
break;
}
}
}
Use the $scope.Subarray in the second ng options.
Here's a similar question, maybe try this too.

How do I set a selected option for select in Angular

I have an object
$scope.colleges = [{"CollegeCode":"40","CollegeName":"College1"},
{"CollegeCode":"35","CollegeName":"College2"},
{"CollegeCode":"32","CollegeName":"College3"},
{"CollegeCode":"15","CollegeName":"College4"}]
I'm populating a select element with it's contents
<select ng-model="collegeSelection" ng-options="c as c.CollegeName for c in colleges" name="selectCollege"></select>
It renders on the page like so
<select class="ng-pristine ng-valid" name="selectCollege" ng-options="c as c.CollegeName for c in colleges" ng-model="collegeSelection" style="" selected="selected">
<option value="?" selected="selected"></option>
<option value="0">College1</option>
<option value="1">College2</option>
<option value="2">College3</option>
<option value="3">College4</option>
etc....
I'm also binding this select to collegeSelection and when an item is selected the object look like this
$scope.collegeSelection = {"CollegeCode":"32","CollegeName":"College"}
When someone clicks edit on the page, the college I want selected by default comes from the userToUpdate object.
$scope.userToUpdate = {
Id: 1,
FirstName: 'John',
LastName: 'Doe',
CollegeCode: '35,
CollegeName: 'College2',
Active: true
};
When an edit button and the userToUpdate object is populated, I would like the college in the select element to be set to the college in the user object. How could one make this happen? FYI, the value in the rendered select does not match the CollegeCode. It seems angular uses the index from the colleges object. Thanks
Try this one
<select ng-model="collegeSelection" ng-options="c.CollegeCode as c.CollegeName for c in colleges" name="selectCollege"></select>
<span>{{collegeSelection}}</span>
<input type="button" ng-click="switch()" name="switch" value="switch" />
<script>
var app = angular.module('plunker', []);
app.controller('MainCtrl', function ($scope, $filter) {
$scope.colleges = [{ "CollegeCode": "40", "CollegeName": "College1" },
{ "CollegeCode": "35", "CollegeName": "College2" },
{ "CollegeCode": "32", "CollegeName": "College3" },
{ "CollegeCode": "15", "CollegeName": "College4" }
]
$scope.switch = function () {
$scope.collegeSelection = "32";
};
});

Resources