DEV Community

Masatoshi Nishiguchi
Masatoshi Nishiguchi

Posted on

Elixir Phoenix 1.7 breadcrumb component

I like the breadcrumb navigation in a web app so I wanted to make one what works in Phoenix LiveView apps.

what breadcrumb navigation is

I think I first learned about the breadcrumb from Bootstrap a long time ago, but I do not know the exact definition of it. Let's wikipedia.

A breadcrumb or breadcrumb trail is a graphical control element used as a navigational aid in user interfaces and on web pages. It allows users to keep track and maintain awareness of their locations within programs, documents, or websites.

https://en.wikipedia.org/wiki/Breadcrumb_navigation

my custom breadcrumb component

I do not need anything smart. Simple is best. My breadcrumb component simply accepts a list of navigation items. It might be used like below.

<.breadcrumb items={[
  %{text: "Home", navigate: ~p"/"},
  %{text: "Examples", navigate: ~p"/examples"},
  %{text: "Light"}
]} />
Enter fullscreen mode Exit fullscreen mode

liveview-breadcrumb 2023-08-11 at 22.40.14.gif

By the way, the LiveView pages in the screen recording above are from Pragmatic Studio Phoenix LiveView Course. The online course is amazing. I added my custom breadcrumb component onto them for my extra learning.

HTML and Tailwind classes

Phoenix 1.7 comes with Tailwind CSS by default. Tailwind CSS just works without any configuration changes.

First of all, I did the Internet search for an example HTML snippet with Tailwind CSS classes because my primary focus is to enjoy programming with Elixir and Phoenix, not HTML, not Tailwind CSS.

I started by using this example snippet from Flowbite.

https://flowbite.com/docs/components/breadcrumb

Phoenix.Component.link/1

For links, I took advangate of Phoenix.Component.link/1 and its :navaigate attribute so I can navigate between LiveView pages smoothely.

https://hexdocs.pm/phoenix_live_view/Phoenix.Component.html#link/1

icons

Phoenix 1.7 ships with SVG icons from heroicons and those SVG icons can be used easily via MyAppWeb.CoreComponents.icon component.

Since all the functions in MyAppWeb.CoreComponents module are imported by default, the module name can be omitted when we use the icon component.

Thanks to the icon component, we no longer need to wrap our head around in terms of setting up icons, as long as we are happy with what heroicons library provides. It is very nice.

building breadcrumb component

I created a new module named MyAppWeb.Breadcrumb that is dedicated for the breadcrumb function component. Alternatively it could have been in MyAppWeb.CoreComponents along with the icon component but I chose to create a new module so that I can focus on the breadcrumb concern. I might change my mind in the future.

Here is the entirety of the module I created.

defmodule MyAppWeb.Breadcrumb do
  use Phoenix.Component
  import MyAppWeb.CoreComponents

  attr :items, :list, required: true

  def breadcrumb(assigns) do
    assigns = assign(assigns, :size, length(assigns.items))

    ~H"""
    <nav class="flex" aria-label="breadcrumb">
      <ol class="inline-flex items-center space-x-1 md:space-x-3">
        <.breadcrumb_item
          :for={{item, index} <- Enum.with_index(@items)}
          type={index_to_item_type(index, @size)}
          navigate={item[:navigate]}
          text={item[:text]}
        />
      </ol>
    </nav>
    """
  end

  defp index_to_item_type(0, _size), do: "first"
  defp index_to_item_type(index, size) when index == size - 1, do: "last"
  defp index_to_item_type(_index, _size), do: "middle"

  attr :type, :string, default: "middle"
  attr :navigate, :string, default: "/"
  attr :text, :string, required: true

  defp breadcrumb_item(assigns) when assigns.type == "first" do
    ~H"""
    <li class="inline-flex items-center">
      <.link navigate={@navigate} class="inline-flex items-center text-sm font-medium">
        <.icon name="hero-home" class="h-4 w-4" />
      </.link>
    </li>
    """
  end

  defp breadcrumb_item(assigns) when assigns.type == "last" do
    ~H"""
    <li aria-current="page">
      <div class="flex items-center">
        <.icon name="hero-chevron-right" class="h-4 w-4" />
        <span class="ml-1 text-sm font-medium md:ml-2">
          <%= @text %>
        </span>
      </div>
    </li>
    """
  end

  defp breadcrumb_item(assigns) do
    ~H"""
    <li>
      <div class="flex items-center">
        <.icon name="hero-chevron-right" class="h-4 w-4" />
        <.link navigate={@navigate} class="ml-1 text-sm font-medium md:ml-2 ">
          <%= @text %>
        </.link>
      </div>
    </li>
    """
  end
end
Enter fullscreen mode Exit fullscreen mode

only one public function

The module has only one public function called breadcrumb, which is a function component. For readability, I factored out a sub-component breadcrumb_item that maps provided info to the breadcrumb item markup according to the breadcrumb item type.

three types of breadcrumb items

Typically the breadcrumb navigation has three types of items and they have different looks and behaviors.

  1. the leftmost
    • the home
    • link enabled
    • can look special with home icon
  2. the rightmost
    • the current
    • link disabled
  3. the in-between
    • all other paths between the home and the current
    • link enabled

For each type, the sub-component breadcrumb_item renders differently. We can easily determine the appropriate breadcrumb item type by the index and length of the list. The private function index_to_item_type does the job.

We need one preparation before rendering the component. The length of the list varies so we need to find out the list length beforehand.

Enum.with_index/2 gives an index to each element of the list. With that index and the pre-calculated list length, we can determine the item type.

Top comments (0)