Selector: Fastest way to get descendants of a specific level only - javascript

I have a table like:
<table id="table">
<tbody>
<tr></tr>
<tr>
<td>
<table>
<tbody>
<tr></tr>
</tbod>
</table>
</td>
</tr>
</tbody>
</table>
I'm using jQuery and there exists a stored selector to get the most outer table:
var x = $('#table')
Starting from that If want to get all first level <tr>-elements.
If I use one of those:
x.find('tbody > tr');
x.children('tbody').children();
… the first one will naturally select all nested <tr>-elements as well. The latter seems over-complicated and involves multiple queries.
Is there a way to make this faster/more efficient?

First thing, x.find('tbody > tr') would find all <tr>s. You would need to do x.find('> tbody > tr'), assuming x is x from your example.
I ran a test and this with both and this was my finding.
.children(): 3.013ms
>: 0.626ms
so the > method is faster than the .children() method. The function calls add up... barely.
Here's my JavaScript for the testing.
var $table = $('#table'), $usingChildren, $usingAngleBracket;
console.time('.children()');
$usingChildren = $table.children('tbody').children('tr');
console.timeEnd('.children()');
console.time('>');
$usingAngleBracket = $table.find('> tbody > tr');
console.timeEnd('>');
console.log( $usingChildren, $usingAngleBracket );

the fastest way to get direct children of a parent is .children, so what you can do is:
$('tbody').children('tr')
.find() will search child of child too, so you may not want to use that.

Use can use jQuery's .first() method to find the first <tr> element,
$('#mytable tr').first()
Although, as you wish to find the first <tr> that has nested child elements, you can filter it with .has(). For example: http://jsfiddle.net/cwL4q/3/
$("#mytable tr").has('tbody').first().css("background-color", "red" );
Although, I would strongly suggest simply labelling the 'nested' <tr>'s with a class, then you can simply access them much quicker as you know.
$('.nestedrow');
For the HTML below:
<table id="table">
<tbody>
<tr></tr>
<tr class="nestedrow">
<td>
<table>
<tbody>
<tr></tr>
</tbod>
</table>
</td>
</tr>
</tbody>
</table>

Related

Cypress iterate table rows get specific element in cells

I'm trying to iterate through table rows and get each row which includes a specific value,
but it doesn't work for me.
I'm using .each() to iterate the rows and .within() on each $el,
inside that, I use cy.get('td').eq(1).contains('hello') but I the get assertion error:
Timed out retrying: Expected to find content: 'hello' within the element: <td> but never did.
when I console.log cy.get('td').eq(1) it yields the desired cell in each row and the test passes, so I don't understand why chaining .contains() doesn't work...
it('get element in table', () => {
cy.visit('http://localhost:3000/');
cy.get('tbody tr').each(($el) => {
cy.wrap($el).within(() => {
cy.get('td').eq(1).contains('hello') // contains() doesn't work
})
})
});
<table>
<thead>
<tr>
<th>Month</th>
<th>Savings</th>
</tr>
</thead>
<tbody>
<tr>
<td>January</td>
<td>$100</td>
</tr>
<tr>
<td>February</td>
<td>hello</td>
<td>$80</td>
</tr>
<tr>
<td>$10</td>
<td>hello</td>
</tr>
</tbody>
</table>
should('have.text', text) should work
cy.get('td').eq(1).should('have.text', 'hello')
If there's whitespace around text, use contain.text
cy.get('td').eq(1).should('contain.text', 'hello')
The simple answer is: don't :)
To be more specific use html attribute selection instead. The convention is to have an attribute named data-cy. Furthermore, I discovered it convenient to have a data-cy-identifier for when selecting specific rows. Since I'm not sure what you're trying with your code, I'll use a similar example that can hopefully get you going:
<table data-cy="expences">
<tr>
<td data-cy="month">January</td>
<td data-cy="price">$100</td>
</tr>
<tr data-cy="discounted">
<td data-cy="month">Feburary</td>
<td data-cy="price">$80</td>
</tr>
<tr data-cy="discounted">
<td data-cy="month">March</td>
<td data-cy="price">$10</td>
</tr>
</table>
You can of course do all sorts of combinations of this, but now you can do more specific and useful selections, such as:
cy.get('[data-cy="expenses"]').find('[data-cy="discounted"]').find('[data-cy="price"]').should(...)
And similar. This is flexible, because it reflects the structure of your data, and not the presentation, so you can change this to a list or whatever later. It avoids selecting of volatile things, so it's also more robust. It also uses a hierarchy rather than overly specific selectors.
The idea of adding things like data-cy-identifier allows you to do selections by ID (you can propagate it using javascript, angular, vue or whatever you use) and then checking things like the contents of a row with logically related items.
Hope it can get you going. Also I can recommend reading: https://docs.cypress.io/guides/references/best-practices.html

Find elements in a table from a td that has an id

Hi guys I have this question
I have this structure:
<table>
<tbody>
<tr>
<td id="id1"><td>
<td><td>
<td><td>
<td><td>
</tr>
...
</tbody>
</table>
I have the td id and I want to find the table element. Then I want to find, change and show/hide the 4-th tr, but I can't.
I tried $("td#id1") and it finds the td but how can I use it to get the table element and then the 4-th row. They have no IDs.
Another problem I must not change the structure in any way, no new IDs can be added.
You can use closest() along with find() and :eq() selector:
$("td#id1").closest('table').find('tr:eq(3)').show(); // or hide() here

How to select first td element and its text with Jquery

I want to change the "Yes! Pick me" into "Picked" with Jquery in the following HTML structure, I used $('#myDiv>table>tr>td>table>tr').eq(1).text("Picked"); But it was not working. Could someone shed some light on this please? Thanks!
FYI, the first td of the the first table itself contains another table...
<div id="myDiv">
<table>
<tr>
<td>
<table>
<tr>
<td>Yes! Pick me!</td>
<td>Not me..</td>
</tr>
<tr>
<td>Not me..</td>
</tr>
</table>
</td>
<td>Not me..</td>
</tr>
<tr>
<td>Not me..</td>
</tr>
</table>
</div>
The section $('#myDiv>table>tr>td>table>tr>td').eq(1).text("Picked"); does the trick, I forgot the last td part. Thanks to Rocket and everyone's help.
Try this:
$("#myDiv table table td:first").text("Picked")
$('#myDiv').find('table table td').eq(0).text(...);
Start your selection at the #myDiv element ($('#myDiv')), then find all the TD element that are inside a table that is inside another table (.find('table table td')), then only alter the first one (.eq(0)).
Documentation:
.find(): http://api.jquery.com/find
.eq(): http://api.jquery.com/eq
The main problem is that you want .eq(0) not .eq(1) as .eq() is 0-based, and you are not selecting the td, only the tr.
Other than that using > direct descendant selectors makes your selection not very robust at all.
Try $('#myDiv table table td').eq(0).text('Picked');
You can try:
$("td:contains('Yes! Pick me!')").text("Picked"); ​
You can use the :contains(text) selector
$('#myDiv td table td:contains(Yes! Pick me!)').text('Picked');
Be careful with nested tables however because if you were to use just
$('#myDiv td:contains(Yes! Pick me!)').text('Picked');
You would get both the cell your after plus the cell it is nested within.
Your child selector query won't work because HTML5 requires the parser to insert <tbody> elements inside your <table> elements, since you've forgotten to put them in yourself. Perhaps you should consider validating your HTML?

Better way to find an element within a element

I am looking a better method than "find" to find all the elements within an element:
<table>
<tr>
<td class="test">
Test
</td>
</tr>
<tr>
<td class="test">
Test
</td>
</tr>
<tr>
<td class="test">
Test
</td>
</tr>
</table>
This is structure and and I have jquery of table as $table
I am willing to do something like this:
$table.find('.test');
Find is a very expensive call. I need a better way to do it. I know I can do it with direct selectors like this:
$('table .test');
But I am looking for an alternative for find which performs better. Find performs really bad especially in IE.
The biggest thing you can do is to improve the selector you are using:
$table.find('td.test');
Providing an element name speeds up the selection significantly. This is especially true in versions of IE before IE9, which lack the getElementsByClassName method.
If you have getElementsByClassName, this won't offer a significant improvement. If you don't, however, the improvement will be massive. jQuery has to select every element that might match, then test to see if it has the relevant class present. If you provide td, it will only test td elements. If you don't, it has to test every element, which will obviously take much longer.
find is slow in IE because IE does not implement a native getElementsByClassName. The fastest solution is apply knowledge of your DOM structure to the function that selects the elements. It is less flexible but always will be the fastest solution. In this case, it looks like the only elements that have the test class are td:
var rows = $table[0].rows, cells, i, j, numRows, numCells, result = [];
for(i = 0, numRows = rows.length; i<numRows; i++) { //Iterate over the table's rows
cells = rows[i].children;
for(j = 0, numCells = cells.length; j<numCells; j++) { //Iterate over the cells in the row
if(cells[j].className == "test") { //Won't be valid if the cells have more than one class
result.push(cells[j]);
}
}
}
This is just an example. Like I said, you sacrifice flexibility for speed, so you may need to tweak it to adapt to your particular structure but the principle remains.
I think if you use
document.getElementById('myTable')
on
<table id="myTable">
<tr>
<td class="test">
Test
</td>
</tr>
<tr>
<td class="test">
Test
</td>
</tr>
<tr>
<td class="test">
Test
</td>
</tr>
</table>
Will be fastest as possible.
I made a jsperf to test $('.test', $('#my_table')) vs $('table .test');
http://jsperf.com/find-vs-direct
It looks like $('.test', $('#my_table')) is the winner

Looping through table rows with Javascript/Jquery

So what I'm trying to do is get the last row of an HTML table. If this row then has a certain class I will ignore this row and select the previous one. This would then be cycled through from the end of the table until a row was found without this certain class.
I figured it's probably involving a for loop, a check for the row class and then JQuery's row.prev method, but still not quite sure how to approach this.
Thanks in advance!
To get the last table row that doesn't have a certain class, say targetClass, you can do this:
$("tr:not(.targetClass):last");
I'm not sure what you want to do with this table row, but if you were to add targetClass to the last row that didn't have it, it would look like this
$("tr:not(.targetClass):last").addClass("targetClass");
Check out this fiddle to see it in action
This example shows you how to get the last of each table on the current page: http://jsfiddle.net/JBnzK/
$('table').find('tr:last').each(function(){
if ($(this).hasClass('stupid')) {
$(this).css('color', 'red');
} else {
$(this).css('color', 'green');
}
});
Assuming you've got the following HTML:
<table id="mytable">
<tbody>
<tr>
<td>1</td>
</tr>
<tr id="YouFoundMe">
<td>1</td>
</tr>
<tr class="certainclass">
<td>1</td>
</tr>
<tr class="certainclass">
<td>1</td>
</tr>
<tr class="certainclass">
<td>1</td>
</tr>
</tbody>
</table>
You can do this:
var elWithoutClass = $('#mytable tr:not(.certainclass):last');
if (elWithoutClass.length) {
alert(elWithoutClass.get(0).id);
// alerts "YouFoundMe"
}
:not(.certainclass) will eliminate <tr> without class 'certainclass'
:last will get you the last one
I invite you to check the Selectors documentation page of jquery to learn more about them.

Categories

Resources