Comprehensions - how to get Phoenix to render a list of buttons?

I am trying to get my head around comprehensions and enum.each

I am trying to get phoenix to render a list of buttons.

My data structure is :

 btn =    [ 
              {"first", "red", "myfunc1()"}
              {"second", "blue", "myfunc2()"}  
            ]

my first go at it was using comprehensions

  for n <- btn,do: content_tag(:button, "#{elem(n, 0)}", class: "#{elem(n, 1)}", onclick: "#{elem(n, 2)}")

Which ended up as a eternal loop of doom
I assume this has something to do with me not directly doing something to n which in my mind should be the tuple.
so I tried Enum.each because it looks like the right tool for the job.

return = []
Enum.each a, fn x -> return++[elem(x,0)] end

I get :Ok from this but not the list I expected [“first”,“second”]

so I am thinking I am missing something really obvious

Compare the return value of these two functions:

https://hexdocs.pm/elixir/Enum.html#each/2

https://hexdocs.pm/elixir/Enum.html#map/2

I don’t think the for itself is causing your loop of doom, it looks fine. A more idiomatic way to write it would be to match the tuple inside the for:

for {name, class, onclick} <- btn do
  content_tag(:button, name, class: class, onclick: onclick)
end
5 Likes

What exactly do you mean by “for loop of doom”? What does it return and what are you expecting instead?

This looks like you were used to mutation from other languages, but there is no mutation in elixir, only rebinding.

What you actually want is probably Enum.map(btn, &elem(&1, 0))

Enum.map/2 is used to iterate over a list and return a list where a given function has been applied to each element of the original list, while Enum.each/2 is meant to iterate over a list and using the current element of the list to create a sideeffect and one does not care about the return value at all.

3 Likes

Secondarily you may want to consider using event listeners instead of DOM on-event handlers.

Unsafe Names for HTML Form Controls

1 Like

I`m not entirely sure either I have made a note to test and prod a bit later

I put a IO.puts inside the loop to see what happened , It kept on spewing out the text in an eternal loop

I changed the Enum.each to Enum.map removed the array initialization , and it worked. I thought Enum.map was something to do with maps so I instictively skipped it I guess I should read more :slight_smile:

yes as a matter of fact I am comparing C# / ASP.net Core to Elixir/Phoenix so I learned basic c# and now I am learning Elixir/Phoenix

I`m not going back to c# anytime soon, Elixir is more fun. documentation is less confusing and if I get this kind of help within minutes of asking questions, its a nobrainer.

Thanks for the wonderful help

1 Like

btn which is your input had only 2 elements, there should be no infinite loop created by that.

Can you replicate a full code example that shows the problem?

1 Like

Thanks !

I still have a lot to learn here as well I see. I`m going to do forms next so I will be sure to read up before I do more stuff !

Are you sure you are not getting SyntaxError,There should be a comma after the first tuple.

yeah I will have a look into it I kinda rushed into removing what did not work and get the working stuff up. it could be because I had dgettext going in my original code.
but I`ll branch and rollback and see what the muffin was going on.

its not a direct copy of what I have just enough to show what I was thinking. I`m one of those tinfoil hat people…
so in my code I have the comma

# file: demo.exs
# elixir -pr ./deps/phoenix_html/lib/phoenix_html.ex -pr ./deps/phoenix_html/lib/phoenix_html/tag.ex -pr ./deps/phoenix_html/lib/phoenix_html/safe.ex -pr ./deps/plug/lib/plug/html.ex demo.exs
#
import Phoenix.HTML, only: [safe_to_string: 1]
import Phoenix.HTML.Tag, only: [content_tag: 3]

defmodule Demo do
  defp make_button({content, class, on_click}),
    do: content_tag(:button, content, class: class, onclick: on_click);

  def first(list),
    do: for data <- list, do: make_button(data)

  def second(list),
    do: Enum.map(list, &make_button/1)

  defp cons_button(data, tail),
    do: [make_button(data)|tail]

  def third(list) do
    list
    |> List.foldl([], &cons_button/2)
    |> :lists.reverse
  end

  def fourth(list) do
    list
    |> Enum.reduce([], &cons_button/2)
    |> :lists.reverse
  end

  def show(name, fun) do
    IO.puts(name)
    fun.()
    |> Enum.map(&safe_to_string/1)
    |> Enum.each(&IO.puts/1)
  end
end

buttons = [
  {"first", "red", "myfunc1()"},
  {"second", "blue", "myfunc2()"}
]

Demo.show("first:", fn() -> Demo.first(buttons) end)
Demo.show("second:", fn() -> Demo.second(buttons) end)
Demo.show("third:", fn() -> Demo.third(buttons) end)
Demo.show("fourth:", fn() -> Demo.fourth(buttons) end)
$ elixir -pr ./deps/phoenix_html/lib/phoenix_html.ex -pr ./deps/phoenix_html/lib/phoenix_html/tag.ex -pr ./deps/phoenix_html/lib/phoenix_html/safe.ex -pr ./deps/plug/lib/plug/html.ex demo.exs
first:
<button class="red" onclick="myfunc1()">first</button>
<button class="blue" onclick="myfunc2()">second</button>
second:
<button class="red" onclick="myfunc1()">first</button>
<button class="blue" onclick="myfunc2()">second</button>
third:
<button class="red" onclick="myfunc1()">first</button>
<button class="blue" onclick="myfunc2()">second</button>
fourth:
<button class="red" onclick="myfunc1()">first</button>
<button class="blue" onclick="myfunc2()">second</button>
$
4 Likes