Conditionally interspersing a date seperator in a list

Conditionally interspersing a seperator

Hi :slight_smile:
This is my first post here.
I have a list of items that each have a date field.
When redndering the list, its sorted by this date field,
and now I would like to add a small header everytime the date changes.

Example:

14th March 2024
Item 1
Item 2
Item 3
13th March 2024
Item 4
Item 5
11th March 2024
Item 6

Current loop:

<div id="events" phx-update="stream" class="space-y-4">  
  <%= for {id, event} <- @streams.events do %>
     ## Render event
   <% end %>
</div>

My first approach coming from other languages would have been to assign a small variable like
last_date_seen, and in the for loop check if last_date_seen is the same or not as the current date.
But from what I learned this is not advisable, because it will break change tracking.
Using intersperse seems also not feasible, since it would put a header between each item, even if they have the same date.

Hello and welcome to the forum.

Enum.group_by is your friend here. Given this list of events you should then do this:

events = [
  %{name: "Item 1", date: ~D[2000-01-01]},
  %{name: "Item 2", date: ~D[2000-01-01]},
  %{name: "Item 3", date: ~D[2000-01-02]},
  %{name: "Item 4", date: ~D[2000-01-03]}
]

Enum.group_by(events, &(&1.date), &(&1.name))

which yields:

%{
  ~D[2000-01-01] => ["Item 1", "Item 2"],
  ~D[2000-01-02] => ["Item 3"],
  ~D[2000-01-03] => ["Item 4"]
}

The Enum.group_by call basically says “group items by the date field and only give me their name field after”.

You can flatten the resulting map to a list exactly the way you need it in the UI. I say give it a try and if you still can’t, I (or somebody else) can give you the next hint.

2 Likes

You could use Enum.chunk_by:

@streams.events
|> Map.values()
|> Enum.sort_by(& &1.date)
|> Enum.chunk_by(& &1.date)
|> Enum.map(fn chunk ->
  # here, chunk is a list of events that all have the same `date` value
  # you can get that date from hd(chunk).date
end)
3 Likes

Can you hide all that Enum mapping/chunking/grouping in a function, passing in the stream, without breaking change tracking? Or does it have to happen in the template to keep change tracking happy?

@al2o3cr @dimitarvp Beautiful, thanks!