With protractor whats the best way to select child elements? Say we have the layout below...
<div id='parent_1'>
<div class='red'>Red</div>
<div class='blue'>Blue</div>
</div>
<div id='parent_2'>
<div class='red'>Red</div>
<div class='blue'>Blue</div>
</div>
With jQuery we'd do something like this.
var p1 = $('#parent_1');
var p1_red = $('.red', p1); //or p1.find('.red');
var p1_blue = $('.blue', p1); //or p1.find('.blue');
But with Protractor does it make sense to first get the parent element?
Since doing this var p1 = element('#parent_1'); doesn't actually retrieve/search for the object until getText() or something is called.
so doing this..
Scenario 1
expect(p1.element('.red')).toBe('red');
expect(p1.element('.blue')).toBe('blue');
OR
Scenario 2
expect(element('#parent_1').element('.red')).toBe('red');
expect(element('#parent_1').element('.blue')).toBe('blue');
OR
Scenario 3
expect(element('#parent_1 > .red')).toBe('red');
expect(element('#parent_1 > .blue')).toBe('blue');
Are there any benefits in one approach over the other?
This is what I'm doing but I don't know if there's any advantage of separating the parent from the cssSelector:
function getChild(cssSelector, parentElement){
return parentElement.$(cssSelector);
}
var parent = $('#parent_1');
var child_red = getChild('.red', parent);
var child_blue = getChild('.blue', parent);
Looking at Protractor's elementFinder I could be doing this:
function getChild(cssSelector, parentCss){
return $(parentCss).$(cssSelector);
}
var child_red = getChild('.red', '#parent_1');
var child_blue = getChild('.blue', '#parent_1');
The advantage of separating the child from the child css selector would only be if you'd like to use the parent for something else. Otherwise, it's slightly faster to do it in one call, like expect(element('#parent_1 > .red')).toBe('red'); since Protractor doesn't need to make two calls to the browser in this case.
Another reason to use the first approach would be if you were using a Locator strategy that cannot be expressed in CSS. For example:
var parent = element(by.css('.foo'));
var child = parent.element(by.binding('childBinding'));
expect(child.getText()).toEqual('whatever');
Related
I have an element with class="objbox" but this attribute have multiple instances.
The current code that I use for scrolling is browser.executeScript('$(".objbox").scrollLeft(' + strPixels + ')'); but since there are multiple instances, it seems like it is getting the first instance and scroll was not successfully done to the target element.
I am wondering if it is possible to include the parent element on my code, or if there is a different work around.
<div class="dhxgrid2-wrapper">
<div class="dhtmlxgrid-container gridbox">
<div class="objbox">
...
</div>
</div>
</div>
It's possible.
What you need to do is the following
// Define the elementfinder of your parent, pick option A or B
const elementFinderWithParentA = $('.dhtmlxgrid-container .objbox');
// Or
const elementFinderWithParentB = $('.dhtmlxgrid-container').$('.objbox');
// The amount to scroll
const scrollLeft = 50;
browser.executeScript('arguments[0].scrollLeft = arguments[1];', elementFinderWithParentA, scrollLeft);
// Or making it more readable, make a function for the scrolling
// and pass it to the browser.executeScript
function scrollToLeft(element, scrollAmount) {
element.scrollLeft = scrollAmount;
}
browser.executeScript(scrollToLeft, elementFinderWithParentA, scrollLeft);
Hope it helps
For instance with following html:
<div class='parent'>
<p>
<span class='child'></span>
</p>
</div>
To locate .parent from .child I'm trying something like :
let child = element(by.css('.child'));
let parent = child.element(by.xpath('../..')); // not working
let parent = child.element(by.xpath('ancestor::.parent')); // not working
What's the best way to achieve that?
You have mentioned incorrect locator combination. That is incuded css value '.parent' in xpath expression. The right way is:
let parent = child.element(by.xpath('ancestor::div'));
OR
let parent = child.element(by.xpath('ancestor::div[1]'));
I am able to do this using an ID prefix as the selector, but I need to be able to do it with classes instead. It's an each function for opening up different modal windows on the same page. I need to avoid using ID names because I have some modal windows that will have multiple links on the same page, and when using IDs, only the first link will work.
So here's the function as it works with IDs:
$('div[id^=ssfamodal-help-]').each(function() {
var sfx = this.id,
mdl = $(this),
lnk = $('.link-' + sfx),
cls = $('.ssfamodal-close'),
con = $('.ssfamodal-content');
lnk.click(function(){
mdl.show();
});
cls.click(function(){
mdl.hide();
});
mdl.click(function() {
mdl.hide();
});
con.click(function() {
return false;
});
});
and I'm trying to change it to classes instead, like:
$('div[class^=ssfamodal-help-]').each(function() {
var sfx = this.attr('class'),
etc.
But I cannot get it to work without using IDs. Is it possible?
EDIT Fixed error with semi-colon at end of Vars, and updated Fiddle with the fix. Still not working though.
Here's a Fiddle
** UPDATE **
To be clearer, I need to be able to refer to the same modal more than once on the same page. E.g.:
MODAL 1
MODAL 2
MODAL 3
MODAL 4
LINK TO MODAL 1
LINK TO MODAL 2
LINK TO MODAL 3
LINK TO MODAL 4
OTHER STUFF
LINK TO MODAL 1
LINK TO MODAL 4
LINK TO MODAL 3
OTHER STUFF
LINK TO MODAL 2
ETC.
When using classes get rid of the ID habit :
className1, className2, className3 ... etc
simply use
className
HTML:
<div class="ssfamodal-help-base ssfamodal-backdrop">
<div id="help-content" class="ssfamodal-content">
<span class="ssfamodal-close">[x]</span>
Howdy
</div>
</div>
<div class="ssfamodal-help-base ssfamodal-backdrop">
<div id="help-content" class="ssfamodal-content">
<span class="ssfamodal-close">[x]</span>
Howdy Ho
</div>
</div>
<span class="link-ssfamodal-help-base">One</span>
<span class="link-ssfamodal-help-base">Two</span>
LIVE DEMO
var $btn = $('.link-ssfamodal-help-base'),
$mod = $('.ssfamodal-help-base'),
$X = $('.ssfamodal-close');
$btn.click(function(i) {
var i = $('[class^="link-"]').index(this); // all .link-** but get the index of this!
// Why that?! cause if you only do:
// var i = $('.link-ssfamodal-help-base').index();
// you'll get // 2
// cause that element, inside a parent is the 3rd element
// but retargeting it's index using $('className').index(this);
// you'll get the correct index for that class name!
$('.ssfamodal-help-base').eq(i).show() // Show the referenced element by .eq()
.siblings('.ssfamodal-help-base').hide(); // hide all other elements (with same class)
});
$X.click(function(){
$(this).closest('.ssfamodal-help-base').hide();
});
From the DOCS:
http://api.jquery.com/eq/
http://api.jquery.com/index/
http://api.jquery.com/closest/
Here I created a quite basic example on how you can create a jQuery plugin of your own to handle modals: http://jsbin.com/ulUPIje/1/edit
feel free to use and abuse.
The problem is that class attributes can consist of many classes, rather than IDs which only have one value. One solution, which isn't exactly clean, but seems to work is the following.
$('div').filter(function () {
var classes = $(this).attr('class').split(/\s+/);
for (var i = 0; i < classes.length; i++)
if (classes[i].indexOf('ssfamodal-help-') == 0)
return true;
return false;
}).each(function() {
// code
});
jsFiddle
Or, equivalently
$('div').filter(function () {
return $(this).attr('class').split(/\s+/).some(function (e) {
return e.indexOf('ssfamodal-help-') == 0;
});
}).each(function() {
// code
});
jsFiddle
If there is one-to-one relationship between the modal helps and the modal links which it appears there is...can simplfy needing to match class values by using indexing.
For this reason you don't need unique class names, rather they just overcomplicate things. Following assumes classes stay unique however
var $helps=$('div[id^=ssfamodal-help-]');
var $help_links=$('div[id^=link-ssfamodal-help-]');
$help_links.click(function(){
var linkIndex= $help_links.index(this);
$helps.hide().eq( linkIndex ).show();
});
/* not sure if this is what's wanted, but appeared original code had it*/
$helps.click(function(){
$(this).hide()
})
/* close buttons using traverse*/
$('.ssfamodal-close').click(function(){
$(this).closest('div[id^=ssfamodal-help-]' ).hide();
});
Also believe that this code is a little more readable than original apporach
DEMO
Can you try this,
$('div[class^=ssfamodal-help-]').each(function() {
var sfx = $(this).attr('class');
console.log(sfx);
/*console log:
ssfamodal-help-base ssfamodal-backdrop
ssfamodal-help-base2 ssfamodal-backdrop
*/
});
Demo: http://jsfiddle.net/xAssR/51/
why don't you write like
$('div.classname').each(function() {
// you can write your desired code here
var sfx = this.attr('class');
var aa= this.attr('id');
});
or
$('.classname').each(function() {
// you can write your desired code here
var sfx = this.attr('class');
var aa= this.attr('id');
});
where classname is the name of the class used for the div in html
Thanks.
In the example code below, I want to know why the variable called child must be global ( no var) in order for the code to work. I also want to know if the code below is considered bad practice due to having a global variable and how a better practiced rendition of the code below might look. Thanks.
<!DOCTYPE html>
<meta charset="UTF-8">
<title>dom</title>
<div class="product">
<h2> Product Name </h2>
<img src="pic.jpg" />
<p> Description </p> </div>
<script>
var products = document.getElementsByClassName("product"),
child; // how come var breaks the code ?
for ( i = 0; i < products.length; i++) {
child = products[i].firstChild;
while (child.nodeType !== 1) {
child = child.nextSibling;
}
console.log(child);
}
</script>
You already have a var, as there is a comma before child. Hence adding var would give you
var product, var child
which is illegal.
child is not global, because the var in
var product, child
applies to the whole list of variables following var. (Well, child is global anyway since it is not nested in a function. But that does not have to do with var or not var.)
If you insist on having var twice, write
var product = ... ;
var child;
Let's assume I have a part of an html document containing the following code (basic structure) :
<p>
<span class="1">This is my first content</span>
<span class="2">This is my second content</span>
</p>
I'd like to allow the user to select a part of the text and apply a new class to it.
Let's say the user selects "is my first" in the first span, and applies class "3".
I'd like to have the following result :
<p>
<span class="1">This </span>
<span class="3">is my first</span>
<span class="1"> content</span>
<span class="2">This is my second content</span>
</p>
I've managed to do this on Firefox by using the execCommand "InsertHTML", but I can't find a way to do this in IE (before IE9)
The only result I have is a nested span element, like below :
<p>
<span class="1">This <span class="3">is my first</span> content</span>
<span class="2">This is my second content</span>
</p>
Do you have any idea of how I could achieve this ?
Any help would be much appreciated !
By the way, if this looks too simple to you, how would you handle the case of a user selecting a portion of text that spans over 2 or more spans ? over 2 or more ps ?
you can get the selected segment using selection range. I would recommend using rangy, which is a cross browser range module.
Here's some "untested" code using jQuery and Rangy to hopefully point you in the right direction, for your first case:
var splitTag=function(class){
var sel = rangy.getSelection();
// this is your selection, in your example "is my first"
var r0 = sel.getRangeAt(0);
// create a new range
var r1 = rangy.createRange();
// this would be your <p>
var p = r0.endContainer.parentNode;
// set the new range to start at the end of your phrase and to end at <p>
r1.setStart(r0.endContainer, r0.endOffset);
r1.setEnd(p, p.length-1);
// extract the content of your first selection "is my first"
var r0Txt=r0.toHtml();
// make it into an span, with class set to "class argument" which would be 3
var newContent=$("<span/>").html(r0Txt).attr("class", class);
r0.deleteContents();
// insert the new node before r1
r1.insertNode(newContent[0]);
sel.removeAllRanges();
}
this should get you the result for your first situation. for selections across multiple paragraphs, here's a modification of the code:
var splitTag=function(class){
var sel = rangy.getSelection();
var r0 = sel.getRangeAt(0);
var r1 = rangy.createRange();
var p = r0.endContainer.parentNode;
r1.setStart(r0.endContainer, r0.endOffset);
r1.setEnd(p, p.length-1);
var r0Txt=r0.toHtml();
if(!r0.startContainer===r0.endContainer){
// the selection spans multiple dom's
// set the class of all spans in the highlight to 3
var newContent=$(r0Txt).find("span").attr("class",class);
}else{
var newContent=$("<span/>").html(r0Txt).attr("class", class);
}
r0.deleteContents();
r1.insertNode(newContent[0]);
sel.removeAllRanges();
}