Print map key/values within EEX template

Hi folks,

I have a question regarding printing a map inside an eex template.

How do I print out the key of a map within the template in, for example, a standard html tag such as “<h1>”? I’ve created the logic behind it and have printed it using IO.puts but I can’t seem to get the syntax correct for eex.

Here’s the code that I’m trying to implement:

for  {k, v}  <-  gps do
   for  p  <-  patients  do
      if p.gp_id == k do 
         IO.puts(v<>":"<>p.fname<>" "<>p.lname)
      end
   end
end

Thanks in advance.

for {k, v} <- gps, p <- patients, p.gp_id == k do
    key = content_tag(:h2, v)
    value = html_escape("#{p.fname} #{p.lname}")
    html_escape([key, value])
end

Also keep in mind that this is more a Phoenix.HTML issue than one of eex. Eex is totally agnostic to what format you’ll be rendering data into.

1 Like

In EEx you can do it very similar and I will assume that gps and patients are available in the template as such.

<%= for {k, v} <- gps do %>
  <%= for p <- patients do %>
    <%= if p.gp_id == k do %><%= v %>:<%= p.fname %> <%= p.lname %><% end %>
  <% end %>
<% end %>

You might need to tweak to match the output formatting you actually desire.

2 Likes

Thanks for the suggestions guys. However, neither seems to be working.

An extra piece of info that I should have included at the start, I’m currently running Phoenix 1.2.

What do you mean by not working?

Also EEx is not related to phoenix, it’s in the stdlib.

I’ll elaborate some more. The function of the page is to act as a report page. The system holds patients, and each one has a GP associated with it. The idea is to print out a report that shows “What patients belong to what Gp” in the format:

  • GP 1
    - Patient 1
    - Patient 2
  • GP 2
    - Patient 3
    - Patient 4

The issue I’m having is displaying this data in any way inside the html template (I’m sure the data is being sent correctly)

I hope this helps.

1 Like

Maybe you can transform your data to the right shape before rendering? And then just do

<ul>
<%= Enum.map(gps, fn %GP{name: gp_name, patients: patients} -> %>
  <li><%= gp_name %></li>
  <ul>
  <%= Enum.map(patients, fn %Patient{name: patient_name} -> %>
     <li><%= patient_name %></li>
  <% end) %>
  </ul>
<% end) %>
</ul>

or something like that.


for  {k, v}  <-  gps do
   for  p  <-  patients  do
      if p.gp_id == k do 
        # ...
      end
   end
end

can maybe be done a bit more efficiently, I think

def prepare_data(gps, patients) do
  patients
  |> Stream.group_by(fn patient -> patient.gp_id end) # group all patients by their gp_id
  |> Stream.map(fn {gp_id, patients} -> {gps[gp_id], patients} end) # get gp's data by their id
  |> Enum.reject(fn {gp, _patients} -> is_nil(gp) end) # filter out any gps that are not in `gps`
end

output from prepare_data/2 can be rendered like this then

<ul>
<%= Enum.map(data, fn {gp, patients} -> %>
  <%= render(GPView, "gp.html", gp: gp) %>
  <ul>
  <%= Enum.map(patients, fn patient -> %>
     <%= render(PatientView, "patient.html", patient: patient) %>
  <% end) %>
  </ul>
<% end) %>
</ul>
2 Likes

It does not. Please show a minified example and the output you get compared to the output you want.

For me my version from above just works:

iex(1)> Application.load(:eex)
iex(2)> EEx.eval_string """                       
...(2)> <%= for {k, v} <- foo do %>               
...(2)>   <%= k %> => <%= v %>                    
...(2)> <% end %>                                 
...(2)> """, [foo: %{a: :b, c: :d, e: 1, f: "two"}]
"\n  a => b\n\n  c => d\n\n  e => 1\n\n  f => two\n\n"

I left of the comparison you had, as your threads title just speaks about printing keys and values which is done properlyin my example.

2 Likes

Here’s a (very) rough idea of what it should display:

Here’s the controller handling the data:

def index(conn, _params) do
      gps = Repo.all(GP) 
      patients = Repo.all(Patient)
      render conn, "patientbygp.html", patients: patients, gps: gps
    end

and patientbygp.html

<div class="jumbotron">
  <%= for {k, v} <- @gps do %>
    <%= for p <- @patients do %>
    <%= if p.gp_id == k do %> <h1><%= v %>:<%= p.fname %> <%= p.lname %><% end %></h1>
    <% end %>
  <% end %>
</div>

Just to clarify, I understand that the html code will not make the page look like the screenshot, at the moment I’m more concerned with getting things displaying rather than blank space.

and the extra logic in the template could probably be replaced by a bit more elaborate sql query or maybe

defmodule GP do
  def list(preloads) do
    GP
    |> Repo.all()
    |> Repo.preload(preloads)
  end
end

# and then in the controller
gps_with_patients = GP.list([:patients])

The rendering part then becomes rather straightforward (as I’ve shown in my previous post).


If your for comprehensions return empty lists or lists of nil, it means that the patterns in them don’t match.

For example, <%= for {k, v} <- @gps do %> this won’t work if @gps is not a map or a list of {k, v} tuples. And Repo.all(GP) probably returns a list of %GP{}, which is neither of those, so for silently fails and returns an empty list.

1 Like

Are you sure that Repo.all(GP) returns something that is similar to a list of Key-Value Pairs?

Sorry to say, but I doubt that, since usually Repo-functions do return structs, So in your case I do expect it to be a list of %GP{}-structs. Thats probably why it does not work.

Of course, what @idi527 said about the optimized query is absolutely true.

1 Like

Another way to do this is with Enum.group_by/3. Assuming your Patient has a gp_id field and (in this sample) they each have a name field for illustrative purposes.

<%= for {gp_id, gp} <- Enum.group_by(gps, &(&1.id)) do %>
  <h1><%= gp.name %></h1>
  <ul>
    <%= for patient <- Enum.filter(patients, &(&1.gp_id == gp.id) do %>
      <li><%= patient.name %></li>
    <% end %>
  </ul>
<% end %>
1 Like

EUREKA! It was the repo return that was causing issues, once I added:

|> Enum.map(&{&1.name, &1.cdoctor_id})

Thank you for your help and patience!