I’ve recently found myself writing many Phoenix templates and wanted to streamline my experience.
I often want to set some defaults, e.g. in Bootstrap a table usually has a class “table” <table class="table">
But then I’d like to take the default and specify that this time the table should be striped.
I can’t use Phoenix.HTML.Tag.content_tag
in this case. Everything in Phoenix.HTML produces final io lists that can’t be modified later. I figured I need some middleware.
defmodule ComposableHtml.Tag do
defstruct [:tag_name, :content, :attrs]
end
This way I can define a tag and extend it:
table = %Tag{tag_name: :table, content: ..., attrs: [class: "table"]}
...
table_striped = tag |> update_attr(:class, &"#{&1} table-striped")
I saw the same problem in the Elm community https://www.youtube.com/watch?v=PDyWP-0H4Zo
The talk concludes that a data structure that allows overwriting its fields is a nice API.
I’d like to turn my helpers into a lib generic enough to be a building block for other libraries like https://hexdocs.pm/ex_effective_bootstrap/0.1.17/api-reference.html
The idea is also to be able to use the ComposableHtml.Tag
directly in the templates by implementing Phoenix.HTML.Safe
protocol:
defmodule ComposableHtml.Tag do
...
def to_phoenix_html(%Tag{} = tag) do
if tag.content do
PhoenixTag.content_tag(tag.tag_name, to_phoenix_html(tag.content), tag.attrs)
else
PhoenixTag.tag(tag.tag_name, tag.attrs)
end
end
def to_phoenix_html(list_of_tags) when is_list(list_of_tags) do
Enum.map(list_of_tags, &to_phoenix_html/1)
end
# We pass the data in the `content_tag`, so we don't have to escape here
def to_phoenix_html(data), do: data
end
defimpl Phoenix.HTML.Safe, for: ComposableHtml.Tag do
alias ComposableHtml.Tag
def to_iodata(data) do
{:safe, data} = Tag.to_phoenix_html(data)
data
end
end
I’ve found existing HTML builder that uses macros https://github.com/dczombera/html_builder
But it is not composable.
My questions are:
- Would you find such a library useful?
- If it is useful, are there any existing libraries for composing HTML that I missed?
- If I am going to create a new library, could you help me with API design?
Ad.3 The with_*
approach from Brian’s talk seems compelling but it is more specific than I like. E.g. in the talk there is |> withRole Danger |> withFormat Outline
and in Bootstrap they would both translate to a class <button class="btn btn-outline btn-danger">
I am not sure what would be some proper names for functions that append to an attribute (e.g. changing class="table"
to class="table table-striped"
), and what names to use for functions overwriting the attributes.