A library to write html text in an elixir way.
If available in Hex, the package can be installed
by adding html_writer
to your list of dependencies in mix.exs
:
def deps do
[
{:html_writer, "~> 0.2"}
]
end
Documentation can be generated with ExDoc and published on HexDocs. Once published, the docs can be found at https://hexdocs.pm/html_writer.
There are templates, and elixir's eex template is among the nicer ones. However, I still hate templates. It has bastardized syntax that is neither html nor elixir (or any host programming language), feels awkward, and messes up syntax highlight in editors.
There are cleaner looking template like mustache template, however the functionality is too weak. You would need to put significant view related code into the controller layer.
HtmlWriter is not a template system. It is just a simple elixir library making heavy use of closures and pipe operators so you can write html in a functional way. Here is a simple example:
[]
|> div(fn h ->
h
|> h1("My Title")
|> a("link text", href: "#")
end, class: "my-class")
Which will print the text as:
<div class="my-class">
<h1>My Title</h1>
<a htef="#">Link Text</a>
</div>
Yes, it is more verbose, however:
- it is pure elixir and can be refactored in any way you want
- there is no way to write mal-formed html
If you already have the html, just want to change some small parts into dynamic contents, you prabably should stick to templates. If you are printing complex HTML structure that is highly dynamic, you may want to try this.
You could probably do similiar things with Phoenix.HTML but ity is not intended to be used this way and will be very awkward.
There is also some helper macros that make your code easier to read:
[]
|> ul(fn h ->
h
|> roll_in(["a", "b", "c"], fn each, h ->
h |> li(each)
end)
end)
Can you guess what it will do?
Please also keep in mind that nothing is escaped by default. You'll have to esacape the problematic
string your self by calling escape/1
.
The library has no dependancy on Phoenix, you can use it in your standalone app to print html files. Just import the library so all html functions came into your scope:
import Kernel, except: [div: 2]
import HtmlWriter
You would need to un-import the div/2
from kernel to avoid function name collision. then:
f = File.open!("my.html", [:write, :delayed_write, :utf8])
new_html()
|> html(fn h ->
h
|> head(fn h ->
h
|> meta(charset: "utf-8")
|> title("My title")
end)
|> body(fn h ->
h
|> print_my_body()
end, lang: "en")
end)
|> export()
|> bind_to(html)
IO.write(f, html))
File.close(f)
As you can see, everything fit into the flow. Most functions in this library takes a chardata as the first argument and just add to it. I called them builder functions. You are encouraged to write your own builder functions by combining the primitive builder functions in closures.
Of course you can also use it in a Phoenix app. I still use templates, but only for layouts. My inner views are all elixir code, without templates. In your view module:
import Kernel, except: [div: 2]
import HtmlWriter
defp html_fragment(f), do: f |> fragment() |> Phoenix.HTML.raw()
def render("index.html", %{key: value}) do
html_fragment(fn h ->
...
end)
end
Just write your own render function clauses with HtmlWriter then you can do away the templates. I also don't use or import Phoenix.HTML
into my view modules to avoid function name collisions. The html_fragment/1
is not provided in the library because I don't want to pull in dependancy for such a simple thing.
It is also possible to completely get rid of EEX
and Phoenix.HTML
by implementing your layout modules in HtmlWriter
. Your layout module can be implemented as something like:
...
def wrap(assigns, inner) do
new_html()
|> html(fn h ->
h
|> head(&my_head(&1))
|> body(fn h ->
h
|> my_header(assigns)
|> div(inner, class: "content", id: "inner-content")
|> my_footer(assigns)
end)
end)
|> export()
end
And your view can be something like:
def render("index.htm", %{key: value} = assigns) do
Layout.wrap(assigns, fn h ->
...
end)
end
By using the htm
suffix instead of html
suffix, you bypassed all the layout and HTML escaping from phoenix while still sending the correct content_type
.
PRs are welcomed. The code is stable, only ~100 LOC, but poorly documented for now. I encourage users to read the code and understand what is it doing.