Setting initial value for ng-model in ng-options - javascript

I've searched Stack Overflow for an answer to my question but I can't seem to find a solution that works.
I have two select tag that follow this format:
select(required class='form-control nf-input' ng-model='Game.loadedGame.teams[0].teamInfo' ng-change='Game.addTeam(Game.loadedGame.teams[0])',
ng-options='team.name for team in Game.teams | orderBy:"name" | preventSameMatchTeams:Game.loadedGame.teams[1].teamInfo.id'
)
I am trying to initialize the ng-model for this select by passing in Game.loadedGame into this function after the server responds with the correct data:
function setLoadedGameTeams(loadedGame, teams) {
loadedGame.teams.forEach(matchTeam => {
let index = teams.findIndex(team => team.id === matchTeam.id);
if (index !== -1) {
matchTeam.teamInfo = teams[index];
}
});
}
The select dropdown isn't changing to the correct option after I set the teamInfo property on each team, even though the view is properly updated to show each team's information.
Any help is appreciated!

The solution I discovered is very simple. I simply had to add a track by to the ng-options!

Related

Making a sort of "tags" list in Angular

I've been trying to make in Angular, a list where you could select multiple items, and that these items would appear in a field near the select, and that you could delete them at will; like the "tags" field when you ask a question here.
So far, i did this :
<md-input-container class="md-block">
<p>{{classesSelected}}</p>
<md-select ng-model="classesSelected" multiples>
<md-option ng-value="classe.name" ng-repeat="classe in vm.classes">
{{classe.name}}
</md-option>
</md-select>
</md-input-container>
It gives me this :
here, you can see the list working like i'd want it to
and here the field with the data selected
i'd like that selecting an element would remove it from the list (or make it impossible to pick it again) and create a "block" in the field, with its name a little cross or something to remove it if i want to.
Picking another element from the list would ADD it to the field, and not reset it.
I have no idea how to do it, like i see the logic but i don't know how to realize it in Angular. Can someone help me out ?
Your solution might be to use two lists :
One list with the item the user can select
One list with the items the user selected
I'm not sure about what you want,
but here is a jsfiddle
function MyCtrl() {
this.options = ['Maternelle', 'CP', 'CE1', 'CM1'];//Possible to select
this.selected = [];//Chosen by user
}
MyCtrl.prototype.change = function(value) {//Called when the user select an option
if (value && value.length) {
this.selected.push(value);
this.options = this.options.filter(x => x != value);
}
}
MyCtrl.prototype.removeSelection = function(value) {//Called when the user click on the little cross
if (value && value.length) {
this.options.push(value);
this.selected = this.selected.filter(x => x != value);
}
}

What's the React way to manipulate multiple elements according to event of another element?

I am confused about this for a long time.
Here is the case:
1, I create a table with multiple rows, in this way:
tableRow(basicInfoArray) {
return basicInfoArray.map((basicInfo, index) => (
<tr
key={basicInfo._id}
className={index % 2 === 0 ? 'alt' : null}
onClick={event => this.props.showDetail(basicInfo._id, event)}
>
<td>{basicInfo.mentee_id}</td>
<td>{`${basicInfo.firstname} ${basicInfo.lastname}`}</td>
<td>{basicInfo.othername}</td>
<td>{basicInfo.location}</td>
</tr>
));
}
As you can see, I bind a onClick event to each row. If the row is clicked, it will highlight, and there will be a drilldown to show detail information.
The question is here, after clicked on the backdrop(which bind a onClick event), the drilldown hide and I should remove the highlight effect from the row. Currently I use this way:
const highLightRows = document.getElementsByClassName('highLight');
for (let i = 0; i < highLightRows.length; i += 1) {
highLightRows[i].classList.toggle('highLight');
}
As the documents of React.js says that it's not a good practice to manipulate the dom directly, the UI change should be caused by the props/state change. Obviously it's not a good idea to bind a state for each row of the table because of the quantity. What's the best practice to do this?
It's important to keep in mind that react renders whatever you have in memory, in this case you have an array of items that you want to display in a table, when clicking any of those items you want to highlight the selected, right?
Well, for that you could define a property in each element of the array called selected, this property will be true/false depending on the user selection, then when rendering the row you will check for this property and if it's there you will assign thehighLight class or not. With this approach you will only need to worry to change the value of this property on memory and it will automatically get highlighted on the DOM.
Here's an example:
renderRow(info, index) {
const styles = [
index % 2 === 0 ? 'alt' : '',
info.selected = 'highLight' : '',
];
return (
<tr
key={info._id}
className={styles.join(' ')}
onClick={event => this.props.showDetail(info, event)}
>
<td>{basicInfo.mentee_id}</td>
<td>{`${info.firstname} ${info.lastname}`}</td>
<td>{info.othername}</td>
<td>{info.location}</td>
</tr>
);
}
renderContent(basicInfoArray) {
return basicInfoArray.map((basicInfo, index) => this.rendeRow(basicInfo, index));
}
Just make sure to set to true the selected property on showDetail function, and then set to false when you need to hide and remove the highLight class.
Good luck!

How to hide row after changing status in angularjs

I'm now trying to hide clicked row after changing status in angularjs. Here is my coding and please let me know how to do it?
table.table
tr(data-ng-repeat="application in job.applications", ng-hide="application.hideApplication")
td.status
div.bold #{getMessage('Change Status:')}
div.normal
a(ng-class="app_status === 'shortlist' ? 'admin_edit_bold' : 'admin_edit_normal'", ng-click="changeApplicationStatus(application.id, 'shortlist', application)") #{getMessage('Shortlist')}
td.rating
div(ng-init='rating = application.app_rating')
.star-rating(star-rating='', rating-value='rating', data-max='5', on-rating-selected='rateFunction(application.id, rating)')
Here is controllerjs.
$scope.changeApplicationStatus = function (appId, app_status, application) {
return jobsService.changeApplicationStatus(appId, app_status).then(
function () {
application.hideApplication = false;
}
);
};
Put this attribute on whichever element you're wanting to show/hide
ng-hide="application.hideApplication"
Edit subsequent to comment:
That attribute wouldn't work on the same element as the ng-repeat, I don't think the application variable would be in scope...
Instead, you could change your repeat to:
application in job.applications | filter: { hideApplication : false }

How to select option in drop down protractorjs e2e tests

I am trying to select an option from a drop down for the angular e2e tests using protractor.
Here is the code snippet of the select option:
<select id="locregion" class="create_select ng-pristine ng-invalid ng-invalid-required" required="" ng-disabled="organization.id !== undefined" ng-options="o.id as o.name for o in organizations" ng-model="organization.parent_id">
<option value="?" selected="selected"></option>
<option value="0">Ranjans Mobile Testing</option>
<option value="1">BeaverBox Testing</option>
<option value="2">BadgerBox</option>
<option value="3">CritterCase</option>
<option value="4">BoxLox</option>
<option value="5">BooBoBum</option>
</select>
I have tried:
ptor.findElement(protractor.By.css('select option:1')).click();
This gives me the following error:
An invalid or illegal string was specified
Build info: version: '2.35.0', revision: 'c916b9d', time: '2013-08-12 15:42:01'
System info: os.name: 'Mac OS X', os.arch: 'x86_64', os.version: '10.9', java.version: '1.6.0_65'
Driver info: driver.version: unknown
I have also tried:
ptor.findElement(protractor.By.xpath('/html/body/div[2]/div/div[4]/div/div/div/div[3]/ng-include/div/div[2]/div/div/organization-form/form/div[2]/select/option[3]')).click();
This gives me the following error:
ElementNotVisibleError: Element is not currently visible and so may not be interacted with
Command duration or timeout: 9 milliseconds
Build info: version: '2.35.0', revision: 'c916b9d', time: '2013-08-12 15:42:01'
System info: os.name: 'Mac OS X', os.arch: 'x86_64', os.version: '10.9', java.version: '1.6.0_65'
Session ID: bdeb8088-d8ad-0f49-aad9-82201c45c63f
Driver info: org.openqa.selenium.firefox.FirefoxDriver
Capabilities [{platform=MAC, acceptSslCerts=true, javascriptEnabled=true, browserName=firefox, rotatable=false, locationContextEnabled=true, version=24.0, cssSelectorsEnabled=true, databaseEnabled=true, handlesAlerts=true, browserConnectionEnabled=true, nativeEvents=false, webStorageEnabled=true, applicationCacheEnabled=false, takesScreenshot=true}]
Can anyone please help me with this problem or throw some light on what i might be doing wrong here.
For me worked like a charm
element(by.cssContainingText('option', 'BeaverBox Testing')).click();
I had a similar problem, and eventually wrote a helper function that selects dropdown values.
I eventually decided that I was fine selecting by option number, and therefore wrote a method that takes an element and the optionNumber, and selects that optionNumber. If the optionNumber is null it selects nothing (leaving the dropdown unselected).
var selectDropdownbyNum = function ( element, optionNum ) {
if (optionNum){
var options = element.all(by.tagName('option'))
.then(function(options){
options[optionNum].click();
});
}
};
I wrote a blog post if you want more detail, it also covers verifying the text of the selected option in a dropdown: http://technpol.wordpress.com/2013/12/01/protractor-and-dropdowns-validation/
An elegant approach would involve making an abstraction similar to what other selenium language bindings offer out-of-the-box (e.g. Select class in Python or Java).
Let's make a convenient wrapper and hide implementation details inside:
var SelectWrapper = function(selector) {
this.webElement = element(selector);
};
SelectWrapper.prototype.getOptions = function() {
return this.webElement.all(by.tagName('option'));
};
SelectWrapper.prototype.getSelectedOptions = function() {
return this.webElement.all(by.css('option[selected="selected"]'));
};
SelectWrapper.prototype.selectByValue = function(value) {
return this.webElement.all(by.css('option[value="' + value + '"]')).click();
};
SelectWrapper.prototype.selectByPartialText = function(text) {
return this.webElement.all(by.cssContainingText('option', text)).click();
};
SelectWrapper.prototype.selectByText = function(text) {
return this.webElement.all(by.xpath('option[.="' + text + '"]')).click();
};
module.exports = SelectWrapper;
Usage example (note how readable and easy-to-use it is):
var SelectWrapper = require('select-wrapper');
var mySelect = new SelectWrapper(by.id('locregion'));
# select an option by value
mySelect.selectByValue('4');
# select by visible text
mySelect.selectByText('BoxLox');
Solution taken from the following topic: Select -> option abstraction.
FYI, created a feature request: Select -> option abstraction.
element(by.model('parent_id')).sendKeys('BKN01');
To access a specific option you need to provide the nth-child() selector:
ptor.findElement(protractor.By.css('select option:nth-child(1)')).click();
This is how i did my selection.
function switchType(typeName) {
$('.dropdown').element(By.cssContainingText('option', typeName)).click();
};
Here's how I did it:
$('select').click();
$('select option=["' + optionInputFromFunction + '"]').click();
// This looks useless but it slows down the click event
// long enough to register a change in Angular.
browser.actions().mouseDown().mouseUp().perform();
Try this, it is working for me:
element(by.model('formModel.client'))
.all(by.tagName('option'))
.get(120)
.click();
You can try this hope it will work
element.all(by.id('locregion')).then(function(selectItem) {
expect(selectItem[0].getText()).toEqual('Ranjans Mobile Testing')
selectItem[0].click(); //will click on first item
selectItem[3].click(); //will click on fourth item
});
Another way to set an option element:
var select = element(by.model('organization.parent_id'));
select.$('[value="1"]').click();
To select items (options) with unique ids like in here:
<select
ng-model="foo"
ng-options="bar as bar.title for bar in bars track by bar.id">
</select>
I'm using this:
element(by.css('[value="' + neededBarId+ '"]')).click();
We wrote a library which includes 3 ways to select an option:
selectOption(option: ElementFinder |Locator | string, timeout?: number): Promise<void>
selectOptionByIndex(select: ElementFinder | Locator | string, index: number, timeout?: number): Promise<void>
selectOptionByText(select: ElementFinder | Locator | string, text: string, timeout?: number): Promise<void>
Additional feature of this functions is that they wait for the element to be displayed before any action on the select is performed.
You can find it on npm #hetznercloud/protractor-test-helper.
Typings for TypeScript are provided as well.
Maybe not super elegant, but efficient:
function selectOption(modelSelector, index) {
for (var i=0; i<index; i++){
element(by.model(modelSelector)).sendKeys("\uE015");
}
}
This just sends key down on the select you want, in our case, we are using modelSelector but obviously you can use any other selector.
Then in my page object model:
selectMyOption: function (optionNum) {
selectOption('myOption', optionNum)
}
And from the test:
myPage.selectMyOption(1);
The problem is that solutions that work on regular angular select boxes do not work with Angular Material md-select and md-option using protractor. This one was posted by another, but it worked for me and I am unable to comment on his post yet (only 23 rep points). Also, I cleaned it up a bit, instead of browser.sleep, I used browser.waitForAngular();
element.all(by.css('md-select')).each(function (eachElement, index) {
eachElement.click(); // select the <select>
browser.waitForAngular(); // wait for the renderings to take effect
element(by.css('md-option')).click(); // select the first md-option
browser.waitForAngular(); // wait for the renderings to take effect
});
There's an issue with selecting options in Firefox that Droogans's hack fixes that I want to mention here explicitly, hoping it might save someone some trouble: https://github.com/angular/protractor/issues/480.
Even if your tests are passing locally with Firefox, you might find that they're failing on CircleCI or TravisCI or whatever you're using for CI&deployment. Being aware of this problem from the beginning would have saved me a lot of time:)
Helper to set the an option element:
selectDropDownByText:function(optionValue) {
element(by.cssContainingText('option', optionValue)).click(); //optionValue: dropDownOption
}
If below is the given dropdown-
<select ng-model="operator">
<option value="name">Addition</option>
<option value="age">Division</option>
</select>
Then protractorjs code can be-
var operators=element(by.model('operator'));
operators.$('[value=Addition]').click();
Source-https://github.com/angular/protractor/issues/600
Select option by Index:
var selectDropdownElement= element(by.id('select-dropdown'));
selectDropdownElement.all(by.tagName('option'))
.then(function (options) {
options[0].click();
});
I've improved a bit the solution written by PaulL.
First of all I fixed the code to be compatible with the last Protractor API. And then I declare the function in 'onPrepare' section of a Protractor config file as a member of the browser instance, so it can be referenced form any e2e spec.
onPrepare: function() {
browser._selectDropdownbyNum = function (element, optionNum) {
/* A helper function to select in a dropdown control an option
* with specified number.
*/
return element.all(by.tagName('option')).then(
function(options) {
options[optionNum].click();
});
};
},
The below example is the easiest way . I have tested and passed in Protractor Version 5.4.2
//Drop down selection using option's visibility text
element(by.model('currency')).element(by.css("[value='Dollar']")).click();
Or use this, it $ isshort form for .By.css
element(by.model('currency')).$('[value="Dollar"]').click();
//To select using index
var select = element(by.id('userSelect'));
select.$('[value="1"]').click(); // To select using the index .$ means a shortcut to .By.css
Full code
describe('Protractor Demo App', function() {
it('should have a title', function() {
browser.driver.get('http://www.way2automation.com/angularjs-protractor/banking/#/');
expect(browser.getTitle()).toEqual('Protractor practice website - Banking App');
element(by.buttonText('Bank Manager Login')).click();
element(by.buttonText('Open Account')).click();
//Drop down selection using option's visibility text
element(by.model('currency')).element(by.css("[value='Dollar']")).click();
//This is a short form. $ in short form for .By.css
// element(by.model('currency')).$('[value="Dollar"]').click();
//To select using index
var select = element(by.id('userSelect'));
select.$('[value="1"]').click(); // To select using the index .$ means a shortcut to .By.css
element(by.buttonText("Process")).click();
browser.sleep(7500);// wait in miliseconds
browser.switchTo().alert().accept();
});
});
I've been trawling the net for an answer on how to select an option in a model dropdown and i've used this combination which has helped me out with Angular material.
element(by.model("ModelName")).click().element(By.xpath('xpathlocation')).click();
it appears that when throwing the code all in one line it could find the element in the dropdown.
Took a lot of time for this solution I hope that this helps someone out.
If none of the answer's above worked for you, try this
works with async/await too
For selecting options by text
let textOption = "option2"
await element(by.whichever('YOUR_DROPDOWN_SELECTOR'))
.getWebElement()
.findElement(by.xpath(`.//option[text()="${textOption}"]`))
.click();
or by number
let optionNumber = 2
await element(by.whichever('YOUR_DROPDOWN_SELECTOR'))
.getWebElement()
.findElement(by.xpath(`.//option[${optionNumber}]`))
.click();
Of course you may need to modify the xpath of child options
Don't ask me why, but this is the only way I could automate my dropdowns, when I lost hope already
Update
There was actually one case when even this approach didnt work. THe work around was a bit ugly but worked. I simply had to select the value two times
We wanted to use the elegant solution up there using angularjs material but it didnt work because there are actually no option / md-option tags in the DOM until the md-select has been clicked. So the "elegant" way didn't work for us (note angular material!) Here is what we did for it instead, don't know if its the best way but its definately working now
element.all(by.css('md-select')).each(function (eachElement, index) {
eachElement.click(); // select the <select>
browser.driver.sleep(500); // wait for the renderings to take effect
element(by.css('md-option')).click(); // select the first md-option
browser.driver.sleep(500); // wait for the renderings to take effect
});
We needed to have 4 selects selected and while the select is open, there is an overlay in the way of selecting the next select. thats why we need to wait 500ms to make sure we don't get into trouble with the material effects still being in action.
Another way to set an option element:
var setOption = function(optionToSelect) {
var select = element(by.id('locregion'));
select.click();
select.all(by.tagName('option')).filter(function(elem, index) {
return elem.getText().then(function(text) {
return text === optionToSelect;
});
}).then(function(filteredElements){
filteredElements[0].click();
});
};
// using the function
setOption('BeaverBox Testing');
----------
element.all(by.id('locregion')).then(function(Item)
{
// Item[x] = > // x is [0,1,2,3]element you want to click
Item[0].click(); //first item
Item[3].click(); // fourth item
expect(Item[0].getText()).toEqual('Ranjans Mobile Testing')
});
You can select dropdown options by value:
$('#locregion').$('[value="1"]').click();
Here is how to do it by either option value or index. This example is a bit crude, but it shows how to do what you want:
html:
<mat-form-field id="your-id">
<mat-select>
<mat-option [value]="1">1</mat-option>
<mat-option [value]="2">2</mat-option>
</mat-select>
</mat-form-field>
ts:
function selectOptionByOptionValue(selectFormFieldElementId, valueToFind) {
const formField = element(by.id(selectFormFieldElementId));
formField.click().then(() => {
formField.element(by.tagName('mat-select'))
.getAttribute('aria-owns').then((optionIdsString: string) => {
const optionIds = optionIdsString.split(' ');
for (let optionId of optionIds) {
const option = element(by.id(optionId));
option.getText().then((text) => {
if (text === valueToFind) {
option.click();
}
});
}
});
});
}
function selectOptionByOptionIndex(selectFormFieldElementId, index) {
const formField = element(by.id(selectFormFieldElementId));
formField.click().then(() => {
formField.element(by.tagName('mat-select'))
.getAttribute('aria-owns').then((optionIdsString: string) => {
const optionIds = optionIdsString.split(' ');
const optionId = optionIds[index];
const option = element(by.id(optionId));
option.click();
});
});
}
selectOptionByOptionValue('your-id', '1'); //selects first option
selectOptionByOptionIndex('your-id', 1); //selects second option
static selectDropdownValue(dropDownLocator,dropDownListLocator,dropDownValue){
let ListVal ='';
WebLibraryUtils.getElement('xpath',dropDownLocator).click()
WebLibraryUtils.getElements('xpath',dropDownListLocator).then(function(selectItem){
if(selectItem.length>0)
{
for( let i =0;i<=selectItem.length;i++)
{
if(selectItem[i]==dropDownValue)
{
console.log(selectItem[i])
selectItem[i].click();
}
}
}
})
}
We can create a custom DropDown class for this and add a method as:
async selectSingleValue(value: string) {
await this.element.element(by.xpath('.//option[normalize-space(.)=\'' + value + '\']')).click();
}
Also, to verify what value is currently selected, we can have:
async getSelectedValues() {
return await this.element.$('option:checked').getText();
}
This is a simple one line answer in which angular has special locator which can help to select and index from list.
element.all(by.options('o.id as o.name for o in organizations')).get(Index).click()

label inside combobox and conditional multiselect

I am building a pretty combobox with checkboxes and conditional entries. Everything works out alright, except for two features that I cannot figure out how to implement.
1) I would like to move the label inside the combobox, make it shift the values to the right, and appear in a slightly gray color.
2) I would like the value to ignore certain entries (group headers) selected. Those entries are there for functionality only - to select/unselect groups of other entries.
The entire project is in the zip file. You don't need a server, it's a client base app. Just download the archive, unpack, and launch app.html in your browser.
http://filesave.me/file/30586/project-zip.html
And here's a snapshot of what I would like to achieve.
Regarding your second issue, the best way I see is to override combobox onListSelectionChange to filter the values you don't want:
onListSelectionChange: function(list, selectedRecords) {
//Add the following line
selectedRecords = Ext.Array.filter(selectedRecords, function(rec){
return rec.data.parent!=0;
});
//Original code unchanged from here
var me = this,
isMulti = me.multiSelect,
hasRecords = selectedRecords.length > 0;
// Only react to selection if it is not called from setValue, and if our list is
// expanded (ignores changes to the selection model triggered elsewhere)
if (!me.ignoreSelection && me.isExpanded) {
if (!isMulti) {
Ext.defer(me.collapse, 1, me);
}
/*
* Only set the value here if we're in multi selection mode or we have
* a selection. Otherwise setValue will be called with an empty value
* which will cause the change event to fire twice.
*/
if (isMulti || hasRecords) {
me.setValue(selectedRecords, false);
}
if (hasRecords) {
me.fireEvent('select', me, selectedRecords);
}
me.inputEl.focus();
}
},
And change your onBoundlistItemClick to only select and deselect items in the boundlist not to setValue of the combo:
onBoundlistItemClick: function(dataview, record, item, index, e, eOpts) {
var chk = item.className.toString().indexOf('x-boundlist-selected') == -1;
if ( ! record.data.parent) {
var d = dataview.dataSource.data.items;
for (var i in d) {
var s = d[i].data;
if (s.parent == record.data.id) {
if (chk) { // select
dataview.getSelectionModel().select(d[i],true);
} else { // deselect
dataview.getSelectionModel().deselect(d[i]);
}
}
}
}
},
Regarding your first issue, it is easy to add the label using the displayTpl config option. But this will only add the text you need, without any style (grey color, etc). The combo is using a text input, which does not accept html tags. If you don't need the user to type text, than you may want to change the combo basic behavior and use another element instead of the text input.

Categories