Data binding inside <option> element in angular.js and jQuery mobile - javascript

I have recently added a multi-language support to my jQuery-mobile/PhoneGap app using this great repo on gitHub.
It uses firebase and angular.js to bind all the language dependant labels and texts in my app to the currently selected language, like in this example:
<h2>{{getWord("SELECTION_CRITERIA")}}</h2>
However, there are some language-dependant texts in my app that cannot be simply bound to language data, I have found this so far:
<select id="sel" data-role="slider">
<option value="off">{{getWord("SELECT_OFF")}}</option>
<option value="on">{{getWord("SELECT_ON")}}</option>
</select>
The <option> element doesn't allow to bind it's text to the data (the rendered text is empty). I don't know why. I tried to test whether something like this could work:
<option value="off">{{1+2}}</option>
and yes, it displayed number 3 in the slider.
The angular.js part looks like this:
function LanguageCtrl($scope) {
$scope.words = JSON.parse('{}');
$scope.addWord = function(Name, Value) {
$scope.words[Name] = Value;
};
$scope.getWord = function(Name) {
return $scope.words[Name];
};
}
var ref;
var refLang;
var onLangChange;
$(document).on("pageinit", "#mainPage", function(event) {
ref = new Firebase('https://username.firebaseio.com/');
ChangeLangTo("En"); //default lang
});
function ChangeLangTo(lang) {
if (onLangChange !== undefined && onLangChange !== undefined) {
refLang.off('child_changed', onLangChange);
}
refLang = ref.child(lang);
//reading once all words
refLang.once('value', function(dataSnapshot) {
var lang = dataSnapshot.val();
angular.element($("#mainPage")).scope().$apply(function(scope) {
$.each(lang, function(key, value) {
scope.addWord(key, value);
});
});
});
//listening to changes
onLangChange = refLang.on('child_changed', function(childSnapshot, prevChildName) {
angular.element($("#mainPage")).scope().$apply(function(scope) {
scope.addWord(childSnapshot.name(), childSnapshot.val());
});
});
The JSON from firebase that is parsed into that variable is very simple, like:
{
"Cz": {
"SELECT_ON":"Ano",
"SELECT_OFF":"Ne"
},
"En": {
"SELECT_ON":"On",
"SELECT_OFF":"Off"
}
}
I think that the <button> element won't work, too. And there are some cases of setting an element text via an attribute, like in this case:
<ul data-filter-placeholder="Search items..."></ul>
I would like to make this work, too:
<ul data-filter-placeholder="{{getWord("SEARCH_ITEMS")}}"></ul>
I am very new to angular.js, can someone please explain me how to make this work? I have read some documentation on HTML SELECT element with angular data-binding, but do not quite understand how to apply it.
Thanks!

Related

How can I access the font of selected, Outlook Web Add-in JavaScript?

In the Word Web Add-in I can access font of the selected context.document.getSelection().font but I don't find it (after searching too) in Outlook Web Add-in, I only can get the text of selected by Office.context.mailbox.item.getSelectedDataAsync with Office.CoercionType.Text parameter, How can I get the font please?
Text formatting in Outlook is done in HTML (assuming the format isn't plain text). You can return the underlying HTML using Office.CoercionType.Html:
Office.initialize = function () {
Office.context.mailbox.item
.getSelectedDataAsync(Office.CoercionType.Html, {},
function (asyncResult) {
var htmlData = asyncResult.value.data;
// do stuff
});
}
Since the HTML formatting might have been set outside the scope of your selection, you might want to grab the entire body as well. You can then use the getSelectedDataAsync results to find the current selection within the full HTML body:
function myFunction() {
// Get the selected text
Office.context.mailbox.item
.getSelectedDataAsync('html', {}, function (asyncResult) {
// Get the full body and pass through the selectedData
// in the asyncContext.
Office.context.mailbox.item.body.getAsync("html", {
asyncContext: asyncResult.value.data
},
function callback(asyncResult) {
// Get the body from the result
let bodyDaya = asyncResult.value.data;
// Get the selectedData we passed in
let selectedData = asyncResult.asyncContext;
// Do stuff
});
});
}

Using Javascript loop to create multiple HTML elements

I would like to use a javascript loop to create multiple HTML wrapper elements and insert JSON response API data into some of the elements (image, title, url, etc...).
Is this something I need to go line-by-line with?
<a class="scoreboard-video-outer-link" href="">
<div class="scoreboard-video--wrapper">
<div class="scoreboard-video--thumbnail">
<img src="http://via.placeholder.com/350x150">
</div>
<div class="scoreboard-video--info">
<div class="scoreboard-video--title">Pelicans # Bulls Postgame: E'Twaun Moore 10-8-17</div>
</div>
</div>
</a>
What I am trying:
var link = document.createElement('a');
document.getElementsByTagName("a")[0].setAttribute("class", "scoreboard-video-outer-link");
document.getElementsByTagName("a")[0].setAttribute("url", "google.com");
mainWrapper.appendChild(link);
var videoWrapper= document.createElement('div');
document.getElementsByTagName("div")[0].setAttribute("class", "scoreboard-video-outer-link");
link.appendChild(videoWrapper);
var videoThumbnailWrapper = document.createElement('div');
document.getElementsByTagName("div")[0].setAttribute("class", "scoreboard-video--thumbnail");
videoWrapper.appendChild(videoThumbnailWrapper);
var videoImage = document.createElement('img');
document.getElementsByTagName("img")[0].setAttribute("src", "url-of-image-from-api");
videoThumbnailWrapper.appendChild(videoImage);
Then I basically repeat that process for all nested HTML elements.
Create A-tag
Create class and href attributes for A-tag
Append class name and url to attributes
Append A-tag to main wrapper
Create DIV
Create class attributes for DIV
Append DIV to newly appended A-tag
I'd greatly appreciate it if you could enlighten me on the best way to do what I'm trying to explain here? Seems like it would get very messy.
Here's my answer. It's notated. In order to see the effects in the snippet you'll have to go into your developers console to either inspect the wrapper element or look at your developers console log.
We basically create some helper methods to easily create elements and append them to the DOM - it's really not as hard as it seems. This should also leave you in an easy place to append JSON retrieved Objects as properties to your elements!
Here's a Basic Version to give you the gist of what's happening and how to use it
//create element function
function create(tagName, props) {
return Object.assign(document.createElement(tagName), (props || {}));
}
//append child function
function ac(p, c) {
if (c) p.appendChild(c);
return p;
}
//example:
//get wrapper div
let mainWrapper = document.getElementById("mainWrapper");
//create link and div
let link = create("a", { href:"google.com" });
let div = create("div", { id: "myDiv" });
//add link as a child to div, add the result to mainWrapper
ac(mainWrapper, ac(div, link));
//create element function
function create(tagName, props) {
return Object.assign(document.createElement(tagName), (props || {}));
}
//append child function
function ac(p, c) {
if (c) p.appendChild(c);
return p;
}
//example:
//get wrapper div
let mainWrapper = document.getElementById("mainWrapper");
//create link and div
let link = create("a", { href:"google.com", textContent: "this text is a Link in the div" });
let div = create("div", { id: "myDiv", textContent: "this text is in the div! " });
//add link as a child to div, add the result to mainWrapper
ac(mainWrapper, ac(div, link));
div {
border: 3px solid black;
padding: 5px;
}
<div id="mainWrapper"></div>
Here is how to do specifically what you asked with more thoroughly notated code.
//get main wrapper
let mainWrapper = document.getElementById("mainWrapper");
//make a function to easily create elements
//function takes a tagName and an optional object for property values
//using Object.assign we can make tailored elements quickly.
function create(tagName, props) {
return Object.assign(document.createElement(tagName), (props || {}));
}
//document.appendChild is great except
//it doesn't offer easy stackability
//The reason for this is that it always returns the appended child element
//we create a function that appends from Parent to Child
//and returns the compiled element(The Parent).
//Since we are ALWAYS returning the parent(regardles of if the child is specified)
//we can recursively call this function to great effect
//(you'll see this further down)
function ac(p, c) {
if (c) p.appendChild(c);
return p;
}
//these are the elements you wanted to append
//notice how easy it is to make them!
//FYI when adding classes directly to an HTMLElement
//the property to assign a value to is className -- NOT class
//this is a common mistake, so no big deal!
var link = create("a", {
className: "scoreboard-video-outer-link",
url: "google.com"
});
var videoWrapper = create("div", {
className: "scoreboard-video-outer-link"
});
var videoThumbnailWrapper = create("div", {
className: "scoreboard-video--thumbnail"
});
var videoImage = create("img", {
src: "url-of-image-from-api"
});
//here's where the recursion comes in:
ac(mainWrapper, ac(link, ac(videoWrapper, ac(videoThumbnailWrapper, videoImage))));
//keep in mind that it might be easiest to read the ac functions backwards
//the logic is this:
//Append videoImage to videoThumbnailWrapper
//Append (videoImage+videoThumbnailWrapper) to videoWrapper
//Append (videoWrapper+videoImage+videoThumbnailWrapper) to link
//Append (link+videoWrapper+videoImage+videoThumbnailWrapper) to mainWrapper
let mainWrapper = document.getElementById('mainWrapper');
function create(tagName, props) {
return Object.assign(document.createElement(tagName), (props || {}));
}
function ac(p, c) {
if (c) p.appendChild(c);
return p;
}
var link = create("a", {
className: "scoreboard-video-outer-link",
url: "google.com"
});
var videoWrapper = create("div", {
className: "scoreboard-video-outer-link"
});
var videoThumbnailWrapper = create("div", {
className: "scoreboard-video--thumbnail"
});
var videoImage = create("img", {
src: "url-of-image-from-api"
});
ac(mainWrapper, ac(link, ac(videoWrapper, ac(videoThumbnailWrapper, videoImage))));
//pretty fancy.
//This is just to show the output in the log,
//feel free to just open up the developer console and look at the mainWrapper element.
console.dir(mainWrapper);
<div id="mainWrapper"></div>
Short version
Markup.js's loops.
Long version
You will find many solutions that work for this problem. But that may not be the point. The point is: is it right? And you may using the wrong tool for the problem.
I've worked with code that did similar things. I did not write it, but I had to work with it. You'll find that code like that quickly becomes very difficult to manage. You may think: "Oh, but I know what it's supposed to do. Once it's done, I won't change it."
Code falls into two categories:
Code you stop using and you therefore don't need to change.
Code you keep using and therefore that you will need to change.
So, "does it work?" is not the right question. There are many questions, but some of them are: "Will I be able to maintain this? Is it easy to read? If I change one part, does it only change the part I need to change or does it also change something else I don't mean to change?"
What I'm getting at here is that you should use a templating library. There are many for JavaScript.
In general, you should use a whole JavaScript application framework. There are three main ones nowadays:
ReactJS
Vue.js
Angular 2
For the sake of honesty, note I don't follow my own advice and still use Angular. (The original, not Angular 2.) But this is a steep learning curve. There are a lot of libraries that also include templating abilities.
But you've obviously got a whole project already set up and you want to just plug in a template into existing JavaScript code. You probably want a template language that does its thing and stays out of the way. When I started, I wanted that too. I used Markup.js . It's small, it's simple and it does what you want in this post.
https://github.com/adammark/Markup.js/
It's a first step. I think its loops feature are what you need. Start with that and work your way to a full framework in time.
Take a look at this - [underscore._template]
It is very tiny, and useful in this situation.
(https://www.npmjs.com/package/underscore.template).
const targetElement = document.querySelector('#target')
// Define your template
const template = UnderscoreTemplate(
'<a class="<%- link.className %>" href="<%- link.url %>">\
<div class="<%- wrapper.className %>">\
<div class="<%- thumbnail.className %>">\
<img src="<%- thumbnail.image %>">\
</div>\
<div class="<%- info.className %>">\
<div class="<%- info.title.className %>"><%- info.title.text %></div>\
</div>\
</div>\
</a>');
// Define values for template
const obj = {
link: {
className: 'scoreboard-video-outer-link',
url: '#someurl'
},
wrapper: {
className: 'scoreboard-video--wrapper'
},
thumbnail: {
className: 'scoreboard-video--thumbnail',
image: 'http://via.placeholder.com/350x150'
},
info: {
className: 'scoreboard-video--info',
title: {
className: 'scoreboard-video--title',
text: 'Pelicans # Bulls Postgame: E`Twaun Moore 10-8-17'
}
}
};
// Build template, and set innerHTML to output element.
targetElement.innerHTML = template(obj)
// And of course you can go into forEach loop here like
const arr = [obj, obj, obj]; // Create array from our object
arr.forEach(item => targetElement.innerHTML += template(item))
<script src="https://unpkg.com/underscore.template#0.1.7/dist/underscore.template.js"></script>
<div id="target">qq</div>

cannot get dynamic added custom select to work in jQuery Mobile

I have looked through all of the similar questions available at the time of this question, and none of the solutions presented worked in the below code. Google was also not helpful except that I did find a few issues with dynamic code where the entire menu was not wrapped, but those issues should be fixed with either the trigger or enhanceWithin methods - which have been tried here.
I am a fairly new with javascript and the jquery library and this is my first app with jquery mobile.
The raw html as generated from the php file:
<div class="cell_container force_org_select">
<label for"force_org[new_555]" class="ui-hidden-accessible">Troop Type</label>
<select name="force_org[new_555]" id="force_org[new_555]" class="roster_cell" data-mini="true">
<option value="hq">HQ</option>
<option value="elite">Elite</option>
<option value="solo">Solitaire</option>
<option value="formation">Std Formation</option>
</select>
The Javascript function that handles the dynamic injection:
$(document).on('click','.add_item', function(event) {
event.preventDefault();
var the_link = $(this).attr('href')
var area = getParameterByName(the_link, 'area');
var type = getParameterByName(the_link, 'type');
var squad_id = getParameterByName(the_link, 'squad_id');
var vehicle = getParameterByName(the_link, 'vehicle');
var divider = getParameterByName(the_link, 'divider');
var preset = $('#preset').val();
$.post(cmd_ajax.ajaxurl,{action: 'cmd_add_item_mobile', type: type, preset: preset, squad_id: squad_id, vehicle: vehicle, divider: divider}, function(data) {
if(type == 'squad' || type == 'divider') {
$('#list').append(data).enhanceWithin();
//$('#list').append(data).trigger("refresh");
//$('#list').append(data).trigger("create");
$('.squad_help_button').tooltipster({
contentCloning: true,
trigger: 'custom',
triggerOpen: {
click: true,
tap: true
},
triggerClose: {
click: true,
tap: true
}
});
}
else {
$('#' + area).append(data).enhanceWithin();
}
//console.log("squad_id:"+this_id);
set_unit_sortable();
});
return false;
});
I also tried adding the .selectmenu("refresh",true) within the function and that seems to do nothing. The custom selects that are not dynamically generated work fine.
If I use the data-native-menu="false" attribute on the generated select menus, the popup does not function and you cannot select anything, if I remove the attribute, the native select works as it should.
I thought about using a selectmenu() refresh at the very end of the function, but I can't seem to catch the element id of the select menu either. My only guess is that it isn't created yet in the DOM when I try to retrieve it.

How to use bootstrap-select or any other js control with Aurelia?

Not sure what I am missing here, probably something silly, but I am unable to find anything regarding, let's say how to use bootstrap-select control in Aurelia views. Can someone point me to the right article please?
PS: I am not looking to create another custom control out of bootstrap-select but use as it as.
Request for your help.
https://silviomoreto.github.io/bootstrap-select/
You can create a custom attribute that adds the bootstrap-select behavior to the <select> element. Here's an example:
http://plnkr.co/edit/So23Hm?p=preview
bootstrap-select.js
import {inject} from 'aurelia-framework';
const defaultOptions = {
style: 'btn-info',
size: 4
};
#inject(Element)
export class BootstrapSelectCustomAttribute {
constructor(element) {
this.element = element;
}
attached() {
let options = Object.assign({}, defaultOptions, this.value || {});
$(this.element).selectpicker(options);
}
detached() {
$(this.element).selectpicker('destroy');
}
}
app.html:
<template>
<require from="./bootstrap-select"></require>
<select value.bind="selectedPlanet" bootstrap-select>
<option model.bind="null">Select a planet</option>
<option repeat.for="planet of planets" model.bind="planet">${planet.name}</option>
</select>
</template>
app.js:
export class App {
selectedPlanet = null;
planets = [
{ name: 'Mercury', diameter: 3032 },
{ name: 'Venus', diameter: 7521 },
{ name: 'Earth', diameter: 7926 },
{ name: 'Mars', diameter: 4222 },
{ name: 'Jupiter', diameter: 88846 },
{ name: 'Saturn', diameter: 74898 },
{ name: 'Uranus', diameter: 31763 },
{ name: 'Neptune', diameter: 30778 }];
}
Pass options to the selectpicker call like this:
<select bootstrap-select.bind="{ size: 4 }">
Or like this:
<select bootstrap-select.bind="myOptions"> <!-- assumes there's a myOptions property on your view-model -->
You need to fit it into the Aurelia lifecycle, but other than that, you're free to do as you like.
If you look here you will find a couple of examples of using jQuery plugins in Aurelia. A relatively complex example being the Autocomplete widget. This is a custom control wrapping the jQuery plugin, which I think you don't want to do, but it should still give you an idea of how to implement it.
Extending Jeremy's answer and partially solving the refresh question, you could add something like this.
I couldn't find any legal way to get an event when the source of the repeater changed in a custom attribute. If anyone knows a better/elegant/decent way, please share.
Based on this answer Two way binding not working on bootstrap-select with aurelia, you could add a "task" that queries the length of the select and if the length changes, call the refresh.
With a little variation from the original, I decided to abort the task when the elements length changes the first time. In my case, they will always change only once, when updated after getting them from the database.
bind() {
var _this = this;
var sel = this.element;
var prevLen = sel.options.length || 0;
var addOpt = setInterval(function () {
var len = sel.options.length || 0;
if (len != prevLen || len > 0) {
clearTimeout(addOpt); //abort the task
$(_this.element).selectpicker("refresh");
}
}, 250);
};
As a way of disclaimer, I'm against writing code like this. It wastes resources when there should be a better way to solve this problem.
Finally, I started saying that it partially solved the refresh problem. Since it aborts the execution after the first change (assuming that no more changes are going to happen), if the items change more than once, this task would need to run forever.

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()

Categories