Components in Phlex 2.0

Quiz time: Old enough to remember Haml?
To some limited degree Phlex is like Haml -- I know; it's not fair to neither but please bear with me! Haml made it fun and easy to write HTML soup and had it not been for the strict formating I'd probably still be doing it!
Enter Phlex and all of a sudden your old habits die hard - again
Consider this tiny clip:

div(class:"m-2") do
  plain { "Enter Phlex and all of a sudden your "}
  a(href: "https://www.youtube.com/watch?v=2SkYNSKB1aM") { "old habits..." }
end

If you squint your eyes and let them just sweep the code block for a quick look-see? It could fool me - if I dropped the 'end' and added a '%' on the p's and a's :)

(Phlex even has a page in the new beta 2.0 documentation assigned to the difference between Haml and Phlex so I guess it's not all smoke and mirrors)
Anyways with that introduction off my chest let's get this band on the road!

Airborne

First I added the gem to my new-ish Rails project (it's actually running off of the main branch on github - so quite new in fact, but you don't have to be leaning over the edge to join the [phlex.]fun):

bundle add phlex-rails --version=2.0.0.rc1

I willingly stayed in character as the bonus pater familias and did the

bundle exec rails generate phlex:install

So, now I was ready to start porting my components library to Phlex 2.0 – components that I use for index views and forms, mainly and in this post I'll focus on the index views.

Getting the hang of it

"You cannot walk until you master crawling" is a life experience of mine so let's crawl for a start!
The Phlex documentation starts of with building a component but I'd like to introduce you to a slightly different learning curve. We'll build that index action on a home controller – in essence an endpoint like https://app.mortimer.pro/

Three Rails files comes to mind: views/layouts/application.html.erb,  views/home/index.html.erb and controllers/home_controller.rb

From the docs we know where this is all going to end so let's do the obvious thing and write what we'd like to have

# controllers/home_controller.rb
class HomeController < ApplicationController
  def index
    render Views::Home::Index.new
  end
end

Reloading https://localhost:3000 immediately confirms what we feared the most – something is missing (and if you're getting that dreaded almost blank screen informing you that there is an issue with security – I have just the medicin for you)!

Try this in your CLI (you could bring one up using [Cmd+j] if you're using VS Code with default keyboard shortcuts on a Mac) bundle exec rails generate phlex:view home::index
Another reload in the browser and we're once again good – kind'a

Inheritance and composition

So far we've managed little besides exchanging html.erb with rb but that is about to change – scouts honour!
Once again let's write what we want to end up having

# controllers/home_controller.rb
class HomeController < ApplicationController
  layout false

  ...8<..

Reloading tell us that the layout went away. We'll fix that

First back in the CLI do bundle exec rails g phlex:component layout

Then follow up with these changes

# views/home/index.rb
class Views::Home::Index < Views::Base
  def page_title = "Home"
  def layout = Layout

  ...8<..

# views/base.rb
class Views::Base < Components::Base
  PageInfo = Data.define(:title)
  def around_template 
    render layout.new(page_info) do 
      super 
    end
  end
  
  def page_info 
    PageInfo.new( title: page_title ) 
  end
end

We are one change short - but before we add that one allow me to introduce you to one of my absolute favorite web services: phlexing.fun
Its service is so simple and yet so powerful: have html -> get phlex
So copy the contents of your views/layouts/application.html.erb and paste it into the textarea with the placeholder "Paste ERB here" and shortly after you'll have a copy-ready snippet of Phlex to paste into your layout.rb
(be prepared to follow the 'prompting' when reloading - your HTML will probably contain a number of calls to Rails specifics - like csrf_meta_tags but Phlex has answers for most)

# components/layout.rb
class Components::Layout < Components::Base
  def initialize(page_info)
    @page_info = page_info
  end

  def view_template
    doctype
    html do
      head do
        title { @page_info.title }
        meta(name: "viewport", 
          content: "width=device-width,initial-scale=1")
        meta(name: "apple-mobile-web-app-capable", 
          content: "yes")
        meta(name: "mobile-web-app-capable", 
          content: "yes")
        csrf_meta_tags
        
        # csp_meta_tag
        # Enable PWA manifest for installable apps 
        # (make sure to enable in config/routes.rb too!)
        # = tag.link rel: "manifest", 
        #  href: pwa_manifest_path(format: :json)
        
        link(rel: "icon", href: "/icon.png", type: "image/png")
        link(rel: "icon", href: "/icon.svg", type: "image/svg+xml")
        link(rel: "apple-touch-icon", href: "/icon.png")

        # Includes all stylesheet files in app/assets/stylesheets
        stylesheet_link_tag "tailwind",
          "inter-font",
          "data-turbo-track": "reload"

        stylesheet_link_tag :app, 
          "data-turbo-track": "reload"

        javascript_importmap_tags
      end

      body do
        main(class: "container mx-auto mt-28 px-5 flex flex-col") do
          yield
        end
      end
    end
  end
end

Agreed, this did not bring us Total World Domination or Peace To All Mankind but we took a decent step towards DRY-ing up and componentizing our views – more on that in the next post, and while I hurry to post that you could give this one a quick skim; it's on DRY-ing up the controller!