Hi all, I’m using Phoenix with Bootstrap and I’m having some trouble figuring out the best way to set the active navbar item.
In Bootstrap, adding the "active"
class to a nav item sets it as the current nav item. My goal is to do something like this in my Phoenix templates:
<li class="nav-item <%= nav_item_state(:foos) %>">
<%= link "Foos", to: Routes.foo_path(@conn, :index), class: "nav-link" %>
</li>
<li class="nav-item <%= nav_item_state(:bars) %>">
<%= link "Bars", to: Routes.bar_path(@conn, :index), class: "nav-link" %>
</li>
and something like this in my controllers:
# I want to avoid having to pass `:foos`/`:bars` as an assign every time I call `render`
defmodule MyAppWeb.FooController do
...
def active_nav_item, do: :foos
end
defmodule MyAppWeb.BarController do
...
def active_nav_item, do: :bars
end
I want nav_item_state(:foos)
to return "active"
when the current controller is FooController
, and to return ""
otherwise.
I think nav_item_state
should be defined in my LayoutView
module, but not really sure how to piece things together. Appreciate any suggestions!
Hi @zizheng!
A very simplistic version of what I’m using in my applications is something like this:
def nav_link(conn, text, opts) do
to = opts[:to]
case Map.fetch(conn, :request_path) do
{:ok, ^to} ->
link(text, class: "#{opts[:class]} active")
_ ->
link(text, opts)
end
end
And then, you can use it like this:
<li class="nav-item">
<%= nav_link(@conn, "My Navlink",
to: Routes.resource_path(@conn, :edit, @resource),
class: "nav-link" %>
</li>
I also have other options that allows me to selectively enable/ disable the nav link based on the current path, which is very useful for layouts. I usually pass a regex like this: ~r(/resource/new$)
or ~r(/resource/\d+$)
; which tells the logic behind to enable the nav link for nested resources, etc.
Hope that helps, cheers!
Thanks for the reply @thiagomajesk! I wonder how this part works:
case Map.fetch(conn, :request_path) do
{:ok, ^to} ->
Doesn’t this mean the current request path needs to match the nav link target path for the item to be active…?
Also, I kinda want to avoid having to pass @resource
as an assign each time I call render
. I’m trying to figure out how to have each controller only declare once (for example by using the active_nav_item
function in my example code) what nav item it activates.
Have you tried using nav-pills? I use them and ccs customize it. Bootstrap handles the active class.
Also see th “Using Data Attributes” section at the bottom which might also help.
Best regards.
You can create a function in your view file like this
def active_menu(conn, item) do
if conn.request_path =~ Atom.to_string(item), do: "active", else: ""
end
and in your html.eex file do like this
<li class=" <%= active_menu(@conn, :foo) %>"> <a href="/foo">foo</a></li>
<li class=" <%= active_menu(@conn, :bar) %>"> <a href="/bar">bar</a></li>
1 Like
Yes, like I said this is roughly what I’m using in my application. You certainly will have to tweak for your own needs. Perhaps matching only parts of the path, using a contains expression or a regex pattern, etc.
Unfortunately, this is how functional programming works, you explicitly pass the values around.
Also, IMHO I don’t think you should rely on your controller’s for that, this is exactly what views are for.
Is there a specific reason you are trying to do that?
Another solution could be creating a plug in your controllers that puts an assign with the desired path that should be activated in the template:
plug :put_active_path when action in [:index, :new, :edit]
# [...]
def put_active_path(conn, _opts) do
assign(conn, :active_path, Routes.resource(conn, :index))
end
I don’t see a lot of ways around explicitly passing the values you need to the helper function though.
Cheers!
Thanks, I ended up creating a custom plug that puts the current active nav item in the conn:
defmodule MyAppWeb.FooController do
plug MyAppWeb.Plugs.ActiveNavItem, :foos
...
end
and added a function in my LayoutView
that queries the conn for this information.
1 Like
this doesn’t cover situations where there isnt a request path.