How to properly use case statements in EEx (LiveView)

I have code in page_live.html.leex that looks like this:

<div>
  <%= case @nav do %>

    <% :post -> %>
      <div>Post view goes here</div>

    <% :main -> %>
      <div>Main view goes here</div>

    <% :board -> %>
      <div>Board view goes here</div>

  <% end %>
</div>

But I get a compilation error like this:
unexpected operator ->. If you want to define multiple clauses, the first expression must use ->. Syntax error before: ‘->’

What am I doing wrong? I searched the documentation and the forum but could not find any solution that would compile.

1 Like

Have you tried something like:

<div>
  <div>
  <%= case @nav do
    :post -> "Post view goes here"
    :main -> "Main view goes here"
    :board -> "Board view goes here"
   end %>
  </div>
</div>

?

1 Like

You should use a helper for this… and try to remove any logic from template.

<%= display_nav(@nav) %>

and in page_view.ex

def display_nav(:post) do
  content_tag(:div) do
    "whatever"
  end
end
# etc.
3 Likes

Yes, doing it this way results in an error “missing terminator: end for do”. It does not recognize the “end” when formatted this way.

The whole reason I’m using the template page_live.html.leex is so I don’t have 1000 lines of HTML inside my page_live.ex (with no coloring/syntax highlighting because the IDE considers it all “String”). Are you saying it’s better to cut the template down to a mere four lines of function calls and put everything else, front- and back-end, inside page_live.ex? What is the advantage of this? I’m still relatively new to Phoenix, so I’m sure there are things I’m overlooking.

1 Like

It’s just good practice… and it’s not like You have to add file, the page_view.ex already exists and is done for this purpose.

I don’t have any file page_view.ex in my project. I used the “mix phx.new my_app --live” command for initial config.

If it is for live, there was a live_helpers.ex inside live… but for 1.5.7.

Anyway live view can also have its own set of helpers.

OK well if I see anything like that, I will consider it. But I still need to figure out proper EEx case statement syntax. It seems like it would be documented somewhere.

Weird, we do exactly this a lot. And @kokolegorille the issue with content_tag is that it isn’t really usable with live view. You can’t for example do live_component inside of it and have it work correctly.

        <%= case @event do  %>
        <% %{type: :research_status_changed, data: status_change} -> %>
           stuff

        <% %{type: :labeling, data: %{comment: comment, user: user}} = event -> %>
             stuff
        <% end %>

This is pulled straight out of our codebase.

Can you provide a reproduceable example?

6 Likes

Hey, I run into similar problems when starting with liveview. For me it was mostly my due to non clean, highly convoluted code… from my messing programming. Disclaimer: I am not proficient in any ways. And your code doesn’t look convoluted. But we just see a part of it :wink:

Spinning up a fresh non-ecto liveview it all worked with your code.

But when doing something like this I can reproduce your error:

  <%= case @nav   do%>
   <% nil  %>
   <% _ -> %>

unexpected operator ->. If you want to define multiple clauses, the first expression must use ->. Syntax error before: ‘->’

Yes, doing it this way results in an error “missing terminator: end for do”. It does not recognize the “end” when formatted this way.

I guess further analysis could maybe explain how this can be achieved too.

My approach is maybe to trivial to have a chance. But have you checked on which line the compiler hits the syntax error? And is there any “surrounding” code. Another case statement somewhere around?

1 Like

Just a quick note - have you tried the following:

<div>
<%= case @nav do %>
<% :post -> %>
  <%="<div>Post view goes here</div>" %>

<% :main -> %>
  <%="<div>Main view goes here</div>" %>

<% :board -> %>
  <%="<div>Board view goes here</div>" %>

<% end %>
</div>

I have done some eex for JSON, html etc… and a quick look made me think this might nudge you in the right direction…

p.s.
It has been a while since I was doing it, and it was on elixir 1.9 just in case if that matters
R.
Fridrik.

This will not work the way you want it to, it will escape the inner HTML. <%= "<div></div>" %> shouldn’t be necessary, it should be possible to just <div></div>. I think @tschnibo is on to something, my guess is that @APB9785’s code is doing do%>

Sure! Here’s a link to the page_live.html.leex I’m running when I get the error:

The full error for this file is

== Compilation error in file lib/phx_bb_web/live/page_live.ex ==
** (SyntaxError) lib/phx_bb_web/live/page_live.html.leex:11: unexpected operator ->. If you want to define multiple clauses, the first expression must use ->. Syntax error before: '->'
    (eex 1.11.3) lib/eex/compiler.ex:126: EEx.Compiler.generate_buffer/4
    (eex 1.11.3) lib/eex/compiler.ex:65: EEx.Compiler.generate_buffer/4
    (phoenix_live_view 0.15.4) expanding macro: Phoenix.LiveView.Renderer.__before_compile__/1
    lib/phx_bb_web/live/page_live.ex:1: PhxBbWeb.PageLive (module)
    (elixir 1.11.3) lib/kernel/parallel_compiler.ex:314: anonymous fn/4 in Kernel.ParallelCompiler.spawn_workers/7

Just a guess but… did you try without this html comment?

6 Likes

You know, I thought I had tried that already, but I guess not, because you’re totally right. The case statement must expect the first pattern to come immediately, and the comment beforehand was breaking syntax.

In the previous version, I was using nested if-else statements for this navigation switch, and the comments weren’t bothering anything. But now I see that in a case statement, the comments must come AFTER the pattern clause to match.

Thanks so much for taking the time to help!

2 Likes

Totally unrelated to your question, but…

…Try “Surface”! It changed my life! :smile:

You can take each of those big groups of HTML, slap them in a “component” and then you insert something like a “fake html” tag into the code. The tag gets inflated into the contents of the component.

Now at this point you are correct in pointing out this is no different to using a partial. However, the syntax is similar to adding new HTML tags, so this also implies that the content of the tag gets passed in kind of like a param into the partial function.

I use it by creating a component say “BoilerPlate”, which is the outline of the form, it only contains components like “SideBar”, “TopNav” and “Content”. These inflate to their own components and so on. Content will be the wrapped content, which in your case will be one of several components, decided by the results of the case statement.

I’m then creating a folder with the same name as the component and further break that main content down into a small enough number of pieces that it’s understandable. I really like it!

2 Likes