Selecting a certain dropdown element using xpath (or better alternative) - javascript

How would I select a certain element in this drop down list.
<li role="presentation" ng-repeat="match in $matches" ng-class="{active: $isActive($index)}" class="ng-scope active">
<a style="cursor: default" role="menuitem" tabindex="-1" ng-click="$select($index, $event)">
<!-- ngIf: $isMultiple && $isActive($index) -->
<i class="glyphicon glyphicon-ok pull-right" ng-if="$isMultiple && $isActive($index)"></i>
<!-- end ngIf: $isMultiple && $isActive($index) -->
<span ng-bind="match.label" class="ng-binding">Streamer</span>
</a>
</li>
I tried this
element(by.model('selectedAvailable')).click();
element(by.xpath('..//ul//li[1]')).click().
and this:
element(by.repeater('match in $matches').row(0)).click();

An alternative would be to use by.cssContainingText :
element(by.cssContainingText('[ng-repeat="match in $matches"]', 'Streamer')).click();

I would filter() it, assuming you know the "Streamer" label and want to select it:
var matches = element.all(by.repeater('match in $matches'));
matches.filter(function (match) {
return match.element(by.binding("match.label")).getText().then(function (text) {
return text === "Streamer";
});
}).first().click();
Or, in a similar fashion with evaluate() instead of getText():
var matches = element(by.repeater('match in $matches'));
matches.filter(function (match) {
return match.evaluate("match.label").then(function (label) {
return label === "Streamer";
});
}).first().click();

Related

How to click on a child element

There many items and I get object of the desired item. But I don't know how can I click on the child element in this object.
html:
<div class="item">
<div role="button" tabindex="-1">
<strong>ItemName2</strong>
</div>
<div class="d">
<div class="item-icon" role="button" tabindex="-1" style="display: none">
<i aria-label="icon: add" class="add"></i> <!-- I need to click on this Item -->
</div>
<div class="item-icon" role="button" tabindex="-1" style="display: none">
<i aria-label="icon: del" class="del"></i>
</div>
</div>
</div>
<div class="item"> ... </div>
<div class="item"> ... </div>
<div class="item"> ... </div>
js:
let fBtns = await driver.findElements(By.tagName('strong')); // Find all objects
let buttons = fBtns.map(elem => elem.getText());
const allButtons = await Promise.all(buttons);
console.log(allButtons); // All object names
let current = fBtns[fBtns.length - 1];
console.log(current); // This is desired object
await current.click(); // This is click on the object and operates as expected
// But I need to click on the <i aria-label="icon: add" class="add"> element
// How can I click on the desired element?
To click the element <i aria-label="icon: del" class="del"></i>, you can just use an XPath to query directly on the element:
await driver.findElement(By.xpath("//div[div/strong[text()='ItemName2']]/div/div/i[#class='del']")).click()
You can probably shorten this a bit to:
await driver.findElement(By.xpath("//div[div/strong[text()='ItemName2']]//i[#class='del']")).click()
Try invoking click by trigger method:
$('.item-icon .add').trigger("click");
In the below example, I scan the document for a dynamic xpath that finds the strong with string ItemName2 and then traverse back up one level (/../) before moving back down to the child element. This will act like a waitForElement that you can hopefully repurpose to trigger a click.
var MyDefaultTimeout = 1500;
var loaded = false;
do {
var icon = document.getElementsByClassName('//*[contains(#strong,\'ItemName2\')]/../div/div/i');
if(!icon.length == 0)
{
setTimeout(function() { loaded = false }, MyDefaultTimeout);
}
else
{
if(!document.readyState === 'complete')
{
setTimeout(function() { loaded = false }, MyDefaultTimeout);
}
else
{
loaded = true;
return document.readyState;
}
}
}
while(loaded === false);

Changing a word in a class using javascript

I am working on a site that need to have certain words changed. The problem is the HTML etc is not controlled by myself. e.g. I can not edit the html. However, I can append extra code to the end.
The code I need to change is the wording "CHANGE ME" in:
<div class="price">
<a class="button" ng-click="commands.order.execute()"
ng-class="(!session.hidePrice && project.project.requiredCalculation) ? 'refreshing' : ''">
<span class="icon mdi mdi-cart" ng-class="::$root.icons.basket"></span>
<currency value="project.project.total.total"
class="price-total ng-isolate-scope ng-hide"
ng-show="!session.hidePrice && project.project.pricing">
<span class="currency ng-binding">
<span ng-class="left" class="ng-binding currency-sign-left">£</span>
1,089.02<span ng-class="right" class="ng-binding ng-hide">£</span>
</span>
</currency>
<span ng-show="session.hidePrice || !project.project.pricing" class="ng-binding">
CHANGE ME
</span>
</a>
</div>
I can change the whole class using:
<script type="text/javascript">
var myClasses = document.getElementsByClassName("price");
for (var i = 0; i < myClasses.length; i++) {
myClasses[i].innerHTML = "new content";
}
</script>
Problem is, this removes all the coding and replaces with "new content". What I need to do is keep the content the same but replace the wording "CHANGE ME" with something else.
p.s. ideally I want to say something like Change the wording "CHANGE ME" if it is in the class "price" and the class="ng-binding" (or another identifier, don't know if you can use "ng-show" to identify??)
If possible, please include a fiddle
p.s. needs to be onload so no buttons etc (so no user input needed)
If you cannot change the Angular code, you can use a selector.
div.price > a.button span:nth-child(3)
document.querySelector('div.price > a.button span:nth-child(3)').innerHTML = 'Hello World!';
<div class="price">
<a class="button" ng-click="commands.order.execute()" ng-class="(!session.hidePrice && project.project.requiredCalculation) ? 'refreshing' : ''">
<span class="icon mdi mdi-cart" ng-class="::$root.icons.basket"></span>
<currency value="project.project.total.total" class="price-total ng-isolate-scope ng-hide" ng-show="!session.hidePrice && project.project.pricing">
<span class="currency ng-binding">
<span ng-class="left" class="ng-binding currency-sign-left">£</span>
1,089.02
<span ng-class="right" class="ng-binding ng-hide">£</span>
</span>
</currency>
<span ng-show="session.hidePrice || !project.project.pricing" class="ng-binding">
CHANGE ME
</span>
</a>
</div>
You can write a function to loop over all matching elements and either replace some or all of the content.
setContent('div.price > a.button > span', 'CHANGE ME', 'Hello World!', false);
function setContent(selector, pattern, text, replaceContent) {
var els = document.querySelectorAll(selector);
for (var i = 0; i < els.length; i++) {
var el = els[i];
if (replaceContent) {
el.innerHTML = el.innerHTML.replace(pattern, text);
} else {
if ((pattern instanceof RegExp && el.innerHTML.match(pattern)) ||
(typeof pattern === 'string' && el.innerHTML.trim() === pattern)) {
el.innerHTML = text;
}
}
}
}
<div class="price">
<a class="button" ng-click="commands.order.execute()" ng-class="(!session.hidePrice && project.project.requiredCalculation) ? 'refreshing' : ''">
<span class="icon mdi mdi-cart" ng-class="::$root.icons.basket"></span>
<currency value="project.project.total.total" class="price-total ng-isolate-scope ng-hide" ng-show="!session.hidePrice && project.project.pricing">
<span class="currency ng-binding">
<span ng-class="left" class="ng-binding currency-sign-left">£</span>
1,089.02
<span ng-class="right" class="ng-binding ng-hide">£</span>
</span>
</currency>
<span ng-show="session.hidePrice || !project.project.pricing" class="ng-binding">
CHANGE ME
</span>
</a>
</div>
If the purpose is to only look at text content, then you should iterate over all the text nodes and apply a test/replace on each of those. You can use createTreeWalker to iterate over such nodes:
function textNodesUnder(el){
var n, a=[], walk=document.createTreeWalker(el,NodeFilter.SHOW_TEXT,null,false);
while(n=walk.nextNode()) a.push(n);
return a;
}
textNodesUnder(document.querySelector('.price')).forEach(function(node) {
node.data = node.data.replace('CHANGE ME', 'new Content');
});
<div class="price">
<a class="button" ng-click="commands.order.execute()" ng-class="(!session.hidePrice && project.project.requiredCalculation) ? 'refreshing' : ''">
<span class="icon mdi mdi-cart" ng-class="::$root.icons.basket"></span>
<currency value="project.project.total.total" class="price-total ng-isolate-scope ng-hide" ng-show="!session.hidePrice && project.project.pricing"><span class="currency ng-binding"><span ng-class="left" class="ng-binding currency-sign-left">£</span>1,089.02<span ng-class="right" class="ng-binding ng-hide">£</span></span></currency>
<span ng-show="session.hidePrice || !project.project.pricing" class="ng-binding">
CHANGE ME
</span>
</a>
</div>
Vanilla possible solution:
To replace a string you can use String.prototype.replace().
If you need instead to append some text in innerHTML you can use +- (JavaScript Assignment Operator) which will append your new content in the current content visible in innerHTML;
myClasses[i].innerHTML += "new content";
Generic example using Assignment Operator:
let result = document.getElementById('result');
document.getElementById('button').addEventListener('click', event => {
result.innerHTML += 'new content</br>';
});
<button id="button">Click me!</button>
<div id="result">Hello some content here!</br></div>
why not using angularjs with ng-bind:
<span ng-show="session.hidePrice || !project.project.pricing"
ng-bind="(session.hidePrice || !project.project.pricing) ? 'CHANGE ME' : 'new Content'"
class="ng-binding">
</span>
Using a directive:
app.directive('price', function(){
return {
restrict:'C',
link:function(scope, el, attrs){
angular.element(el).find('span').last().html('new Content');
}
};
})

Why value in directive does not get updated - Angular Js?

I just encounter a problem I have written a directive but its not getting update, I dont know why, in console it does change but in directive it does not.
Here is my directive
mainControllers.directive('mallsproduct', function () {
return {
restrict: 'E',
scope: {
productInfo: '=info',
linkid: '=linkid'
},
templateUrl: 'directives/dashboard_product.html'
};
});
Here is my `html`
<div class="aa-properties-content-body mg-7" ng-controller="DashboardController as ctrl">
<ul class="aa-properties-nav aa-list-view">
<li style="border: 1px solid #ccc;margin-bottom: 25px;" ng-repeat="active_products in productInfo.items">
<article class="aa-properties-item mg-top-0-notimp">
<a class="aa-properties-item-img" href="#/product/{{active_products.id}}">
<img ng-if="active_products.photos[0].path" resize-image alt="img" class="" src="{{active_products.photos[0].path}}">
<img ng-if="!active_products.photos[0].path" resize-image class="" src="img/default_product.jpg" alt="">
</a>
<div class="aa-properties-item-content">
<div class="aa-properties-about padding-0-notimp">
<h5>{{active_products.name| limitTo : 10}}{{active_products.name.length > 10 ? '...' : ''}}</h5>
<p class="font-size-11-imp"><i class="fa fa-building-o" aria-hidden="true"></i> {{active_products.mall.name| limitTo : 10}}{{active_products.mall.name.length > 10 ? '...' : ''}}</p>
<p class="font-size-11-imp"><i class="fa fa-map-marker" aria-hidden="true"></i> {{active_products.mall.address| limitTo : 10}}{{active_products.mall.address.length > 10 ? '...' : ''}}</p>
<p class="font-size-11-imp"><i class="fa fa-phone" aria-hidden="true"></i> {{active_products.shop.telephone}}</p>
<p class="font-size-11-imp" ng-if="linkid == 3"><i class="fa fa-eye" aria-hidden="true"></i> {{active_products.views}}</p>
<div class="modal-demo">
<script type="text/ng-template" id="myModalContent.html">
<div ng-include src="'partials/update_product.html'"></div>
</script>
<div ng-controller="AddProductController">
<button ng-click="view_product(active_products.id)"><i class="fa fa-pencil" aria-hidden="true"></i></button>
<button ng-click="del_product(active_products.id)"><i class="fa fa-trash-o" aria-hidden="true"></i></button>
<button ng-if="linkid == 2" ng-init="status = 1" ng-click="reactivate_product(active_products.id, status)"><i class="fa fa-lock" aria-hidden="true"></i></button>
</div>
<div class="modal-parent">
</div>
</div>
</div>
</div>
</article>
</li>
</ul>
<div class="aa-title pad-top-30" ng-if="linkid == 1">
<p>Global page count for active product is {{global_pagecount}} and active product count from API is {{productInfo._meta.pageCount}}</p>
<h3 ng-if="global_pagecount < productInfo._meta.pageCount" class="text-align-center color-feroz cursor-pointer" ng-click="load_more(global_pagecount, linkid)">{{$root.translated_labels.dashboard.load_more}}</h3>
</div>
<div class="aa-title pad-top-30" ng-if="linkid == 3">
<p>Global page count for most viewed is {{global_pagecount_mostv}} and most viewed count from API is {{productInfo._meta.pageCount}}</p>
<h3 ng-if="global_pagecount_mostv < productInfo._meta.pageCount" class="text-align-center color-feroz cursor-pointer" ng-click="load_more(global_pagecount_mostv, linkid)">{{$root.translated_labels.dashboard.load_more}}</h3>
</div>
</div>
I am including directive in dashboard partial like this
<div class="active tab-pane" ng-if="linkid === '1'">
<malls-product info="active_products" linkid="linkid"></malls-product>
</div>
<!--Active products list ends here -->
<!-- Get Inactive Products -->
<div class="active tab-pane" ng-if="linkid === '2'" >
<malls-product info="$root.inactive_products" linkid="linkid"></malls-product>
</div>
<!--Get Inactive products ends here -->
<div class="active tab-pane" ng-if="linkid === '3'" >
<malls-product info="$root.mostviewed_products" linkid="linkid"></malls-product>
</div>
<!-- View Profile-->
and This is the api which does show the result in console.
$scope.global_pagecount = 1;
$scope.active_product = function () {
$http.get($rootScope.baseurl + 'abc?&page=' + $scope.global_pagecount,
{headers:
{'Content-Type': 'application/x-www-form-urlencoded',
'Authorization': $rootScope.keyword_auth_token, 'Accept-Language': $cookies.get('type')}
})
.success(function (data) {
//$scope.active_product_pageCount = data._meta.pageCount;
if ($scope.global_pagecount === 1) //I know for sure the first page of pagination is 1
{
$scope.active_products = data;
}
if ($scope.global_pagecount > 1) // If user click load more global count gets incremented and new results push in active_producst
{
/* for loading new results Pagination Applied */
for (var times = data.items.length - 1; times >= 0; times--) {
$scope.active_products.items.push(data.items[times]);
}
}
console.log($scope.active_products);
})
.error(function (data) {
// console.log(data);
});
};
What is the issue, why it is not getting update, If I use rootscopethen it works fine, obviously it has too, but not with $scope.
Note : when scope.global_pagecount value is equal to 2 i get new results but not in directive only in console. By default scope.global_pagecount has value equal to 1.
You don't use your directive correctly. You define it as:
mainControllers.directive('mallsproduct'
Which means you should use it as:
<mallsproduct ..>
Or define your directive camelcased:
mainControllers.directive('mallsProduct'
Then you can use it as you do now:
<malls-product ..>
This is because of the Isolated scope doesn’t know anything about its parent scope. You just created a directive with an isolated scope.
To access any parent scope data, we need to pass the scope data to our directive explicitly. This is achieved by setting properties on the scope object in the DDO.
Another important thing is that, these properties also MUST be set as the attributes of the directive html element.

Get elements between 2 elements

I have this html:
<div class="categories">
<div class="list-group">
<a class="root list-group-item" id="427" style="display: block;"><span class="glyphicon indent0 glyphicon-chevron-down"></span><span>Home</span></a>
<a class="list-group-item first active" id="428" style="display: block;"><span class="glyphicon indent1 glyphicon-chevron-right"></span><span>Images</span></a>
<a class="list-group-item child" id="431" style="display: none;"><span class="glyphicon indent2"></span><span>Sub category</span></a>
<a class="list-group-item first" id="429" style="display: block;"><span class="glyphicon indent1 glyphicon-chevron-right"></span><span>Videos</span></a>
<a class="list-group-item child" id="432" style="display: none;"><span class="glyphicon indent2"></span><span>Another sub</span></a>
<a class="list-group-item first" id="430" style="display: block;"><span class="glyphicon indent1"></span><span>Documents</span></a>
</div>
</div>
and what I need to do is select all elements between a.active.
To explain that a little better; a.active has a span.glyphicon with the class indent1, so I need to select the elements between indent1 and the next indent1
I attempted to use jQuery's nextAll function but couldn't get it to work correctly :(
any help would be appreciated,
/r3plica
Update 1
Thanks to Arun, here is my script which now works:
$(".glyphicon", treeRoot).on('click', function (e) {
e.stopPropagation();
var $glyph = $(this);
var $item = $glyph.parent();
var indent = $item.find(".glyphicon").prop('className').match(/\b(indent\d+)\b/)[1];
console.log($item[0].outerHTML);
console.log(indent);
if (indent != undefined) {
var $children = $item.nextUntil("a:has(." + indent + ")");
console.log($children[0].outerHTML);
if ($glyph.hasClass("glyphicon-chevron-right")) {
$glyph
.addClass('glyphicon-chevron-down')
.removeClass("glyphicon-chevron-right");
if ($children != null) $children.show();
} else {
$glyph
.addClass('glyphicon-chevron-right')
.removeClass("glyphicon-chevron-down");
if ($children != null) $children.hide();
}
}
});
Try
$('.active').nextUntil('a:has(.indent1)')
To dynamically determine the indent value
var $active = $('.active');
var indent = $active.find('.glyphicon').prop('className').match(/\b(indent\d+)\b/)[1];
var $nexts = $active.nextUntil('a:has(.' + indent + ')');
console.log($nexts)
Demo: Fiddle
Try using nextUntil()
Live Demo
$('.active:has(.indent1)').nextUntil(':has(.indent1)')
Maybe with nextUntil (from Jquery)
Look at http://api.jquery.com/nextUntil/
$('.active').nextUntil('.indent1');

How do I toggle an ng-show in AngularJS based on a boolean?

I have a form for replying to messages that I want to show only when isReplyFormOpen is true, and everytime I click the reply button I want to toggle whether the form is shown or not. How can I do this?
You just need to toggle the value of "isReplyFormOpen" on ng-click event
<a ng-click="isReplyFormOpen = !isReplyFormOpen">Reply</a>
<div ng-show="isReplyFormOpen" id="replyForm">
</div>
Basically I solved it by NOT-ing the isReplyFormOpen value whenever it is clicked:
<a ng-click="isReplyFormOpen = !isReplyFormOpen">Reply</a>
<div ng-init="isReplyFormOpen = false" ng-show="isReplyFormOpen" id="replyForm">
<!-- Form -->
</div>
If based on click here it is:
ng-click="orderReverse = orderReverse ? false : true"
It's worth noting that if you have a button in Controller A and the element you want to show in Controller B, you may need to use dot notation to access the scope variable across controllers.
For example, this will not work:
<div ng-controller="ControllerA">
<a ng-click="isReplyFormOpen = !isReplyFormOpen">Reply</a>
<div ng-controller="ControllerB">
<div ng-show="isReplyFormOpen" id="replyForm">
</div>
</div>
</div>
To solve this, create a global variable (ie. in Controller A or your main Controller):
.controller('ControllerA', function ($scope) {
$scope.global = {};
}
Then add a 'global' prefix to your click and show variables:
<div ng-controller="ControllerA">
<a ng-click="global.isReplyFormOpen = !global.isReplyFormOpen">Reply</a>
<div ng-controller="ControllerB">
<div ng-show="global.isReplyFormOpen" id="replyForm">
</div>
</div>
</div>
For more detail, check out the Nested States & Nested Views in the Angular-UI documentation, watch a video, or read understanding scopes.
Here's an example to use ngclick & ng-if directives.
Note: that ng-if removes the element from the DOM, but ng-hide just hides the display of the element.
<!-- <input type="checkbox" ng-model="hideShow" ng-init="hideShow = false"></input> -->
<input type = "button" value = "Add Book"ng-click="hideShow=(hideShow ? false : true)"> </input>
<div ng-app = "mainApp" ng-controller = "bookController" ng-if="hideShow">
Enter book name: <input type = "text" ng-model = "book.name"><br>
Enter book category: <input type = "text" ng-model = "book.category"><br>
Enter book price: <input type = "text" ng-model = "book.price"><br>
Enter book author: <input type = "text" ng-model = "book.author"><br>
You are entering book: {{book.bookDetails()}}
</div>
<script>
var mainApp = angular.module("mainApp", []);
mainApp.controller('bookController', function($scope) {
$scope.book = {
name: "",
category: "",
price:"",
author: "",
bookDetails: function() {
var bookObject;
bookObject = $scope.book;
return "Book name: " + bookObject.name + '\n' + "Book category: " + bookObject.category + " \n" + "Book price: " + bookObject.price + " \n" + "Book Author: " + bookObject.author;
}
};
});
</script>
If you have multiple Menus with Submenus, then you can go with the below solution.
HTML
<ul class="sidebar-menu" id="nav-accordion">
<li class="sub-menu">
<a href="" ng-click="hasSubMenu('dashboard')">
<i class="fa fa-book"></i>
<span>Dashboard</span>
<i class="fa fa-angle-right pull-right"></i>
</a>
<ul class="sub" ng-show="showDash">
<li><a ng-class="{ active: isActive('/dashboard/loan')}" href="#/dashboard/loan">Loan</a></li>
<li><a ng-class="{ active: isActive('/dashboard/recovery')}" href="#/dashboard/recovery">Recovery</a></li>
</ul>
</li>
<li class="sub-menu">
<a href="" ng-click="hasSubMenu('customerCare')">
<i class="fa fa-book"></i>
<span>Customer Care</span>
<i class="fa fa-angle-right pull-right"></i>
</a>
<ul class="sub" ng-show="showCC">
<li><a ng-class="{ active: isActive('/customerCare/eligibility')}" href="#/CC/eligibility">Eligibility</a></li>
<li><a ng-class="{ active: isActive('/customerCare/transaction')}" href="#/CC/transaction">Transaction</a></li>
</ul>
</li>
</ul>
There are two functions i have called first is ng-click = hasSubMenu('dashboard'). This function will be used to toggle the menu and it is explained in the code below. The ng-class="{ active: isActive('/customerCare/transaction')} it will add a class active to the current menu item.
Now i have defined some functions in my app:
First, add a dependency $rootScope which is used to declare variables and functions. To learn more about $roootScope refer to the link : https://docs.angularjs.org/api/ng/service/$rootScope
Here is my app file:
$rootScope.isActive = function (viewLocation) {
return viewLocation === $location.path();
};
The above function is used to add active class to the current menu item.
$rootScope.showDash = false;
$rootScope.showCC = false;
var location = $location.url().split('/');
if(location[1] == 'customerCare'){
$rootScope.showCC = true;
}
else if(location[1]=='dashboard'){
$rootScope.showDash = true;
}
$rootScope.hasSubMenu = function(menuType){
if(menuType=='dashboard'){
$rootScope.showCC = false;
$rootScope.showDash = $rootScope.showDash === false ? true: false;
}
else if(menuType=='customerCare'){
$rootScope.showDash = false;
$rootScope.showCC = $rootScope.showCC === false ? true: false;
}
}
By default $rootScope.showDash and $rootScope.showCC are set to false. It will set the menus to closed when page is initially loaded. If you have more than two submenus add accordingly.
hasSubMenu() function will work for toggling between the menus. I have added a small condition
if(location[1] == 'customerCare'){
$rootScope.showCC = true;
}
else if(location[1]=='dashboard'){
$rootScope.showDash = true;
}
it will remain the submenu open after reloading the page according to selected menu item.
I have defined my pages like:
$routeProvider
.when('/dasboard/loan', {
controller: 'LoanController',
templateUrl: './views/loan/view.html',
controllerAs: 'vm'
})
You can use isActive() function only if you have a single menu without submenu.
You can modify the code according to your requirement.
Hope this will help. Have a great day :)

Categories