Accordion EEX Template using a list comprehension

Trying to use a nested list comprehension to display in an accordion form like in Nested Accordion
I can’t use plain css because the list comprehension just replicates the same href for each dropdown element causing every button to interact with the first accordion element only.

Using this js example I’ve only been able to get it to render the initial button, which does nothing.
Here is what I have:

<%= for {state, cities} <- @stores do %>
<ul class="accordion">
  <li>
    <a class="toggle" href="javascript:void(0);"><%= state %></a>
    <%= for {city, [names]} <- cities do %>
    <ul class="inner">
      <li>
        <a href="#" class="toggle"><%= city %></a>
        <div class="inner">
          <%= for name <- names do %>
          <p>
            <%= link name, to: Routes.page_path(@conn, :show, name) %>
          </p>
          <% end %>
        </div>
      </li>
    </ul>
    <% end %>
  </li>
</ul>
<% end %>

Anyone have any experience with this or have any examples or suggestions on other solutions?
maybe the elixir equivalent for @{int i=0;} from source

If you‘re lookin for indexes those are generally available by Enum.with_index/1 and .../2. No need for anything special for comprehentions.

2 Likes

In your controller:

def some_action(conn, params) do
   ... 
  render(conn, "page.html", i: 1)
end

the list comprehension just replicates the same href for each dropdown element

I’m not seeing that. If I do this:

lib/rumweb/controllers/page_controller.ex:

defmodule RumWeb.PageController do
  use RumWeb, :controller

  def index(conn, _params) do
    stores = %{
      CA: %{
            la: [["name1", "name2", "name3"]],
            sf: [["name4", "name5", "name6"]]
          },
      TX: %{ 
            austin: [["name6", "name7", "name8"]]
          }
    }

    render(conn, "index.html", stores: stores)
  end
end

templates/page/index.html.eex:

<section class="phx-hero">
  <h1><%= gettext "Welcome to %{name}!", name: "rum" %></h1>
  <p>A really cool, real time service.</p>
</section>

<section class="phx-hero">

<%= for {state, cities} <- @stores do %>
<ul class="accordion">
  <li>
    <a class="toggle" href="javascript:void(0);"><%= state %></a>
    <%= for {city, [names]} <- cities do %>
    <ul class="inner">
      <li>
        <a href="#" class="toggle"><%= city %></a>
        <div class="inner">
          <%= for name <- names do %>
          <p>
            <%= link name, to: Routes.user_path(@conn, :show, name) %> 
          </p>
          <% end %>
        </div>
      </li>
    </ul>
    <% end %>
  </li>
</ul>
<% end %>
</section>

This is what I see in my browser:

And here is the html source:

<section class="phx-hero">

<ul class="accordion">
  <li>
    <a class="toggle" href="javascript:void(0);">CA</a>
    <ul class="inner">
      <li>
        <a href="#" class="toggle">la</a>
        <div class="inner">
          <p>
<a href="/users/name1">name1</a>          </p>
          <p>
<a href="/users/name2">name2</a>          </p>
          <p>
<a href="/users/name3">name3</a>          </p>
        </div>
      </li>
    </ul>
    <ul class="inner">
      <li>
        <a href="#" class="toggle">sf</a>
        <div class="inner">
          <p>
<a href="/users/name4">name4</a>          </p>
          <p>
<a href="/users/name5">name5</a>          </p>
          <p>
<a href="/users/name6">name6</a>          </p>
        </div>
      </li>
    </ul>
  </li>
</ul>
<ul class="accordion">
  <li>
    <a class="toggle" href="javascript:void(0);">TX</a>
    <ul class="inner">
      <li>
        <a href="#" class="toggle">austin</a>
        <div class="inner">
          <p>
<a href="/users/name6">name6</a>          </p>
          <p>
<a href="/users/name7">name7</a>          </p>
          <p>
<a href="/users/name8">name8</a>          </p>
        </div>
      </li>
    </ul>
  </li>
</ul>
</section>


    </div>

    <script type="text/javascript" src="/js/app.js"></script>
  <iframe src="/phoenix/live_reload/frame" style="display: none;"></iframe>
</body>
</html>

You can see that the link() helper created href’s that are different for each name.

1 Like

Do the buttons work for you? If I don’t style it mine shows up like yours does but the accordion style still doesn’t collapse.

if you look at the examples on Bootstrap Collapse they set the link to have an id or href to tell it which button should link to the accordion. If you use a for each comprehension, it sets each href to the same value making every button in the list refer to the first entry.

this is demonstrated in this javascript example

which was solved by adding @{int i=0;} and id="accordion_@i", id="panel_@i", data-target="#collapseOne_@i" href="#collapseOne_@i" etc

If you need an html attribute, like a class name, to end in an integer that increments, then as LostKobrakai suggested you can use Enum.with_index() in your for-loops, for example:

  <%= for {{state, cities}, i} <- Enum.with_index(@stores) do %>
    <ul class="accordion<%= i+1 %>">

Then the <ul> tags in the html source will have class names like so:

accordion1
accordion2

What you should do is hand code the html, so that the accordions work for you, then we can show you how to use a for-loop in your template to get the same html.

2 Likes

I got something to work, but I’ll warn you that a nested accordion is so tedious to construct and so hard to debug that I will never look at Bootstrap accordion again! And, the nested accordion effect doesn’t even work correctly all the time: sometimes when I expand the city LA, then the city SF doesn’t contract. I’m using the stores data that I posted above.

<!-- accordion menu: -->
<div id="accordion">
  <%= for {{state, cities}, i} <- Enum.with_index(@stores) do %>
  <div class="card">
    <div class="card-header" id="heading<%= i %>">
      <h5 class="mb-0">
        <button class="btn btn-link collapsed" 
                data-toggle="collapse" 
                data-target="#collapse<%= i %>" 
                aria-expanded="true" 
                aria-controls="collapse<%= i %>">
          State: <%= state %>
        </button>
      </h5>
    </div>

    <div id="collapse<%= i %>" 
         class="collapse collapsed" 
         aria-labelledby="heading<%= i %>" 
         data-parent="#accordion">

      <div class="card-body">
      <div id="accordion<%= i %>">
      <%= for {{city, [names]}, j} <- Enum.with_index(cities) do %>
          <div class="card">
            <div class="card-header" id="heading<%= i %><%= j %>">
              <h5 class="mb-0">
                <button class="btn btn-link collapsed" 
                        data-toggle="collapse" 
                        data-target="#collapse<%= i %><%= j %>" 
                        aria-expanded="true" 
                        aria-controls="collapse<%= i %><%= j %>">
                   City: <%= city  %>
                </button>
              </h5>
            </div>

            <div id="collapse<%= i %><%= j %>" 
                  class="collapse collapsed" 
                  aria-labelledby="heading<%= i %><%= j %>" 
                  data-parent="#accordion<%= i %>">

              <div class="card-body">
                <ul>
                  <%= for {name, x} <- Enum.with_index(names) do %>
                    <li><%= link name, to: Routes.user_path(@conn, :show, x+1) %></li>
                <% end %>
                </ul>
              </div>

            </div>

          </div>
        </div>
      <% end %>
      </div>
      </div>
    </div>
  <% end %>
</div>
1 Like

Yeah I’ve been working on this solution on and off for a week now, any other recommendations for showing the data? Your solution looks nice, wow thats a lot. Thank you for your efforts!

Definitely some odd occurances with it, like when you have more than 2 cities.

I’ve got something working using the methods I saw implemented in your example. Everything works really nicely! When I load the page though, it shows everything opened. Also if possible I’d like to hide every other state if one state is opened. Any suggestions for this?

Example: Bootstrap Nested Accordion

<div id="accordion">
<%= for {{state, cities}, i} <- Enum.with_index(@stores) do %>
  <div class="card">
    <div class="card-header" id="heading-<%= i %>">
      <h5 class="mb-0">
        <a role="button" data-toggle="collapse" href="#collapse-<%= i %>" aria-expanded="true" aria-controls="collapse-<%= i %>">
          <%= state %>
        </a>
      </h5>
    </div>
    <div id="collapse-<%= i %>" 
         class="collapse show" 
         data-parent="#accordion" 
         aria-labelledby="heading-<%= i %>">

      <div class="card-body">
        <div id="accordion-<%= i %>">
          <div class="card">
            <%= for {{city, [names]}, j} <- Enum.with_index(cities) do %>
            <div class="card-header" id="heading-<%= i %>-<%= j %>">
              <h5 class="mb-0">
                <a class="collapsed" 
                   role="button" 
                   data-toggle="collapse" 
                   href="#collapse-<%= i %>-<%= j %>" 
                   aria-expanded="false" 
                   aria-controls="collapse-<%= i %>-<%= j %>">

                  <%= city %>

                </a>
              </h5>
            </div>
            <div id="collapse-<%= i %>-<%= j %>" 
                 class="collapse" 
                 data-parent="#accordion-<%= i %>" 
                 aria-labelledby="heading-<%= i %>-<%= j %>">

              <div class="card-body">
                  <div id="accordion-<%= i %>-<%= j %>">
                     <div class="card">
                       <%= for {name, k} <- Enum.with_index(names) do %>
                        <div class="card-header" id="heading-<%= i %>-<%= j %>-<%= k %>">
                        <h5 class="mb-0">
                          <a class="collapsed" 
                             role="button" 
                             data-toggle="collapse" 
                             href="#collapse-<%= i %>-<%= j %>-<%= k %>" 
                             aria-expanded="false" 
                             aria-controls="collapse-<%= i %>-<%= j %>-<%= k %>">

                            <%= link name, to: Routes.page_path(@conn, :show, name) %>

                          </a>
                        </h5>
                      </div>
                      <% end %>
                    </div>
                  </div>
              </div>
            </div>
          <% end %>
          </div>
        </div>      
      </div>
    </div>
  </div>
  <% end %>
</div>