Home > Mobile >  How do I display the row number count in a dynamic javascript table?
How do I display the row number count in a dynamic javascript table?

Time:05-10

I'm looping over an array on the front end using ejs and displaying the data in a bootstrap 5 table. The table is dynamic so rows will be added and deleted with time.

All the data is coming through without an error and the table is populating, however, I'd like to have the first column show the "count" for each row. Eg., "1, 2, 3, 4, 5, etc".

I've tried using indexOf without any success and since the code already exists in a loop, creating another loop requires me to switch my ejs syntax and I lose the ability to count the length of the array.

Below is my client side code that yields value -1 down the entire # column for each row:

 <div >
  <a href=""  onclick="exportTableToCSV(null, 'text.csv')">Export to csv</a>
 </div>
 <div >
<table >
  <thead>
    <tr>
      <th scope="col">#</th>
      <th scope="col">Email</th>
      <th scope="col">Gender</th>
      <th scope="col">Age</th>
      <th scope="col">Clicked IG</th>
      <th scope="col">Date Added</th>
      <th scope="col">Remove</th>
    </tr>
  </thead>
  <tbody>
    <% for (let fan of artist.fans){%>
    <tr>
      <th scope="row"><%= artist.fans.indexOf(fan.id) %></th>
      <td><%= fan.email %></td>
      <td><%= fan.gender %></td>
      <td><%= fan.age %></td>
      <td><%= fan.subscribed %></td>
      <td><%= fan.dateAdded %></td>
      <td style="padding-left: 2rem;">
        <button onclick="removeRow()" ></button>
         </td>
      <td style="padding-left: 2rem;"><i ></i></td>
      <% } %> 
    </tr>
  </tbody>
</table>
</div>

If I switch artist.fans.indexOf(fan.id) with just fan.id I get the corresponding objectId for each fan email object.

If I switch artist.fans.indexOf(fan.id) with artist.fans.length I get 7 down the # column for each row.

Here's my db model:

const artistSchema = new Schema({
image: [ImageSchema], 
genre: [ String ], 
fans: [{ 
    email: String, 
    subscribed: String, 
    gender: String, 
    age: Number 
        dateAdded: {
            type: Date,
            default: Date.now
                }
      }], 
 });

How do I get each row to be numbered?

CodePudding user response:

the problem is that you used fan.id as a search param in artist.fans.indexOf(fan.id) which is not directly accessible throw the artist.fans so instead you need to use another method that accepts comparing function so you can compare their id

try to use

<th scope="row"><%= artist.fans.findIndex(f=> f.id == fan.id) %></th>

CodePudding user response:

TL;DR:

Just do this:

<% for( const fan of artist.fans.map( ( e, index ) => ({ index, ...e }) ) ) { %>
<tr>
    <th scope="row"><%= fan.index %></th>
    <td><%= fan.email %></td>
    <!-- etc -->
</tr>

<% }%>

Explanation:

I'd like to have the first column show the "count" for each row. Eg., "1, 2, 3, 4, 5, etc".

  • That's not a "count". I would refer to that as "the row-number" (if 1-based) or "row-index" (if 0-based).

  • If you're using SQL to get your data then you can just use ROW_NUMBER() in your query and map it to some new number property in your Fan type.

    • Note that a ROW_NUMBER() value is kinda meaningless without a well-defined ORDER BY criteria.
  • Don't use indexOf in a loop: indexOf has a runtime complexity of O(n) so it's inadvisable to use that function inside a loop over the same array as that will give you O(n^2) runtime, which is very bad.

    • This also applies to other O(n) reduction functions like findIndex, find, includes, and lastIndexOf.
    • Also, indexOf (and others) only defined on Array.prototype, so it isn't available on other kinds of iterables, such as NodeList.
  • Within ejs, you can use an overload of Array.prototype.map which gives you the index of each element, and stick the index into each element object, like so:

const fansWithIndex = artist.fans
    .map( function( element, index ) {
        element.index = index;
        return element;
    } );

<% for( const fan of fansWithIndex ) { %>
<tr>
    <th scope="row"><%= fan.index %></th>
    <td><%= fan.email %></td>
    <!-- etc -->
</tr>

<% }%>

...though FP purists (like myself) would argue that the above example is bad code because it mutates source data, and instead Object.assign should be used instead. Also, the long-form function can be made simpler with an arrow-function =>, like this:

const fansWithIndex = artist.fans.map( ( e, idx ) => Object.assign( { index: idx }, e ) );

<% for( const fan of fansWithIndex ) { %>
<tr>
    <th scope="row"><%= fan.index %></th>
    <td><%= fan.email %></td>
    <!-- etc -->
</tr>

<% }%>

This can be simplified further:

const fansWithIndex = artist.fans.map( ( e, index ) => ({ index, ...e }) );

<% for( const fan of fansWithIndex ) { %>
<tr>
    <th scope="row"><%= fan.index %></th>
    <td><%= fan.email %></td>
    <!-- etc -->
</tr>

<% }%>
  • Because the artist.fans.map(...) part is a single expression with a single output you can now inline it directly into your for(of) statement, like so:
<% for( const fan of artist.fans.map( ( e, index ) => ({ index, ...e }) ) ) { %>
<tr>
    <th scope="row"><%= fan.index %></th>
    <td><%= fan.email %></td>
    <!-- etc -->
</tr>

<% }%>
  • Related