riebeekn
riebeekn

Reputation: 175

How to handle "status" variable in Phoenix templates

I am attempting to do something like the below in my eex template:

<% current_production_date = nil %>
<%= for widget <- @widgets do %>
  <%= if (current_production_date != widget.production_date) do %>
    <!-- ... output a new date header and re-assign current production_date -->
    <% current_production_date = widget.production_date %>
  <% end %>
  <%= render "_widget.html", widget: widget %>
<% end %>

This won't work as the outer "current_production_date" variable can't be re-assigned inside the comprehension. This seems like a common scenario thou, so I imagine there is a straight-forward way of accomplishing this... I just can't figure it out... any hints much appreciated!

Upvotes: 0

Views: 200

Answers (3)

riebeekn
riebeekn

Reputation: 175

Thanks for the suggestions, I ended up going with a suggestion I got from the Elixir forum... using group_by:

<%= for {date, widgets}
  <- Enum.group_by(@widgets, fn(x) -> DateTime.to_date(x.production_date) end)
  |> Enum.sort(fn({date1, _widget1}, {date2, _widget2}) ->
    case Date.compare(date2, date1) do
      :lt -> true
      _ -> false
    end
  end) do %>
  <%= render "_date_header.html", date: date %>
  <%= for widget <- widgets do  %>
    <%= render "_widget.html", widget: widget %>
  <% end %>
<% end %>

I ended up extracting this into the view as it's a little nasty when it's directly in the template.

Upvotes: 0

Aleksei Matiushkin
Aleksei Matiushkin

Reputation: 121010

While the @Badu’s answer is technically correct, it’s [opinionated] not fully idiomatic Elixir since it has code duplication and uses [opinionated] the wrong abstraction to present chunks of data.

What you do have is literally a list of chunks, so what you probably need is Enum.chunk_while/4.

chunk_fun = fn 
  widget, [] ->
    {:cont, [widget]}
  #                  ⇓⇓                                ⇓⇓  PATTERN MATCH!
  %{production_date: pd} = widget, [%{production_date: pd} | _] = prev ->
    {:cont, [widget | prev]}
  widget, acc ->
    {:cont, Enum.reverse(acc), []}
  end
after_fun = fn
  [] -> {:cont, []}
  acc -> {:cont, Enum.reverse(acc), []}
end
widgets = Enum.chunk_while(@widgets, [], chunk_fun, after_fun)

Now in widgets you have chunks of @widgets, grouped by date. Let’s output them:

for [%{production_date: date} | _] = chunk <- widgets do
  # output the header with the date
  for widget <- chunk do
    # render the widget
  end
end

I did not test this code but it should work as it is.

Upvotes: 1

Badu
Badu

Reputation: 1082

You can use Enum.reduce/3 to accumulate the result and output the result after.

<% 
current_production_date = nil
{result, _}  = 
Enum.reduce(@widgets, {[], current_production_date}, 
fn %{production_date: production_date} = widget, {acc, current_date} ->
    if product_date != current_date do
      output = "<h1>output a new date header and re-assign current production_date</h1>"
      {[output, Phoenix.View.render_to_string(PageView, "widget.html", widget: widget) 
        |acc], production_date}
    else
        {[Phoenix.View.render_to_string(PageView, "widget.html", widget: widget) |acc], current_date} 
    end
end) %>

<%= for w <- Enum.reverse(result) do %>
    <%= raw(w) %>
<% end %>

Upvotes: 0

Related Questions