4
\$\begingroup\$

I have a table of user info. When you click the button, I want to get the email address from each selected row, output into a comma-separated string.

I got it to work with the following code, but I'm a novice with JS so I assume it must be inefficient:

function tableToEmail() {

  var recipients = [];
  $('input.selector:checked').each(function(){
          var email = $(this).closest('tr').find('.email');
          recipients.push(email.text());
  });
  console.log(recipients.join(','));

}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
<table>
   <tr>
      <td><input type="checkbox" class="selector"></td>
      <td class="name">John Doe</td>
      <td class="country">Australia</td>
      <td class="email">[email protected]</td>
    </tr>
    <tr>
      <td><input type="checkbox" class="selector"></td>
      <td class="name">Jane Smith</td>
      <td class="country">Canada</td>
      <td class="email">[email protected]</td>
    </tr>
</table>

<button onclick="tableToEmail()">Get Emails</button>

The real table will likely have 75-150 rows. Each row will have about 8 cells. Is there a better way to achieve this, preferably with vanilla JS if jQuery is not needed?

\$\endgroup\$
0

3 Answers 3

3
\$\begingroup\$

Calling push() inside an each() loop can typically be simplified with map()

Typically when an array is created and then a loop iterates over another array to push into that first array then the .map() method could be used instead. With this approach the array can be assigned once and push() does not need to be called within the loop.

jQuery has its own .map() method. An array can be created using it combined with the .get() method.

function tableToEmail() {

  var recipients = $('input.selector:checked').map(function(){
          var email = $(this).closest('tr').find('.email');
          return email.text();
  }).get();
  console.log(recipients.join(','));

}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
<table>
   <tr>
      <td><input type="checkbox" class="selector"></td>
      <td class="name">John Doe</td>
      <td class="country">Australia</td>
      <td class="email">[email protected]</td>
    </tr>
    <tr>
      <td><input type="checkbox" class="selector"></td>
      <td class="name">Jane Smith</td>
      <td class="country">Canada</td>
      <td class="email">[email protected]</td>
    </tr>
</table>

<button onclick="tableToEmail()">Get Emails</button>

A simpler selector can be used

Instead of selecting the checbox inputs, then navigating up to its parent before finding the sibling element with class email, one could also use the jQuery :has() selector along with the Subsequent-sibling combinator ~ to find the elements with class email that have a previous sibling <td> element which has a checked checkbox - i.e. td:has(input.selector:checked) ~ .email

function tableToEmail() {

  var recipients = $('td:has(input.selector:checked) ~ .email')
    .get() // Retrieve the elements matched by the jQuery object.
    .map(el => $(el).text())
  console.log(recipients.join(','));

}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
<table>
   <tr>
      <td><input type="checkbox" class="selector"></td>
      <td class="name">John Doe</td>
      <td class="country">Australia</td>
      <td class="email">[email protected]</td>
    </tr>
    <tr>
      <td><input type="checkbox" class="selector"></td>
      <td class="name">Jane Smith</td>
      <td class="country">Canada</td>
      <td class="email">[email protected]</td>
    </tr>
</table>

<button onclick="tableToEmail()">Get Emails</button>

Using vanilla JS without jQuery

As the main heading on https://youmightnotneedjquery.com/ states:

You might not need jQuery

There is a CSS selector :has() which has only been supported by modern browsers since December 20231. One could use document.querySelectorAll() to get all the elements, put them into an array using the spread syntax ... and then call .map() to put the innerText of each element into an array.

function tableToEmail() {
  const emails = document.querySelectorAll('td:has(input.selector:checked) ~ .email');
  const recipients = [...emails].map(el => el.innerText)
  console.log(recipients.join(','));
}
<table>
   <tr>
      <td><input type="checkbox" class="selector"></td>
      <td class="name">John Doe</td>
      <td class="country">Australia</td>
      <td class="email">[email protected]</td>
    </tr>
    <tr>
      <td><input type="checkbox" class="selector"></td>
      <td class="name">Jane Smith</td>
      <td class="country">Canada</td>
      <td class="email">[email protected]</td>
    </tr>
</table>

<button onclick="tableToEmail()">Get Emails</button>

\$\endgroup\$
1
  • 2
    \$\begingroup\$ Very thorough explanation. I like using :has to save a trip up and back down. \$\endgroup\$
    – LBF
    Commented Apr 26 at 19:33
3
\$\begingroup\$

You can go with a one-liner along with less traversel like this:

recipients.push($(this).closest('td').siblings('.email').text());

Your code traversal pattern:

enter image description here

VS

Mine code traversal pattern:

enter image description here

Note: This above solution can raise concerns related to readability, but for me, it's perfectly readable and fine.

\$\endgroup\$
1
  • \$\begingroup\$ Thanks, I've implemented your solution. Since it's a small enough function, I can still understand it with the removal of the email variable. \$\endgroup\$
    – LBF
    Commented Apr 26 at 15:04
-1
\$\begingroup\$
  1. Convert to named functions, deconstructed by their operation (single responsibility principle)
  2. Use a functional approach to building the string
function tableToEmail() {
    const getSelectedRows = () => $('input.selector:checked')
    const getEmail = (rowNode) => rowNode.closest('tr').find('.email')
    return getSelectedRows().map(getEmail).join(',')
}
\$\endgroup\$
1
  • \$\begingroup\$ I'm still learning -- could you share what this is achieving? Would this be faster? \$\endgroup\$
    – LBF
    Commented Apr 26 at 15:05

Not the answer you're looking for? Browse other questions tagged or ask your own question.