Properly emit boolean attributes (#139)

* Update some docs

* Properly emit boolean attributes.

* Account for quoted literals when compiling attributes

* Update changelog
This commit is contained in:
Mitchell Hanberg 2021-06-26 21:47:21 -04:00 committed by GitHub
parent d307fe35de
commit 9d05f74cdf
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 62 additions and 23 deletions

View file

@ -2,7 +2,9 @@
## Main ## Main
Nothing yet ### Enhancements
- [breaking] Attributes who values are boolean expressions will be emitted as boolean attributes.
## 0.6.2 ## 0.6.2

View file

@ -22,15 +22,6 @@ def deps do
] ]
end end
``` ```
or
```elixir
def deps do
[{:temple, github: "mhanberg/temple"}]
end
```
## Goals ## Goals
Currently Temple has the following things on which it won't compromise. Currently Temple has the following things on which it won't compromise.
@ -140,7 +131,8 @@ config :phoenix, :template_engines,
# you can enable Elixir syntax highlighting in your editor # you can enable Elixir syntax highlighting in your editor
lexs: Temple.LiveViewEngine lexs: Temple.LiveViewEngine
# If you're going to be using live_view, make sure to set the `:mode` # If you're going to be using live_view, make sure to set the `:mode` to `:live_view`.
# This is necessary for Temple to emit markup that is compatible.
config :temple, :mode, :live_view # defaults to normal config :temple, :mode, :live_view # defaults to normal
# config/dev.exs # config/dev.exs
@ -156,6 +148,7 @@ config :your_app, YourAppWeb.Endpoint,
```elixir ```elixir
# app.html.exs # app.html.exs
"<!DOCTYPE html>"
html lang: "en" do html lang: "en" do
head do head do
meta charset: "utf-8" meta charset: "utf-8"
@ -163,7 +156,7 @@ html lang: "en" do
meta name: "viewport", content: "width=device-width, initial-scale=1.0" meta name: "viewport", content: "width=device-width, initial-scale=1.0"
title do: "YourApp · Phoenix Framework" title do: "YourApp · Phoenix Framework"
link rel: "stylesheet", href: Routes.static_path(@conn, "/css/app.css") _link rel: "stylesheet", href: Routes.static_path(@conn, "/css/app.css")
end end
body do body do

View file

@ -22,6 +22,8 @@ c Form, changeset: @changeset, action: @action do
text_input f, :author text_input f, :author
error_tag(f, :author) error_tag(f, :author)
input type: "text", disabled: true, id: "disabled-input"
div do div do
submit "Save" submit "Save"
end end

View file

@ -37,6 +37,7 @@ defmodule TempleDemoWeb.TempleFeatureTest do
s |> click(Query.option("21")) s |> click(Query.option("21"))
end) end)
|> fill_in(Query.text_field("Author"), with: "Mitchelob Ultra") |> fill_in(Query.text_field("Author"), with: "Mitchelob Ultra")
|> assert_has(Query.css("#disabled-input[disabled]"))
|> click(Query.button("Save")) |> click(Query.button("Save"))
|> assert_text("Post created successfully.") |> assert_text("Post created successfully.")
end end

View file

@ -19,6 +19,14 @@ defmodule Temple do
id id
end end
# Attributes that result in boolean values will be emitted as a boolean attribute. Examples of boolean attributes are `disabled` and `checked`.
input type: "text", disabled: true
# <input type="text" disabled>
input type: "text", disabled: false
# <input type="text">
# if and unless expressions can be used to conditionally render content # if and unless expressions can be used to conditionally render content
if 5 > 0 do if 5 > 0 do
p do p do
@ -61,7 +69,9 @@ defmodule Temple do
There are two "modes", `:normal` (the default) and `:live_view`. There are two "modes", `:normal` (the default) and `:live_view`.
In `:live_view` mode, Temple emits markup that uses functions provided by Phoenix LiveView in order to be fully "diff trackable". In `:live_view` mode, Temple emits markup that uses functions provided by Phoenix LiveView in order to be fully "diff trackable". These LiveView functions have not been released yet, so if you are going to combine Temple with LiveView, you need to use the latest unreleased default branch from GitHub.
You should use `:live_view` mode even if you only have a single LiveView.
```elixir ```elixir
config :temple, :mode, :normal # default config :temple, :mode, :normal # default

View file

@ -18,11 +18,11 @@ defmodule Temple.Parser.Utils do
for {name, value} <- attrs, into: "" do for {name, value} <- attrs, into: "" do
name = snake_to_kebab(name) name = snake_to_kebab(name)
case value do cond do
{_, _, _} = macro -> (not is_binary(value) && Macro.quoted_literal?(value)) || match?({_, _, _}, value) ->
" " <> name <> "=\"<%= " <> Macro.to_string(macro) <> " %>\"" ~s|<%= {:safe, Temple.Parser.Utils.build_attr("#{name}", #{Macro.to_string(value)})} %>|
value -> true ->
" " <> name <> "=\"" <> to_string(value) <> "\"" " " <> name <> "=\"" <> to_string(value) <> "\""
end end
end end
@ -37,10 +37,22 @@ defmodule Temple.Parser.Utils do
for {name, value} <- attrs, name not in [:inner_block, :inner_content], into: "" do for {name, value} <- attrs, name not in [:inner_block, :inner_content], into: "" do
name = snake_to_kebab(name) name = snake_to_kebab(name)
" " <> name <> "=\"" <> to_string(value) <> "\"" build_attr(name, value)
end} end}
end end
def build_attr(name, true) do
" " <> name
end
def build_attr(_name, false) do
""
end
def build_attr(name, value) do
" " <> name <> "=\"" <> to_string(value) <> "\""
end
def split_args(not_what_i_want) when is_nil(not_what_i_want) or is_atom(not_what_i_want), def split_args(not_what_i_want) when is_nil(not_what_i_want) or is_atom(not_what_i_want),
do: {[], []} do: {[], []}

View file

@ -96,7 +96,7 @@ defmodule Temple.Parser.NonvoidElementsAliasesTest do
|> Temple.Generator.to_eex() |> Temple.Generator.to_eex()
assert result |> :erlang.iolist_to_binary() == assert result |> :erlang.iolist_to_binary() ==
~s|<div class="foo" id="<%= var %>">\n<select>\n<option>\nfoo\n\n</option>\n</select>\n</div>| ~s|<div class="foo"<%= {:safe, Temple.Parser.Utils.build_attr("id", var)} %>>\n<select>\n<option>\nfoo\n\n</option>\n</select>\n</div>|
end end
end end
end end

View file

@ -67,7 +67,7 @@ defmodule Temple.Parser.TempleNamespaceNonvoidTest do
|> Temple.Generator.to_eex() |> Temple.Generator.to_eex()
assert result |> :erlang.iolist_to_binary() == assert result |> :erlang.iolist_to_binary() ==
~s|<div class="foo" id="<%= var %>">\nfoo\n\n</div>| ~s|<div class="foo"<%= {:safe, Temple.Parser.Utils.build_attr("id", var)} %>>\nfoo\n\n</div>|
end end
end end
end end

View file

@ -8,17 +8,19 @@ defmodule Temple.Parser.UtilsTest do
attrs_map = %{ attrs_map = %{
class: "text-red", class: "text-red",
id: "form1", id: "form1",
disabled: false,
inner_block: %{} inner_block: %{}
} }
attrs_kw = [ attrs_kw = [
class: "text-red", class: "text-red",
id: "form1", id: "form1",
disabled: true,
inner_block: %{} inner_block: %{}
] ]
assert {:safe, ~s| class="text-red" id="form1"|} == Utils.runtime_attrs(attrs_map) assert {:safe, ~s| class="text-red" id="form1"|} == Utils.runtime_attrs(attrs_map)
assert {:safe, ~s| class="text-red" id="form1"|} == Utils.runtime_attrs(attrs_kw) assert {:safe, ~s| class="text-red" id="form1" disabled|} == Utils.runtime_attrs(attrs_kw)
end end
end end
end end

View file

@ -89,7 +89,8 @@ defmodule TempleTest do
div class: foo <> " bar" div class: foo <> " bar"
end end
assert result == ~s{<div class="<%= foo <> " bar" %>"></div>} assert result ==
~s|<div<%= {:safe, Temple.Parser.Utils.build_attr("class", foo <> " bar")} %>></div>|
end end
test "renders an attribute on a div passed as a variable as eex" do test "renders an attribute on a div passed as a variable as eex" do
@ -101,7 +102,7 @@ defmodule TempleTest do
end end
assert result == assert result ==
~s{<div class="<%= Enum.map([:one, :two], fn x -> x end) %>"><div class="hi"></div></div>} ~s|<div<%= {:safe, Temple.Parser.Utils.build_attr("class", Enum.map([:one, :two], fn x -> x end))} %>><div class="hi"></div></div>|
end end
test "renders a for comprehension as eex" do test "renders a for comprehension as eex" do
@ -335,4 +336,20 @@ defmodule TempleTest do
assert evaluate_template(result, assigns) == assert evaluate_template(result, assigns) ==
~s{<div>foo</div><hr><div>foo</div><hr class="foofoo"><div>bar</div><hr class="foofoo"><div>bar</div>} ~s{<div>foo</div><hr><div>foo</div><hr class="foofoo"><div>bar</div><hr class="foofoo"><div>bar</div>}
end end
test "boolean attributes" do
assigns = %{is_true: true, is_false: false}
result =
temple do
input type: "text", disabled: true
input type: "text", disabled: false
input type: "text", disabled: @is_true
input type: "text", disabled: @is_false
end
assert evaluate_template(result, assigns) ==
~s{<input type="text" disabled>\n<input type="text">\n<input type="text" disabled>\n<input type="text">}
end
end end