Using Surface for Layout - it is a hack but fun

Hi all,

I love surface and live_view! And I don’t love to write top level components with tailwind utility classes twice :slight_smile:. So I wanted to use Surface to define a layout. Maybe I missed it out, but I always read, that there is not solution yet and I have to wait for surface component/3 to be released. But I didn’t want to.
I had the idea, that a live view is a view and a view could be a layout. I tried it with a surface live_view as layout and the TopNavigation as a surface component:

defmodule Www do
  ...
  def live_view do
    quote do
      use Surface.LiveView, layout: {Www.LiveLayout, "live.html"}

      data top_menu_items, :list, default: []

      unquote(view_helpers())
    end
  end

  def component do
    quote do
      use Surface.Component

      unquote(view_helpers())
    end
  end

  ...

  @doc """
  When used, dispatch to the appropriate controller/view/etc.
  """
  defmacro __using__(which, opts \\ []) when is_atom(which) do
    apply(__MODULE__, which, opts)
  end
end
defmodule Www.LiveLayout do
  use Www, :live_view

  alias Www.Ui.TopNavigation
  alias Surface.Components.LiveRedirect

  def render("live.html", assigns), do: render(assigns)

  def render(assigns) do
    ~F"""
    <header>
      <TopNavigation>
        <:brand><LiveRedirect to={ Routes.page_path(@socket, :index) }>Experiment</LiveRedirect></:brand>
        <:menu>
          <TopNavigation.MenuItem :for={ {to, name} <- @top_menu_items } to={to}>{name}</TopNavigation.MenuItem>
        </:menu>

      </TopNavigation>
    </header>
    <TopNavigation.Content>
      <main role="main" class="container">
        <p class="alert alert-info" role="alert" phx-click="lv:clear-flash" phx-value-key="info">{live_flash(@flash, :info)}</p>
        <p class="alert alert-danger" role="alert" phx-click="lv:clear-flash" phx-value-key="error">{live_flash(@flash, :error)}</p>

        {@inner_content}
      </main>
    </TopNavigation.Content>
    """
  end
end

And it works!

Besides of sharing the solution, I wrote this post to get the opinion of the community about:

Is it a hack or is it supposed to work like that?

  • The fact, that I needed to write a render/2 function in the layout module makes it feel like a hack.

P.S. Later I needed the top_menu_items as assigned values, so it clearly becomes a hack. Obviously mount is not called on the layout - but it still works :joy:, so this post is only for sharing a small war story.

@ouven Thanks for using Surface!

I do not use live.html any more in my project. My live.html file contains only <%= @inner_content %>.

I now use the approach described here: Surface - A component-based library for Phoenix LiveView - #122 by Malian

Feel free to ask question if it is not clear! You can also join us on the slack channel :slight_smile:

3 Likes