larshei

larshei

How would you recommend structuring slightly more complex page layouts?
Ive been roaming the elixir forums for a bit, but I cant really decide on how to continue.
There was an example for a page layout like this, but id did not help me :frowning:

Goal

what i would like to achieve is:

----------------------------------------
|               top_menu               |
----------------------------------------
|      |                               |
| side |                               |
| nav  |            main               |
|      |                               |
|      |                               |

Where the top_menu allows navigation between different categories (e.g. ‘profile’, ‘timeline’, ‘messages’, …). It does not need to change between pages.

So if a user selected e.g. ‘messages’, a side_nav specific to messages should be printed that contains a list of chat partners. The main area then displays the content of whatever chat was selected in side_nav.

What I tried

I was able to build a setup like this doing the following:

  • add the top_menu to the live.html.leex template.
  • each menu entry does a live_redirect.
  • live views load the side_nav as a component. Each item in the nav does a live_patch with different live_actions.
  • live views load a component to main, depending on the live action.

This way, I get

  • the page layout I wanted
  • one live view per item in the top_menu that i can store in a separate folder with all its components
  • All routes are visible in the router and the url is updated properly

The Problem

The top menu should have a sign in/out button, depending on wether a user is signed in or not.
It seems I cannot get that information in the live.html.leex template. To get this information, I need access to the session and therefore a live view.

My first approach was to put the top_menu into a separate component and render it in every live view.
However, this introduces duplication, cause I will have to determine wether a user is signed in or not separately on every live view.

The second thought, that I cant quite wrap my head around, is to use a live view for the top_menu and then another live view for side_nav + main. That way, I only need to check the login state on the first live view.

Question

How would you structure a page that has a top menu with login/out button?

I cannot really see how the setup with a parent and multiple child live views would be achieved. Could you maybe help me here by pointing me to an example?

Do any of my approaches or thoughts make sense or am I way off the track?

Showing Posts 1 to 8

chautelly

chautelly

I’m very new to this and somewhat figuring out similar questions so don’t take my ideas for granite.

  1. Create a single liveview that all routes and sub routes point to:
#router.ex
live "/", MyAppLive, :index
live "/profile", MyAppLive, :profile
live "/timeline", MyAppLive, :timeline
live "/messages", MyAppLive, :{:messages, :index}
live "/messages/:message_id", MyAppLive, {:messages, :detail}
live "/messages/:message_id/edit", MyAppLive, {:messages, :edit}
  1. In MyAppLive implement handle_params if you want to perform any side effects (mutate state, do async work, etc)
defmodule MyAppLive do
  ....
  mount (...)
  ....
  def handle_params(params, url, socket) do
    {:noreply, socket |> assigns(message_id: Map.get(params, "message_id")}
  end
  1. In your template or render function return based on socket.assigns.live_action (or custom state updates).
# my_app_live.html.leex
<nav>
<%=
   case @live_action do
      :profile -> live_render(@socket, SideNavProfile, id: @live_action
      ...
%>
<nav>
<section>
<%=
   case @live_action do
      :profile -> live_render(@socket, ProfileLive, id: @live_action
      {:messages, message_action} -> live_render(
          @socket,
          MessagesLive,
          id: "messages-#{@message_id},
          session: {"message_id" => @message_id, "action" => message_action})
      ...
%>
</section>

In MessagesLive’s mount you’ll have access to session.message_id and session.message_action which you can assign to it’s own assign and in it’s template render based on message_action, etc.

MyAppLive can also keep track of the signed in user and send that to a LiveComponent or LiveView at the top of your template. I would probably use a component rather than a view for the top bar.

Again, this is where I’m at with my understanding so far. I wouldn’t mind if someone with much more liveview experience has any pointers.

larshei

larshei OP

Thanks for the detailed response, the Tuple idea is great, cause it keeps things really clean and i can grab the child live view in MyAppLive and then decide whatever action should be taken on the child in the child itself, very clean.

However, passing a tuple

# router.ex
live "/", AppLive, {:messages, :index}

does not match in phoenix router, as only atoms and lists are allowed as arguments based on the router sources

As soon as I put in a list, like

live "/", AppLive, [{:messages, :index}]

or variations of it (keyword list, list with map, …) the action becomes nil and whatever was passed seems to be merged with opts (optional 4th parameter for live). IO.inspect assigns in the live view seems to show none of the opts, so I have no clue where those opts go and how I may access them.

larshei

larshei OP

One way to get data from the router to the live view is by adding data to the session:

live "/", AppLive, [], session: %{"page" => {:home, :index}}

then, in the live view mount function, assign the "page" parameter to the socket and use it to select the live view that should be rendered.

However, that doesn’t seem to work with live_patch.

I went with putting the top_menu in a component that is rendered on every live page. A helper function to determine the log in state is then called in mount() of each view.

chautelly

chautelly

Yeah I just finished refactoring my app like this. I think this is the best way forward. I was worried the navigation would flicker since it would be rendered in different liveview processes but it’s working great so far.

larshei

larshei OP

For Anyone who ends up reading this in the future, there is more on the same topic to be found here: LiveView with complex layouts - #10 by 50kudos

larshei

larshei OP

Turns out nesting multiple views is not very convenient, as some functions (e.g. handle_params) are not available on child views. Overall, this nested structure requires a lot of data passing between different views/components.

The approach was changed to use templates.

In each live view use MyAppWeb, :live_view is used to load the template. It can be found in lib/my_app_web.ex and will load lib/my_app_web/templates/live.html.

Simply duplicate the existing live_view, rename to e.g. my_live_view.
Duplicate the live template and add the top and side menu as components. Load this template in my_live_view. Then, in your MyAppWeb.MyLive use this template with use MyAppWeb, :my_live_view.

This way, no nested live views are needed, but there is still the option for multiple side menus, by simply loading different templates.

scoop

scoop

Great to be able to share your insights, thank you.

Do you, by any chance, have an example that you care to point to?

While I’m at it: have you developed your thoughts on this further since the last post?

Thank you in advance.

larshei

larshei OP

This was done for a company internal project so unfortunately I cannot share code with you.
I am also not aware of other projects that I could point you to.

As the project was not continued and other projects I have worked on were simpler from a navigation perspective, I have not developed my thoughts on this.

— All posts loaded —

Where Next? Top

Trending in Questions Top

stjefim
Hello! Suppose you are building workflow (order / task / payment) processing system with the following requirements: Each workflow con...
New
Blokh
Hey guys, I’ve got a huge CSV ( around 10 GB ) that needs to be processed hourly Do you guys have any suggestions what is the best prac...
New
roeland
Kia ora, We have been using elixir-google-api to connect to Google Drive. However, with the updates to Tesla due to CVEs this is now bro...
New
kszambelanczyk
Hello! Could someone please give me a help/sample code, how to delete a file from s3 using waffle/waffle_ecto from Phoenix app. I creat...
New
Onor.io
I have what I’ve heard referred to as a “lookup table” in my database. This is a way of assigning codes to common values. One common lo...
New
jaybe78
Hello, I’m developing a online persistent chat system (what’s app) like using elixir/dynamodb/aws for a mobile app(flutter). The diffic...
New
Trolleger
What approach to take when sending live updates to “random” users Hi! I have a question, I have a little chat app, and when I create a DM...
New

Other Trending Topics Top

garrison
Hobbes is a low-level distributed database for the Elixir programming language. Hobbes provides a simple, safe, and scalable storage lay...
New
mcass19
ExRatatui lets you cook up rich terminal UIs in Elixir, powered by Rust’s ratatui via Rustler NIFs. Build interactive terminal applicatio...
New
Damirados
Hello everyone. After busy few months I am happy to announce v0.1.0 of Emerge &amp; Solve. They are GUI (Emerge) and State management (S...
New
netoum
Corex is an accessible, unstyled UI component library for Phoenix that integrates Zag.js state machines using Vanilla JavaScript and Live...
New
wintermeyer
There are three potential reasons for members of this forum to have a look at https://vutuv.de You are tired or annoyed of LinkedIn. Yo...
New
aseigo
ICal is a library for interacting with iCalendar data. It parses iCalendars into typed Elixir structs via ICal.from_ics, and can prepare ...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews