Super flexible Phlex components

TheDumbTechGuyTheDumbTechGuy
2 min read

Phlex is a Ruby gem for building fast object-oriented HTML and SVG components.

Unlike traditional erb, where you write html interwoven with ruby, Phlex allows you to write your components with plain ol' ruby, and if you've used ruby, I don't have to tell you the possibilities this opens.

In this article, I want to show you how you can make a super flexible component using a technique I call phlexi rendering.

Let's take a very simple PageHeader component that has a title and a description.

class PageHeader < Phlex::HTML
  def initialize(page_title:, page_description:)
    @page_title = page_title
    @page_description = page_description
  end

  def view_template
    div(class: "flex-row items-center justify-between space-y-3 sm:flex sm:space-y-0 sm:space-x-4 mb-6") {
      div {
        h2(class: "mb-2 text-3xl font-extrabold leading-none tracking-tight text-gray-900 md:text-4xl dark:text-white") {
          @page_title
        }
        p(class: "text-gray-500 dark:text-gray-400") {
          @page_description
        }
      }
    }
  end
end

This works fine, buuuuuuut, we are limited to only rendering strings. What if I want this one page to use something different?

Am I limited to recreating the PageHeader for this page? What if it is part of a layout?
phlexi rendering to the rescue! By defining this method, you can pass strings, blocks or even another component.

def phlexi_render(arg, &)
  return unless arg
  raise ArgumentError, "phlexi_render requires a default render block" unless block_given?

  # Handle Phlex components or Rails Renderables
  if arg.class < Phlex::SGML || arg.respond_to?(:render_in)
    render arg
  # Handle procs
  elsif arg.respond_to?(:to_proc)
    instance_exec(&arg)
  else
    yield
  end
end

First, let's modify our component's view template to use phlexi_render for the description.

def view_template
  div(class: "flex-row items-center justify-between space-y-3 sm:flex sm:space-y-0 sm:space-x-4 mb-6") {
    div {
      h2(class: "mb-2 text-3xl font-extrabold leading-none tracking-tight text-gray-900 md:text-4xl dark:text-white") {
        @page_title
      }
      phlexi_render(@page_description) {
        p(class: "text-gray-500 dark:text-gray-400") {
          @page_description
        }
      }
    }
  }
end

Then we create our custom description component.

class CustomDescriptionComponent < Phlex::HTML
  def view_template
    div(class: "mt-4 gap-4 flex") do
      p(
        class:
          "text-2xl font-extrabold text-gray-900 sm:text-3xl dark:text-white"
      ) { " $1,249.99" }
      div(class: "flex items-center gap-2 mt-2 sm:mt-0") do
        a(
          href: "#",
          class:
            "text-sm font-medium leading-none text-gray-900 underline hover:no-underline dark:text-white"
        ) { " 345 Reviews " }
      end
    end
  end
end

Now, we can render our page header like this

render PageHeader.new(
  title: "Special purpose vehicles",
  description: CustomDescriptionComponent.new
)

You can also pass in procs which will be executed in the context of the PageHeader component like this:

render PageHeader.new(
  title: "Special purpose vehicles",
  description: -> {
    p { "This is a callable description" }
  }
)

And that's it! I hope you liked this trick.
Stay Phlexi!

0
Subscribe to my newsletter

Read articles from TheDumbTechGuy directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

TheDumbTechGuy
TheDumbTechGuy