phlex icon indicating copy to clipboard operation
phlex copied to clipboard

Support .html.phlex files

Open natematykiewicz opened this issue 1 year ago • 1 comments

If I’m understanding how Phlex works, in order to use it in Rails you will either need to call render in the controller and hand it a Phlex component, or embed a Phlex component in an erb, slim, or haml file. That got me thinking, what if Phlex supported .html.phlex files so that Phlex files could live in app/views. I’m thinking they’d be fairly similar to Jbuilder files then. They’d be anonymous components instead of named, and basically be what you see in the template functions in the examples.

You could support blocks with a yield, to nest templates (just like partials), and they’d have access to instance variables set in the controller.

Take this example from the documentation:

class NavComponent < Phlex::Component
  def template
    nav id: "main_nav" do
      ul do
        li { a "Home", href: "/" }
        li { a "About", href: "/about" }
        li { a "Contact", href: "/contact" }
      end
    end
  end
end

Now imagine this:

# app/views/things/show.html.phlex
nav id: "main_nav" do
  ul do
    @links.each do |link|
      li { a link.text, href: link.url }
    end
  end
end

I feel like it could help gain traction if people could still define views in a way they’re used to (app/views, with a DSL instead of defining a class). This would make it more of a direct competitor with ERB, Slim, and HAML then.

Under the hood, I imagine Phlex would define a constant for this based on the path. Like Phlex::RailsViews::Things::Show or something.

My thought is, if it’s a reusable thing, devs would give it it’s own name and put it in app/components or something. But if it’s just a view for a specific page, this anonymous form seems simpler, and more Railsy (and intentionally not easily reusable), but still defines the component with Ruby.

natematykiewicz avatar Jul 19 '22 03:07 natematykiewicz

Hey @natematykiewicz, thanks for opening the issue. In principle, I like the idea of supporting anonymous Phlex templates like this. There are definitely some challenges around making it a proper ActionView template engine though.

One thing that's not clear from your example is where the @links instance variable came from. I guess that could come from the default initializer method that sets all the keyword arguments to instance variables, though I think it's good practice to override that and specify which keywords are expected / required by the component.

One thing I really don't want to imitate is ActionView's implicit copying of instance variables from the controller. The page should declare which arguments it expects and the controller should pass them down.

joeldrapper avatar Jul 19 '22 10:07 joeldrapper

I don’t think it’s worth defining a new file format for anonymous Phlex components if it would only be applicable to components that don't accept arguments. We can re-open this if we find a more compelling use-case.

joeldrapper avatar Aug 18 '22 16:08 joeldrapper

I think one thing that would be good is adding a section to the README that gives an example of how you'd use this in Rails. How do you go from a controller action, to rendering HTML that's a Phlex component?

I think that's the crux of what I wanted to solve, and just (wrongfully) assumed that a .html.phlex file was the answer.

natematykiewicz avatar Aug 18 '22 16:08 natematykiewicz

Or is the assumption that you'd still render a .html.erb file, and pieces of that would render a Phlex component (similar to how ViewComponent works)? Your vision seems to be having the entire HTML generated with Ruby though, no files in app/views basically.

natematykiewicz avatar Aug 18 '22 16:08 natematykiewicz

We haven't finished the Rails documentation yet, but you should be able to just do this from your controller.

class ArticlesController < ApplicationController
  def index
    render Views::IndexPage.new(
      articles: Article.all.load_async
    )
  end
end

joeldrapper avatar Aug 18 '22 16:08 joeldrapper

Well that seems simple enough, and would work nicely respond_to as well:

class ArticlesController < ApplicationController
  def index
    articles = Article.all.load_async

    respond_to do |format|
      format.json { render json: articles }
      format.html { render Views::IndexPage.new(articles) }
    end
  end
end

natematykiewicz avatar Aug 18 '22 16:08 natematykiewicz