Highlighting the current page link in the Navbar menu in Phoenix LiveView

Search for a command to run...

No comments yet. Be the first to comment.
Motivation Recently, I tried to learn some low-level system programming stuff. I am a Mac user, and I thought that everything that works on Linux should also work on Mac. After all, Mac is a Unix-based system 😊. I guess we all heard this. Oh boy! I ...

Problem Statement While working on a Nestjs project, I encountered a weird problem related to the database column. I was trying to insert a record into a MySQL table using TypeORM. The error I was experiencing stated that a specific column “cannot be...

Recently, I was exploring design patterns courses on my LinkedIn Learning subscription. I came across a course, Node.js: Design Patterns by Alex Banks. It is a wonderful, easy-to-understand course. I started with the Builder Pattern, and the explanat...

Suppose you are working on a table in a LiveView project. This table has limited static data of not more than one page (you can avoid questions about pagination in the comment section 😊). From a user's point of view, it becomes hard to look into the...

Recently, while working on one of my personal Elixir projects. I came across a scenario where I needed to make some changes to the database schema.The changes mainly revolve around adding and removing indexes due to changes in the business requiremen...

A good user experience happens when an application is easy to use and with intuitive UI feedback. Nav menu bar is one the most important element in web applications, it helps users to navigate to different pages. It's vital that users should be aware of which current tab on which user is. This can be achieved by highlighting the current tab user is. See the cover image of this blog to make sense.
NOTE: Jump to Highlighting the current menu (Dirty Solution) section if you already have a project with Navbar.
First of all, we should have a LiveView project or will create a new LiveView project by running mix phx.new navigation_demo --no-ecto --live
We will then navigate to the project directory by running cd navigation_demo and run mix phx.server. Once, the project starts running navigate to localhost:4000 to see the Welcome phoenix screen.
To demonstrate the navbar highlighting logic in Elixir, we will first add three items to the navbar menu with minimal functionality.
For this open router.ex and following routes
live "/", HomePageLive, :index
live "/page1", Page1Live, :index
live "/page2", Page2Live, :index
Now go to live folder and add three files home_page_live.ex, page1_live.ex, and page2_live.ex.
Add mount and render callbacks in each of the following with minimal text. Refer following code for reference
// home_page_live.ex
defmodule NavigationDemoWeb.HomePageLive do
use NavigationDemoWeb, :live_view
def mount(_params, _session, socket) do
{:ok, socket}
end
def render(assigns) do
~H"""
Home page
"""
end
end
// page1_live.ex
defmodule NavigationDemoWeb.Page1Live do
use NavigationDemoWeb, :live_view
def mount(_params, _session, socket) do
{:ok, socket}
end
def render(assigns) do
~H"""
Page 1
"""
end
end
// page2_live.ex
defmodule NavigationDemoWeb.Page2Live do
use NavigationDemoWeb, :live_view
def mount(_params, _session, socket) do
{:ok, socket}
end
def render(assigns) do
~H"""
Page 2
"""
end
end
Before adding the Navbar menu we will do some cleanup in our existing code.
Open templates/layout/root.html.heex and remove all code in the body tag and just add the following code
<body>
<%= @inner_content %>
</body>
live.html.heex file and add the following code <nav>
<h3>NavBar Test</h3>
<ul>
<li><%= live_patch "Home", to: Routes.home_page_path(socket, :index)%></li>
<li><%= live_patch "Page 1", to: Routes.page1_path(socket, :index)%></li>
<li><%= live_patch "Page 2", to: Routes.page2_page_path(socket, :index)%></li>
</ul>
</nav>
* Open `app.css` and add the following styling
```css
nav { display: flex; justify-content:space-between; align-items: center; padding: 1rem 2rem; background: #cfd8dc; }
nav ul { display: flex; list-style: none; }
nav li { padding-left: 1rem; }
nav a { text-decoration: none; color: lightblue }
.active { background: red; }
The resulting home page will be as follows

But, now here the problem starts if you will notice the `home` page in the navbar does not have any kind of indication that it is the currently selected menu.
def handle_params(_params, url, socket)
{:noreply, socket |> assign(current_path, URI.parse(url).path)}
end
In the live.html.heex, add the following logic
```
<%= live_patch "Home", to: Routes.home_page_path(socket, :index)%>
<%= live_patch "Page 1", to: Routes.page1_path(socket, :index)%>
<%= live_patch "Page 2", to: Routes.page2_page_path(socket, :index)%>
This will work, but it will become very cumbersome when there are more tabs. Also, this approach is not DRY.
The above solution is not very DRY. We can improve this code using the concept of on_mount and attach_hook.
We want that every time when the user changes the menu in our app it should trigger handle_params. Also, we don't want handle_params to be placed in every LiveView.
We can add custom behavior to other callbacks using attach_hook.
So, we'll register this callback i.e handle_params to the socket struct, something like this.
socket |> attach_hook(:set_menu_path, :handle_params, &manage_active_tabs/3)
In the above code,
:set_menu_path is the hook name
:handle_params is the callback
manage_active_tabs is the function body of handle_params
This hook will be assigned to socket struct and this will be done in the on_hook function.
We'll attach these hooks via Phoenix.LiveView.Router.live_session.
We'll add a file live/route_assigns.ex, and we'll mount the RouteAssigns module using on_mount in the router.ex as follows
live_session :default, `on_mount: NavigationDemoWeb.RouteAssigns` do
scope "/", NavigationDemoWeb do
pipe_through :browser
live "/", HomePageLive, :index
live "/page1", Page1Live, :index
live "/page2", Page2Live, :index
end
end
In on_mount you have access to socket struct. We will assign a menus key with value as a list of tuples of menu name and the route of the menu.
Refer to the following code
defmodule NavigationDemoWeb.RouteAssigns do
import Phoenix.LiveView
alias NavigationDemoWeb.Router.Helpers, as: Routes
def on_mount(:default, _params, _session, socket) do
socket =
assign(socket,
menus: [
{"Home", Routes.home_page_path(socket, :index)},
{"Page 1", Routes.page1_path(socket, :index)},
{"Page 2", Routes.page2_path(socket, :index)}
]
)
{:cont,
socket
|> attach_hook(:set_menu_path, :handle_params, &manage_active_tabs/3)
}
end
defp manage_active_tabs(_params, url, socket) do
{:cont, assign(socket, current_path: URI.parse(url).path)}
end
end
Notice cont in the return tuple of on_mount and manage_active_tabs functions. The hook has the option to either halt or continue the process.
In our handle_params callback function i.emanage_active_tabs we will assign current_path field to socket struct. This field will help us to select the current menu.
Because of assigning menus to the socket variable it will be accessible in the live.html.heex file.
We can iterate over it i.e menus, with the first element of the tuple as the link name and the second element as the path. Refer to the code below
// live.html.heex
<ul>
<%= for {menu_name, path} <- @menus do %>
<li class={"#{if path == @current_path, do: "active"}"}>
<%= live_patch menu_name, to: path %>
</li>
<% end %>
</ul>
In the above code, you can notice the if condition which applies active if @current_path is equal to the path of the menu.
The active class applied a background of red color to highlight the selected menu.
I know the blog was very conceptual with lots of information. I hope I did justice with the explanation and you like this blog. If you have any questions then please comment below. Thanks for reading 😊.