I am using the following code to remove an element from the DOM tree:
function onItemDeleted(name) {
$("#" + name).remove();
}
Would this be bad for performance since I am not indicating any parent for the element. The element is a TR contained in a TABLE element. The DOM search for this element will start at the top which might be BODY. So would it be something like this:
BODY => DIV => TABLE => TR (found)
If I find the parent of TR which is TABLE would the search be like this:
TABLE -> TR
I don't know if above will be true since I think search will always start at the root node.
jQuery optimises for ID searches. So, $("#" + name) is effectively the same as $(document.getElementById(name)). From the source, line 120 (1.4):
// HANDLE: $("#id")
} else {
elem = document.getElementById( match[2] );
The difference in performance would likely be negligible.
I guess that when you find elements by ID, the lookup time should be O(1) since there can be only one element with that ID.
Related
I need to assert that a <table> element contains both a nested <thead> and a <tbody> element.
Sure, an obvious solution would be sth. like this:
cy.get('table')
.find('thead')
cy.get('table')
.find('tbody')
but isn't there a simple way to to that all in one chain? Like this:
cy.get('table')
.should('contain.element','thead')
.should('contain.element','tbody')
Unfortunatelly there is no such contain.element.
I know, there is .should('contain.html','...') but it doesn't work for me because this would require me to specify the full html string including nested content (with tr, td). I only want to assert that both child elements thead and tbody are existing.
Try jQuery .has()
cy.get('table')
.should($table => {
expect($table.has('thead')).to.eq(true)
expect($table.has('tbody')).to.eq(true)
})
Or same thing in one line
cy.get('table:has(thead):has(tbody)') // pseudo selector :has()
Or same thing in one selector
cy.get('table thead, table tbody') // jQuery Multiple Elements Selector
.should('have.length', 2) // passes if both exist
Update: Okay, I missed your note, related .should('contain.html'..). Usually, contain.html returns true if a node is a descendant of a target node, no matter if it has a deep three child nodes.
You can use should in combination with and. .and() yields the same subject it was given from the previous command:
cy.get('table')
.should('contain.html','thead')
.and('contain.html','tbody')
Another way would be to create an array of elements and using a forEach() loop assert that they are visible, something like this:
const elements = ['table thead', 'table tbody']
elements.forEach((ele) => {
cy.get(ele).should('be.visible')
})
There is an element I really want to select with document.querySelector. The problem is that the makers of the site I work on, didn't give any identifier to this element.
It's just one of about 600 spans in the webpage with a huge DOM and I've no desire (nor the spare time) to manually count all spans in the DOM to know what item reference span[n] // n == number to use as it would be a nightmare and there must be some other better way, at least to get the item reference via devtool.
Selection ways I tried so far
I tried to copy "Css path" or "Css selector" and put either inside my code:
let myEl = document.querySelector('CSS-PATH-FROM-DEVTOOL');
myEl.click();
let myEl = document.querySelector('CSS-SELECTOR-FROM-DEVTOOL');
myEl.click();
Yet the element wasn't selected in both cases and no click happened.
Selecting via textDocument isn't good because there are many spans with the same textDocument.
Utilizing Xpath for selection
I understand that in such a "special" case when an element has no IDs, classes, to make it unique and is just one tag of many of the same kind, and neither CSS path or CSS selector helps targeting, then I could use Xpath.
Well, when I copy the Xpath I get:
*[#id="js_30"]/div/ul/li[4]/a/span/span
My question
How could this be used to select the element with document.querySelector?
As I'm new to JS, I tried the following which didn't help, the element won't be clicked:
let myEl = document.querySelector(' *[#id="js_30"]/div/ul/li[4]/a/span/span ');
MyEl.click();
Is there a way to target to select only that particular element, directly?
Based on an answer by Utkanos, you can do this:
var elem,
xpath = '*[#id="js_30"]/div/ul/li[4]/a/span/span',
jq_sel = xpath
.substr(14) // remove id part
.replace(/\//g, ' > ')
.replace(/\[(\d+)\]/g, function ($0, i) { return ':nth-child(' + i + ')'; });
// add id part: #js_30 > div > ul > li:nth-child(4) > a > span > span
jq_sel = '#js_30' + jq_sel;
// query element
elem = document.querySelectorAll(jq_sel);
// simulate a mouse click
elem.click();
console.log("*");
console.log("xpath:", xpath);
console.log("jq_sel:", jq_sel);
console.log("elem:", elem);
I am trying to use querySelectorAll() to get each 'td' element that have align attribute && is child of 'tr'
this works :
document.querySelectorAll('tr>td');
and this works :
document.querySelectorAll('[align]');
but how to combine them ?
Preface: There's no point whatsoever to tr> in front of td: The only valid parent element for a td is a tr. So I've left that off below.
That depends on what you want to do.
If you only want td elements with an align attribute:
document.querySelectorAll("td[align]")
Or only td elements that are children of tr elements that have an align attribute
document.querySelectorAll("tr[align]>td")
Or only elements with an align attribute that are children of td elements:
document.querySelectorAll("td[align]")
Or only elements with an align attribute that are descendants (not necessarily direct children) of td elements:
document.querySelectorAll("td [align]")
...and so on; full details in the spec.
Re your comment below:
It works, but is there a way to not select the first td from each tr ?
There's nothing about that in your question.
You could use td:not(:nth-child(0)) which means "a td that is not the first child of its parent" provided that you never have a script element as the first child of a tr (which is valid, but an odd thing to do). With that proviso, it works, because only td and script are valid children for tr.
Or you could just punt and select all the relevant tds and then:
var list = Array.prototype.slice.call(document.querySelector("whatever"), 1);
...which will give you an array skipping the first entry in the list returned by querySelectorAll.
Re your further comment:
tr[style]>td[align]:not(:nth-child(0)) returned 550 node lists which is the same as tr[style]>td[align]
Right. Again, :nth-child looks to see what child it is, not where it falls in the list chosen by the previous selector.
If you want to skip the first td in each row, and you want to ignore tr that don't have a style attribute, it's more complicated:
var result = Array.prototype.reduce.call(
document.querySelectorAll("tr[style]"),
function(list, row) {
list.push.call(
list,
Array.prototype.slice.call(row.querySelectorAll("tr[align]"), 1)
);
return list;
},
[]
);
You can combine selectors the same way as in CSS, like:
document.querySelectorAll('tr>td[align]');
If you haven't nested tables, it's the as document.querySelectorAll('td[align]');.
Combine them like this:
document.querySelectorAll('tr>td[align]');
More info: CSS Attribute Selectors
Note: <td> is only permitted as a child of <tr> anyway.
Inside a number of <tr>'s in my document there are 7 classes, each one sometimes has a corresponding class name (i.e. sub, id, assigned, sub, sub, status, and one classless "td"). Within the td class = "status" lies a span class defined as either
<span class = "statusBox">Passed</span>
or
<span class = "statusBox">Failed</span>.
If the text contained within the span class is "Passed", I need to delete the entire <tr>. If it's failed it stays on the document. How can I achieve this?
My first attempt was through a call such as:
function() {
if ($('tr td span.statusBox').html() == "Passed"){
$('tr td span.statusBox').hide();
}
But this removed every instance of only the phrase "Passed", not the entire <tr>.
I've also tried
$("tr td span.statusBox:contains('Passed')").each(function(){
$(this).css("visibility","hidden");
});
I feel like this is more along the right path, but I can't seem to get it to work.
You were close: find the status box with the word 'Passed' and then find the closest ancestor tr element and remove it from the DOM:
$("tr td span.statusBox").filter(":contains('Passed')").each(function(){
$(this).closest('tr').remove();
});
Since the :contains operator is a jQuery extension (it has been deprecated from the CSS spec), it's faster to break the selector into two parts. The first is finding all spans of class statusBox, this will look up the elements using native browser methods. Then those elements are filtered using the :contains operator (which may have a native implementation or may be implemented in jQuery, depending on the browser).
It is because you are altering the visibility of the span instead of the parent <tr>. You can use $(this).closest('tr') to get to the row element and then alter its visibility.
$("tr td span.statusBox:contains('Passed')").each(function(){
$(this).closest('tr').css("visibility","hidden");
});
$(".statusBox").each(function (i, e) {
if ($(e).text() == "Passed") {
$(e).closest('tr').hide();
}
});
http://jsfiddle.net/B7Wqy/
I have JS function which parses through a table:
// id contains a message id, activeRow is "this" from onClick on tr
function doSomething ( id, activeRow ) {
// AJAX calling using id as parameter
$("#searchResultTable > tbody > tr").each(function(index) {
$(this).removeClass("bold");
});
}
This works perfectly fine (thanks to Ariel # other post), but I was thinking that there should be another possibility, like:
var table = $(activeRow).parent();
$("tbody > tr", table).each(function(index) {
// .. do something
});
// The above clearly doesn't work, but should document what I'm looking for.
This would allow the usage of the same table ID while the function would work on each of them separately.
Many, many thanks!
jQuery's parents() method makes getting the parent table a snap:
$(activeRow).parents('table')[0];
What about:
$(activeRow).siblings().andSelf().removeClass("bold")
That'll take the <tr> in activeRow, grab its sibling <tr>s too, and remove the "bold" class from all of them.
It may be better to use closest as follows:
$(activeRow).closest('table')[0];
From here: http://api.jquery.com/closest/
closest: Travels up the DOM tree until it finds a match for the supplied selector.
parents: Travels up the DOM tree to the document's root element, adding each ancestor element to a temporary collection; it then filters that collection based on a selector if one is supplied.
In this scenario it may well be that parents gets the top most table where there are more than one in the DOM tree, where as closest gets the one you are actually trying to work on.
Close, you would want to use a class on the table
$("table.className tbody tr").each(function() {
// .. do something
});