Coming from other programming languages, many like me will be in for a surprise. I have a simple problem. I have a list - say, users. I want to iterate through the users and display some information. Pretty simple until I got stumped with this:
Using an eex template, I was trying to do this:
<%= for i <- 0..length(@users) do %>
<% user = Enum.at(@users) %>
<!-- every third user, I need to display on a new "row" -->
<%= if rem(i,3) == 0 do %>
<div class="row">
<% end %>
<!-- display user information - like user name -->
<%= user.name %>
<!-- then close out the "row" tag if we are due for starting another on the next iteration -->
<%= if rem(i+1,3) == 0 do %>
<div class="row">
<% end %>
<% end %>
Firstly, the user value is turning out to be nil. Not sure why. Secondly, I don't have an explanation for this.
What is the difference between "user = Enum.at(@users, i)" and <%= for user <- @users %>? In the latter case, the user is not nil.
Finally, what is the best way to keep track of the times through the list and take a slightly different action (like starting the user on a new row as in the example above)?
To keep things simple, I have also ignored the fact that we may not have a final closing "/div" tag for the row (which depends on the number of users in the list). I've ignored putting any logic around that just to focus on the issue at hand.
What is the difference between
user = Enum.at(@users, i)and<%= for user <- @users %>? In the latter case, the user is not nil.
You should be getting user = nil for the last iteration. That's because you're looping from 0 to length(@users), and the last iteration value will be length(@users) which is 1 more than the last valid index in the list as indexing is 0 based. You should loop from 0 to length(@users) - 1. Note that this will cause problems if @users is empty as Enum.to_list(0..-1) #=> [0, -1].
Finally, what is the best way to keep track of the times through the list and take a slightly different action (like starting the user on a new row as in the example above)?
I would use Enum.with_index and if. Also, length in Elixir is a slow (O(n)) operation, so it's best to cache its value outside the loop instead of recalculating it on every iteration. I also believe you meant to print "</div>" in the second if. Here's the final code:
<% users_length = length(@users) %>
<%= for {user, i} <- Enum.with_index(@users) %>
<%= if rem(i, 3) == 0, do: ~s|<div class="row"| %>
<%= user.name %>
<%= if rem(i + 1, 3) == 0 || i + 1 == users_length, do: "</div>" %>
<% end %>
I think one problem is that you needed to loop from 0 to length - 1, instead of 0 to length. Because you looped till length the last recursion, Enum.at(@users, length) was giving nil
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With