I have a piece of HTML like
<form action="/AssetBundle/DownloadFile" data-ajax="true" data-ajax-method="POST" data-ajax-mode="replace" data-ajax-update="#masterLinkHolder" id="form0" method="post"></form>
<table>
<tr>
<td>someimage.png</td>
<td><img src="imageicon.png"></td>
<td><button type="button">Click to Download</button></td>
</tr>
<tr>
<td>somedocument.docx</td>
<td><img src="docicon.png"></td>
<td><button type="button">Click to Download</button></td>
</tr>
</table>
<input type="hidden" id="file2Download" />
</form>
and when a button
is clicked I want to set the value
of the input with id
file2Download
to be the file name (e.g. someimage.png
, somedocument.docx
) from the same tr
as the button, and then submit the form. So I need help filling out
<script type="text/javascript">
$('button').click(function () {
$('#file2Download').val(
// ... ?
);
$('#id0').submit();
});
</script>
in the proper way. I know that I'm looking at a tree like
tr
/ | \
td td td
| \
img button
and trying to go from the button
to the grandparent tr
and then to the first child of that tr
.
I have a piece of HTML like
<form action="/AssetBundle/DownloadFile" data-ajax="true" data-ajax-method="POST" data-ajax-mode="replace" data-ajax-update="#masterLinkHolder" id="form0" method="post"></form>
<table>
<tr>
<td>someimage.png</td>
<td><img src="imageicon.png"></td>
<td><button type="button">Click to Download</button></td>
</tr>
<tr>
<td>somedocument.docx</td>
<td><img src="docicon.png"></td>
<td><button type="button">Click to Download</button></td>
</tr>
</table>
<input type="hidden" id="file2Download" />
</form>
and when a button
is clicked I want to set the value
of the input with id
file2Download
to be the file name (e.g. someimage.png
, somedocument.docx
) from the same tr
as the button, and then submit the form. So I need help filling out
<script type="text/javascript">
$('button').click(function () {
$('#file2Download').val(
// ... ?
);
$('#id0').submit();
});
</script>
in the proper way. I know that I'm looking at a tree like
tr
/ | \
td td td
| \
img button
and trying to go from the button
to the grandparent tr
and then to the first child of that tr
.
5 Answers
Reset to default 10tl;dr
The accepted answer is the second slowest suggestion.
The difference between vanilla javascript and jQuery is huge.
There are many ways to skin a cat, or find a grandparents first child.
Use vanilla javascript whenever possible.
Results
Here are the jsPerf results of all of the methods suggested thus far. Remember that higher numbers for ops/sec are better, I've ordered them from quickest to slowest
Element.parentNode.parentNode.firstElementChild.textContent;
@canon
(Demo)
characters = 60
ops/sec = 3,239,703Element.parentNode.parentNode.children[0].textContent;
@canon
(Demo)
characters = 54
ops/sec = 1,647,235Element.parentNode.parentNode.cells[0].textContent;
@canon
(Demo)
characters = 51
ops/sec = 1,558,070Element.parentNode.parentNode.querySelector('td:first-child').textContent;
@Tiny Giant
(Demo)
characters = 74
ops/sec = 1,189,826document.getElementById(Element.dataset.select).textContent;
@guest271314
(Demo)
characters = 60
ops/sec = 800,876$('#' + $(Element).data('select')).text();
@guest271314
(Demo)
characters = 42
ops/sec = 47,144$('td:first-child',Element.parentNode.parentNode).text();
@Tiny Giant
(Demo)
characters = 57
ops/sec = 18,305$(Element).parent().siblings(":eq(0)").text();
@guest271314
(Demo)
characters = 46
ops/sec = 17,633$(Element).closest('tr').find('td:first').text();
@j08691
(Demo)
characters = 49
ops/sec = 4,511$(Element).parentsUntil('table').find('td:first').text();
@AlvaroMontoro
(Demo)
characters = 54
ops/sec = 3,954
The first five methods all use vanilla javascript where the last five use jQuery. There is a very significant drop in ops/sec in even the fastest jQuery solution. The difference between the slowest vanilla method vs the fastest jQuery method puts the jQuery method at 1/17th the speed of the vanilla method.
As you can see the accepted answer is the second slowest. I would caution against the use of closest in this situation. The more specific you can be the faster it will run, and closest just turns it into a guessing game.
This just goes to show, while it may take a little bit more time to type, it will always be faster to use vanilla javascript. What's even better, vanilla javascript is included by default!
$('#file2Download').val(
$(this).closest('tr').find('td:first').text()
);
.closest()
will take you up the DOM from the button to the closest table row (<tr>
), then .find('td:first').text()
will search down the DOM and take the first table cell's text contents.
BTW, I assume you didn't mean $('#id0')
in your example since your form's ID is form0
, which would be selected via $('#form0')
.
The most elegant and efficient way to get the requisite grandparent's first child is to use plain old javascript. In this particular case, you already know exactly where to get the value you want. Just go get it by explicitly navigating the DOM on your own:
$("button").click(function(e) {
var el = this // button
.parentNode // parent
.parentNode // grandparent
.firstElementChild; // grandparent's first child
console.log(el.textContent);
});
<script src="https://ajax.googleapis./ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<table>
<tr>
<td>someimage.png</td>
<td><button type="button">Click to Download</button></td>
</tr>
<tr>
<td>somedocument.docx</td>
<td><button type="button">Click to Download</button></td>
</tr>
</table>
That's about as fast as you'll ever get. Comparable jQuery code is actually much, much slower, i.e.: ≤ 4.2% of the speed. All you'd maybe gain from a jQuery solution is a few characters. The moral of the story is, don't be afraid to use vanilla javascript in your jQuery environment. Use either or both where it makes sense.
Now, for cases in which you're unsure of your target element's position in the reference element's ancestry, check out closest()
:
For each element in the set, get the first element that matches the selector by testing the element itself and traversing up through its ancestors in the DOM tree.
var text = $(this).closest("tr").find("td:first-child").text();
$("button").click(function(e) {
var text = $(this).closest("tr").find("td:first-child").text();
console.log(text);
});
<script src="https://ajax.googleapis./ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<table>
<tr>
<td>someimage.png</td>
<td><button type="button">Click to Download</button></td>
</tr>
<tr>
<td>somedocument.docx</td>
<td><button type="button">Click to Download</button></td>
</tr>
</table>
Let's shed light on the matter that can arise when thinking of the most reliable & efficient way. I will briefly write here about the closest() and parent() functions, so as to make it clear to any reader which to use, when and why.
closest() gets the first element that matches the selector, a jQuery object which we pass onto the function, by testing the given jQuery object and traversing up the DOM tree looking for its ancestors.
parent() travels a single element up the DOM tree and selects the immediate element who is parent to the given jQuery object.
Their difference is significant, despite the fact that they both seem to function similarly as how they traverse up the DOM tree.
Differences:
closest() - Begins with the current element. Travels up the DOM tree until it finds a match for the supplied selector. The returned jQuery object contains zero or one element for each element in the original set, in document order
parent() - Begins with the parent element. 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. The returned jQuery object contains zero or more elements for each element in the original set, in reverse document order.
Therefore, parent would putationally provide a faster search due to its nature of going up a level into the parent tree structure. Keep in mind that this is the case since we're looking to find the grandparent.
Please refer to the jQuery documentation for more details on implementation.
$('#file2Download').val(
$(this).parent().siblings(":eq(0)").text()
);
$('button').click(function () {
$('#file2Download').val(
$(this).parent().siblings(":eq(0)").text()
);
console.log($("#file2Download").val())
//$('#id0').submit();
});
<script src="https://ajax.googleapis./ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<form action="" data-ajax="true" data-ajax-method="POST" data-ajax-mode="replace" data-ajax-update="#masterLinkHolder" id="form0" method="post"></form>
<table>
<tr>
<td>someimage.png</td>
<td><img src="imageicon.png"></td>
<td><button type="button">Click to Download</button></td>
</tr>
<tr>
<td>somedocument.docx</td>
<td><img src="docicon.png"></td>
<td><button type="button">Click to Download</button></td>
</tr>
</table>
<input type="hidden" id="file2Download" />
</form>
Note, See https://stackoverflow./a/30336313/
An alternative would be to add a data-*
attribute to button
elements , an id
to first td
; could then select first td
directly based on clicked button data-id="first-td"
html
<form action="/AssetBundle/DownloadFile" data-ajax="true" data-ajax-method="POST" data-ajax-mode="replace" data-ajax-update="#masterLinkHolder" id="form0" method="post"></form>
<table>
<tr>
<td id="select1">someimage.png</td>
<td><img src="imageicon.png"></td>
<td><button type="button" data-select="select1">Click to Download</button></td>
</tr>
<tr>
<td id="select2">somedocument.docx</td>
<td><img src="docicon.png"></td>
<td><button type="button" data-select="select2">Click to Download</button></td>
</tr>
</table>
<input type="hidden" id="file2Download" />
</form>
js
$("#file2Download").val(document.getElementById(this.dataset.select).textContent)