Reputation: 369
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 class="col-12 d-flex flex-row-reverse">
<a href="" class="a1" onclick="exportTableToCSV(null, 'text.csv')">Export to csv</a>
</div>
<div class="table-responsive">
<table class="table table-hover">
<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 class="bi bi-trash"></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?
Upvotes: 1
Views: 1098
Reputation: 155726
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>
<% }%>
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.
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.
O(n)
reduction functions like findIndex
, find
, includes
, and lastIndexOf
.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:
Object.assign
the object spread operator ...obj
can be used:
{ index: idx, ...e }
is almost semantically identical to Object.assign( { index: idx }, e )
.
Object.assign
will invoke custom setter
functions, whereas the ...
syntax does not.map
you will need to wrap the object-literal's braces {}
in parentheses ()
to prevent the braces being parsed as function body delimiters, hence the => ({ foo: bar })
instead of => { foo: bar }
.index: idx
can be simplified by renaming the idx
parameter to index
and putting just { index, ...e }
.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>
<% }%>
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>
<% }%>
Upvotes: 1
Reputation: 502
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>
Upvotes: 1