Handling Drop Down using Protractor - javascript

I am facing issues with handling a trivial scenario during automation. I need to select a specific option using Protractor. I am passing the index of the option that I want to select, and then clicking on that to select it. However, my click() method errors out stating the click() method is undefined on that index.
Here is what I am trying to do - in my selectElements.js file, the dropdown method is defined as
const selectElement={}
selectElement.selectElementDropdown =function(element,index,milliseconds){
console.log("Selecting element by drop down","OK");
element.all(by.tagName('option')).then(function(options){
options[2].click();
//here index 2 is hardcoded, which can be changed to options[index]
});
if(typeof milliseconds!=='undefined'){
browser.sleep(milliseconds);
}
}
module.exports =selectElement;
I am using a POM structure, so the dropdown method is in a separate .js file. I am calling this in my page file
const methodDropDown = require('../BasePage/selectElements.js');
var signUpBankDetails = function(){
this.bankName = element.all(by.css('.form-group')).get(7).element(by.xpath("//label[text()='Select Bank Name']"));
//the selector is clicked to open the drop down
console.log("Start of this block =========>");
this.selectDropDownMethod = function(){
console.log("Drop Down method start ==========>");
this.bankName.click();
browser.sleep(1000);
methodDropDown.selectElementDropdown(this.bankName,0,1000);
};
I am getting the error which says -
Failed: Cannot read property 'click' of undefined
The this.bankName.click() block is working fine because, I can see that the element is clicked and the drop down appears, however the selection seems to be erroring out. I have also attached the HTML snippet of code below -
PS- The webpage is using Angular2.

When I look at the HTML I see that the label is not containing the select. The select is in the <div class="ui-helper-hidden-accessible">..</div> that is on the same level as the label.
When I look at you PO I see you are passing the label (this.bankName) as an ElementFinder with this methodDropDown.selectElementDropdown(this.bankName,0,1000);. The methodDropDown.selectElementDropdown() starts searching from the ElementFinder you pass and that is the label, not the <div class="ui-helper-hidden-accessible">..</div> that holds the select.
Maybe you can change it into something this:
// Define ElementFinder for the bankname component
const bankNameComponent = $('p-dropdown[formcontrolename="bankname"]');
// Open the dropdown
bankNameComponent.$('label').click();
// Click on an element by index
bankNameComponent.$$('select option').get(2).click();
// Or search by text in the dropdown
bankNameComponent.element(by.cssContainingText('option', 'BNI')).click();
Hope it helps

Solution 01
Select based on drop-down values attribute details
public selectOptionByValue(element: ElementArrayFinder,valueToSelect: string) : void{
let clickedIndex: number;
element.first().$$('option').filter(function(elem, index) {
return elem.getAttribute("value").then(function(value) {
if (value === valueToSelect) {
elem.click();
console.log('Yes ! Found option is:' + value);
}
return value === valueToSelect;
});
}).then(function (bool) {
}).catch(function (err) {
console.log('Ooops ! Error... '+err.message);
});
}
Solution 02
Select based on drop-down visible text details
public selectOptionByText(element: ElementArrayFinder,optionToSelect: string) : void{
let clickedIndex: number;
element.first().$$('option').filter(function(elem, index) {
return elem.getText().then(function(text) {
if (text === optionToSelect) {
elem.click();
console.log('Yes ! Found option is:' + text);
}
return text === optionToSelect;
});
}).then(function (bool) {
}).catch(function (err) {
console.log('Ooops ! Error... '+err.message);
});
}

Related

jQuery not finding elements (or updating class) after AJAX response

I have a lot of working functions using the jQuery.on() event, and they work fine - but only if it is binded to an element (like "a.my-link" or something).
This function hovever, is bind to the document or body, and then traverses multiple elements with the same class attribute. I have commented the lines, which do not fire.
selectable["holder"]: elements with the class "a.fr-drive-selectable"
if selected it adds the class "fr-drive-selected"
if more than one were selected (multiple), then it adds the class "fr-drive-selected-b"
I also have a function for selecting multiple elements using only the CTRL key + mouse - it works, because the on() function is binded to the element ...
/**
* Selecting or deselecting all available items (CTRL + ALT + A)
*
* - Selecting: CTRL + ALT + A
* - Deselecting: CTRL + ALT + D
*/
page.$body.on("keydown", function (shortcut) {
if (shortcut.ctrlKey && shortcut.altKey && shortcut.key === "a") {
selectable["$holder"].each(function () {
if (!$(this).children(fdrive["content"]["class"]).hasClass(selectable["multiple"]["class"])) {
if (!(items.indexOf(parseInt($(this).attr('data-id'))) > -1)) {
addItem(items, $(this)); // WORKS FINE!
}
addContextMenuOption($(this)); // WORKS FINE!
console.log(items); // WORKS FINE!
$(this).children(fdrive["content"]["class"]).removeClass(selectable["one"] ["class"]); //NOT WORKING AFTER AJAX
$(this).children(fdrive["content"]["class"]).addClass(selectable["multiple"]["class"]); //NOT WORKING AFTER AJAX
}
});
} else if (shortcut.ctrlKey && shortcut.altKey && shortcut.key === "d") {
selectable["$holder"].each(function () {
if ($(this).children(fdrive["content"]["class"]).hasClass(selectable["multiple"]["class"]) || $(this).children(fdrive["content"]["class"]).hasClass(selectable["one"]["class"])) {
$(this).children(fdrive["content"]["class"]).removeClass(selectable["multiple"]["class"]); //NOT WORKING AFTER AJAX
$(this).children(fdrive["content"]["class"]).removeClass(selectable["one"]["class"]); //NOT WORKING AFTER AJAX
resetContextMenuOptions(); // WORKS FINE!
items = []; // WORKS FINE!
console.log("Removed ALL"); // WORKS FINE!
}
});
}
updateItemCounter(items); // WORKS FINE!
});
The function works if no dynamic data is appended - after that, it only removes items, but not the classes (add() or remove()). I have used the console to print out elements - objects in each() after AJAX do not have "up to date" class attributes.
I know the problem is that the event is not bind to the elements in question, but I need a shortcut for handling the key event on the page, not on the element.
Similar function (works great, because it uses the element):
main_content["$holder"].on('contextmenu', 'a.fr-drive-file-selectable', function (ev) {
if (!(findItemById(items, $(this).attr("data-id")))) {
$(this).siblings(selectable["selector"]).children(fdrive["content"]["class"]).removeClass(selectable["multiple"]["class"]);
$(this).siblings(selectable["selector"]).children(fdrive["content"]["class"]).removeClass(selectable["one"]["class"]);
//resetContextMenuOptions();
items = [];
console.log("Adding one item (RESET)");
addItem(items, $(this));
console.log(items);
$(this).children(fdrive["content"]["class"]).removeClass(selectable["multiple"]["class"]);
$(this).children(fdrive["content"]["class"]).addClass(selectable["one"]["class"]);
}
});
For clarity, check out the console output. The blue line indicates the elements found on "keydown" event before AJAX updated the data. After that, jQuery does not find any element matching: "a.fr-drive-file-selectable - they are on the page!
Console output
** Additional info (after an answer was provided) **
<div id="main-content" class="fr-content">
<!-- AJAX CHANGES CONTENTS with the same elements, same class etc. (only the inner HTML of the elements is changed (ex. name, picture etc.) -->
<div class="fr-drive-file-selectable"><!-- name, picture --></div>
<div class="fr-drive-file-selectable"><!-- name, picture --></div>
<div class="fr-drive-file-selectable"><!-- name, picture --></div>
<div class="fr-drive-file-selectable"><!-- name, picture --></div>
<div class="fr-drive-file-selectable"><!-- name, picture --></div>
</div>
<script>
var selectable = {
$holder: $("a.fr-drive-file-selectable"),
"class": ".fr-drive-file-selectable"
}
</script>
<script>
...
request.done(function (response, textStatus, jqXHR) {
if (response["status"]) {
$(id).html(response["fragment"]); //Loads only div.fr-drive-file-selectable (using foreach in PHP)
stopPageLoader();
adjustSidenavHeight();
} else {
///
}
});
...
</script>
I do not have enough rep to add a comment.
Maybe you are trying to reference the elements before they are added to the DOM.
Also for dynamic elements try mentioning the parent element followed by the element you want to access in the selector query as follows.
$(".parent .dynamic-child").removeClass();
So here it goes ... found the answer. I DON'T KNOW WHY, if someone could later clarify.
So I simply changed the line selectable["$holder"] and declared a local variable within the function (check //DIFF).
Altered code
page.$body.on("keydown", .... {
let selector = "a.fr-drive-file-selectable";
$(this).find(selectable["$holder"]) // DIDNT WORK, works before AJAX updates DOM!
$(this).find(selector) // works ... why?
//selectable["$holder"].each(function () { CHANGED TO
$(this).find(selector).each(function () { // NEW CODE
...
}
Full code
/**
* Selecting or deselecting all available items (CTRL + ALT + A)
*
* - Selecting: CTRL + ALT + A
* - Deselecting: CTRL + ALT + D
*/
page.$body.on("keydown", function (shortcut) {
let selector = "a.fr-drive-file-selectable";
if (shortcut.ctrlKey && shortcut.altKey && shortcut.key === "a") {
$(this).find(selector).each(function () {
if (!$(this).children(fdrive["content"]["class"]).hasClass(selectable["multiple"]["class"])) {
if (!(items.indexOf(parseInt($(this).attr('data-id'))) > -1)) {
addItem(items, $(this));
}
addContextMenuOption($(this));
console.log(items);
$(this).children(fdrive["content"]["class"]).removeClass(selectable["one"]["class"]);
$(this).children(fdrive["content"]["class"]).addClass(selectable["multiple"]["class"]);
}
});
} else if (shortcut.ctrlKey && shortcut.altKey && shortcut.key === "d") {
$(this).find(selector).each(function () {
if ($(this).children(fdrive["content"]["class"]).hasClass(selectable["multiple"]["class"]) || $(this).children(fdrive["content"]["class"]).hasClass(selectable["one"]["class"])) {
$(this).children(fdrive["content"]["class"]).removeClass(selectable["multiple"]["class"]);
$(this).children(fdrive["content"]["class"]).removeClass(selectable["one"]["class"]);
resetContextMenuOptions();
items = [];
console.log("Removed ALL");
}
});
}
updateItemCounter(items);
});

Jquery Select Dynamically added element

Several similar question exist, but after fighting with this for a day or so I feel the need to ask because the vast majority of the answers refer to adding event handlers to elements.
I am not interested in adding an event handler to the elements in question, rather I am interested in adding additional dynamic content to dynamically generated content.
The app works thusly:
load a modal form dynamically upon the click of a static element (working properly)
function loadModal(target,modalId) {
console.log("==================> loadModal() Entry");
$.ajax({
type: "GET",
url: 'http://localhost/retrieve-modal/'+modalId,
success : function (text) {
$("#"+modalId)[0].innerHTML = text;
modalSaveIntercept($("#"+modalId)[0])
},
failure : function (e) {
console.log("something is wrong");
}
})
}
Then I have a save interceptor that overrides the default save behavior of my form here this is also working properly, (I suspect because I am loading this event handler at the time of loading the modal)
function modalSaveIntercept(eventTarget) {
if(eventTarget.hasChildNodes()) {
eventTarget.childNodes.forEach(function(e) {
if(e.tagName == "FORM") {
console.log("found the form: " + e.id + " applying save override listener");
$("#"+e.id).submit(function(event){
event.preventDefault();
submitForm(e);
});
modalSaveIntercept(e)
}
});
}
}
the above attaches a listener to the form loaded into my modal and rather than firing the default behavior of a Save button click, it fires my submitForm() function which is here:
function submitForm(form) {
let payload = constructPayloadFromFormData(form);
validate(payload).then(function(v) {
console.log("response Data:");
for(let p in v) {
if(v.hasOwnProperty(p)) {
constructInvalidFeedbackForProperty(p,v[p])
}
}
});
}
this function constructs a payload from the form data (working fine) then executes another ajax call inside of validate() - I wait for the return call from ajax and then iterate through an array of validation data to confirm the form's validity. However, here is where the problem is:
function constructInvalidFeedbackForProperty(prop,e) {
let el = $("#" + "ic-role-" + prop);
console.log(el);
el.append("<div class=\"invalid-feedback\">problem</div>");
}
the problem is the append - I cannot seem to fire that method. I can select the element as the console.log(el) writes to the log the correctly identified element in my dom.
What am I doing wrong?
I have created a contrived jsfiddle for a sample of the problem. I actually believe it may be that an input field is not something you can append to... perhaps? https://jsfiddle.net/jtango/xpvt214o/987051/
Okay, I messed around with your fiddle a bit. If you inspect the input element that is created you can see that your append does work. It's just not displaying. If you are trying to edit what is in the input box then you must use val()
Here is a copy of your fiddle that will display inside the input:
$("#top").on("click", function(){
$("#form").append("<label>some label: </label><input type=\"text\" id=\"myinput\">");
});
$("#btm").on("click",function(){
$("#myinput").val("<div>I will not appear</div>");
});
As your shared https://jsfiddle.net/jtango/xpvt214o/987051/ It will not appear, this is wrong way to append any HTML element inside "input box" or any of form elements. it should allow to set only new attribute or value.
check screenshot: https://i.stack.imgur.com/4FBgn.png
So verify your below code if it's similar then it will not work:
let el = $("#" + "ic-role-" + prop);
console.log(el);
el.append("<div class=\"invalid-feedback\">problem</div>");

how to trigger event on select2

hi guys i have some select tags in my selling item page. first one created by HTML and others are created by jquery append when user click on a button add more
<select id='code0' onclick='getvalue(this)'><option>1</option></select>
and in document.ready im applying select2 on this select tag
$(document).ready(function(){
$("#code0").select2();
})
function append(){
$("table").append("<tr><td><select id='code1' onclick='getvalue(this)'><option>1</option></select></td></tr>");
$("#code1").select2();
}
next appended select will have id of code2. I'm managing it without any issue
Now the function getvalue() is working fine before applying select2 but after applying select2 the select is not triggering click or any event. how can i trigger event after applying select 2;
function getvalue(item){
alert("event triggered"); // to check that jquery called this function or not
console.log(item.value);
}
clicking on select tags is not calling get value function;
whenever the page loads i can see this error on console
jQuery-2.1.4.min.js:2 Uncaught TypeError: Cannot read property 'call' of null
at Function.each (jQuery-2.1.4.min.js:2)
at MutationObserver.<anonymous> (select2.min.js:2)
Once you have linked select2 to existing select.
select2 override select and create list(li) for all options and select is hidden.
So any event added to select will not work directly.
Select2 plugin provides functions which you can use to server your need
Assign select2 ele as a var and use methods given by plugins
var select2Ele = $("#code0").select2();
select2Ele.on("select2:select", function (event) {
// after option is selected
});
select2Ele.on("select2:selecting", function (event) {
// before selection
});
For multiple select2 above code will work like this. In log method we have event which can be used to get current element value and other stuffs
$eventSelect.on("change", function (e) { log("change"); });
function log (name, evt) {
if (!evt) {
var args = "{}";
} else {
var args = JSON.stringify(evt.params, function (key, value) {
if (value && value.nodeName) return "[DOM node]";
if (value instanceof $.Event) return "[$.Event]";
return value;
});
}
var $e = $("<li>" + name + " -> " + args + "</li>");
$eventLog.append($e);
$e.animate({ opacity: 1 }, 10000, 'linear', function () {
$e.animate({ opacity: 0 }, 2000, 'linear', function () {
$e.remove();
});
});
}
Refer select2 doc

How to get the selected check boxes in Kendo Mobile

I'm new to kendo I have a list populated from a data source (with template : Each list item has mobile switch in it).I need to get all the id's of selected mobile switches when I clicked on a single button. Can anyone let me know how to do that in kendo ?
Cheers,
Chinthaka
I solved that Using following code (Only Active Items checked items)
var checkedWorkCentersIds = new Array();
$('#workcenters :checkbox:checked').map(function () {
if (this.checked == true) {
checkedWorkCentersIds.push(this.id);
}
});
Without the code it is hard to give an exact answer, but you should be able to just loop over the data items in the array and find the ones that are selected.
For example:
var dataSource = new kendo.data.DataSource( ... );
function getSelected() {
return $.grep(dataSource.view(), function (item) {
return item.selected;
});
}
Assuming the switches/checkboxes are bound to the "selected" property.
In response to your comment, you can get the selected checkbox element IDs with jQuery:
$("#workcenters :checkbox:checked").map(function (index, checkbox) {
return $(checkbox).attr("id");
});

Tooltip of previous onValidationError event displayed even when correct values are entered in the slickgrid node

I am using requiredFieldValidator for my TextEditor. Using the onValidationError event as given below, i set the title attribute of my cell to the error message so that a tooltip will be displayed as 'This is a required field'.
var handleValidationError = function(e, args) {
var validationResult = args.validationResults;
var activeCellNode = args.cellNode;
var editor = args.editor;
var errorMessage = validationResult.msg
$(activeCellNode).live('mouseover mouseout', function(event) {
if (event.type == 'mouseover') {
$(activeCellNode).attr("title", errorMessage);
} else {
$(activeCellNode).attr("title", "");
}
});
grid.onValidationError.subscribe(handleValidationError);
Successfully, the tooltip is displayed when there is some validation error.
But the problem is When the same cell is given a correct value and validation succeeds, the previous tooltip appears again.
How do I remove that tooltip on successful validation?
I have found a solution for this issue and it works fine.
By going through the slick.grid.js code, i understood that OnValidationError event will be triggered only when the 'valid' value from the validator is false.
My idea was to fire the onValidationError event whenever validator is called i.e on both validation success and failure, and to check for 'valid' value and handle the tooltip according to that value.
STEPS:
In slick.grid.js, I added the trigger for onValidationError event when 'valid' from validator is true also.
(i.e) add the below given code before return statement in if(validationResults.valid) in slick.grid.js
trigger(self.onValidationError, {
editor: currentEditor,
cellNode: activeCellNode,
validationResults: validationResults,
row: activeRow,
cell: activeCell,
column: column
});
2. In the onValidationError event handler of your slickgrid,get the value of the parameter 'valid'. If true, it means validation is success and remove tooltip i.e remove
title attribute for that node. If 'valid' is false, it means
validation has failed and add tooltip.i.e set the title attribute to
error message. By doing this, the tooltip of the previous
onValidationError will not appear on the same node. The code goes as
follows:
grid.onValidationError.subscribe(function(e, args) {
var validationResult = args.validationResults;
var activeCellNode = args.cellNode;
var editor = args.editor;
var errorMessage = validationResult.msg;
var valid_result = validationResult.valid;
if (!valid_result) {
$(activeCellNode).attr("title", errorMessage);
}
else {
$(activeCellNode).attr("title", "");
}
});
Hope this solution will help others for this issue.
HAPPY LEARNING!!!
Rather than editing the slick grid js - I've submitted a request for this change - in the meantime you can subscribe to the following events to remove the previous validation display:
grid.OnCellChange.Subscribe(delegate(EventData e, object a)
{
// Hide tooltip
});
grid.OnActiveCellChanged.Subscribe(delegate(EventData e, object a)
{
// Hide tooltip
});
grid.OnBeforeCellEditorDestroy.Subscribe(delegate(EventData e, object a)
{
// Hide tooltip
});
A much more appropriate way to implement this is to subscribe to the onBeforeCellEditorDestroy event and clean up the state (i.e. clear the tooltip) there.
I wasn't able to determine the current cell in OnBeforeCellEditorDestroy, so I just cleared the title in onCellChange, which fires before onValidationError. For example:
grid.onCellChange.subscribe(function (e, args) {
$(grid.getCellNode(args.row, args.cell)).children("input").attr( "title", "");
});

Categories

Resources