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

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.

### Let's start building

* 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.
    

### Adding liveview pages

* 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
    

```elixir
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
    

```sql
   // 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
```

* Adding of LiveView pages is done, now we will add a navigation menu with minimal styling.
    

### Add Navbar menu

* 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
    

```sql
  <body>
      <%= @inner_content %>        
  </body>
```

* Open the `live.html.heex` file and add the following code
    

```elixir
  <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
    
    !\[Screenshot 2022-06-11 at 12.34.14 PM.png\](https://cdn.hashnode.com/res/hashnode/image/upload/v1654931128407/bdL8-r1Wh.png align="left")
    

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.

### Highlighting the current menu (Dirty Solution)

* The easiest solution can be using the \`handle\_params\` callback in each LiveView file and assigning some variable say \`current\_path\` to the socket struct which will be accessible in the \`live.html.heex\`
    

```elixir
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
    
    \`\`\`
    
    ```elixir
    <%= 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.
    

### Highlighting the current menu (Improved Solution)

* 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.
    

```elixir
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
    

```sql
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
    

```sql
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.e`manage_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
    

```sql
// 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 😊.
