Repository: mhanberg/temple
Pull request #139 (2021-06-26 23:34:57)

Properly emit boolean attributes
Attributes that result in boolean values will be emitted as a boolean attribute. Examples of boolean attributes are `disabled` and `checked`.

```elixir
input type: "text", disabled: true
```
⬇️
```html
<input type="text" disabled>
```

and

```elixir
input type: "text", disabled: false
```
⬇️
```html
<input type="text">
```



Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 882f037..07c85b3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,7 +2,9 @@
 
 ## Main
 
-Nothing yet
+### Enhancements
+
+- [breaking] Attributes who values are boolean expressions will be emitted as boolean attributes.
 
 ## 0.6.2
 
diff --git a/README.md b/README.md
index c59c1f8..8d61ee4 100644
--- a/README.md
+++ b/README.md
@@ -22,15 +22,6 @@ def deps do
   ]
 end
 ```
-
-or
-
-```elixir
-def deps do
-  [{:temple, github: "mhanberg/temple"}]
-end
-```
-
 ## Goals
 
 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
   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/dev.exs
@@ -156,6 +148,7 @@ config :your_app, YourAppWeb.Endpoint,
 ```elixir
 # app.html.exs
 
+"<!DOCTYPE html>"
 html lang: "en" do
   head do
     meta charset: "utf-8"
@@ -163,7 +156,7 @@ html lang: "en" do
     meta name: "viewport", content: "width=device-width, initial-scale=1.0"
     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
 
   body do
diff --git a/lib/temple.ex b/lib/temple.ex
index 8b6d5dc..aaff4ea 100644
--- a/lib/temple.ex
+++ b/lib/temple.ex
@@ -19,6 +19,14 @@ defmodule Temple do
       id
     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 5 > 0 do
       p do
@@ -61,7 +69,9 @@ defmodule Temple do
 
   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
   config :temple, :mode, :normal # default
diff --git a/lib/temple/parser/utils.ex b/lib/temple/parser/utils.ex
index 134fc61..cbe2f49 100644
--- a/lib/temple/parser/utils.ex
+++ b/lib/temple/parser/utils.ex
@@ -18,11 +18,11 @@ defmodule Temple.Parser.Utils do
       for {name, value} <- attrs, into: "" do
         name = snake_to_kebab(name)
 
-        case value do
-          {_, _, _} = macro ->
-            " " <> name <> "=\"<%= " <> Macro.to_string(macro) <> " %>\""
+        cond do
+          (not is_binary(value) && Macro.quoted_literal?(value)) || match?({_, _, _}, value) ->
+            ~s|<%= {:safe, Temple.Parser.Utils.build_attr("#{name}", #{Macro.to_string(value)})} %>|
 
-          value ->
+          true ->
             " " <> name <> "=\"" <> to_string(value) <> "\""
         end
       end
@@ -37,10 +37,22 @@ defmodule Temple.Parser.Utils do
      for {name, value} <- attrs, name not in [:inner_block, :inner_content], into: "" do
        name = snake_to_kebab(name)
 
-       " " <> name <> "=\"" <> to_string(value) <> "\""
+       build_attr(name, value)
      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),
     do: {[], []}
 


Test patch:
diff --git a/integration_test/temple_demo/lib/temple_demo_web/templates/post/form.html.exs b/integration_test/temple_demo/lib/temple_demo_web/templates/post/form.html.exs
index 190875c..aaad6e6 100644
--- a/integration_test/temple_demo/lib/temple_demo_web/templates/post/form.html.exs
+++ b/integration_test/temple_demo/lib/temple_demo_web/templates/post/form.html.exs
@@ -22,6 +22,8 @@ c Form, changeset: @changeset, action: @action do
     text_input f, :author
     error_tag(f, :author)
 
+    input type: "text", disabled: true, id: "disabled-input"
+
     div do
       submit "Save"
     end
diff --git a/integration_test/temple_demo/test/temple_demo_web/features/temple_feature_test.exs b/integration_test/temple_demo/test/temple_demo_web/features/temple_feature_test.exs
index 67be2dc..2a42384 100644
--- a/integration_test/temple_demo/test/temple_demo_web/features/temple_feature_test.exs
+++ b/integration_test/temple_demo/test/temple_demo_web/features/temple_feature_test.exs
@@ -37,6 +37,7 @@ defmodule TempleDemoWeb.TempleFeatureTest do
       s |> click(Query.option("21"))
     end)
     |> fill_in(Query.text_field("Author"), with: "Mitchelob Ultra")
+    |> assert_has(Query.css("#disabled-input[disabled]"))
     |> click(Query.button("Save"))
     |> assert_text("Post created successfully.")
   end
diff --git a/test/parser/nonvoid_elements_aliases_test.exs b/test/parser/nonvoid_elements_aliases_test.exs
index f713d44..da0f236 100644
--- a/test/parser/nonvoid_elements_aliases_test.exs
+++ b/test/parser/nonvoid_elements_aliases_test.exs
@@ -96,7 +96,7 @@ defmodule Temple.Parser.NonvoidElementsAliasesTest do
         |> Temple.Generator.to_eex()
 
       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
diff --git a/test/parser/temple_namespace_nonvoid_test.exs b/test/parser/temple_namespace_nonvoid_test.exs
index 4602654..de508ae 100644
--- a/test/parser/temple_namespace_nonvoid_test.exs
+++ b/test/parser/temple_namespace_nonvoid_test.exs
@@ -67,7 +67,7 @@ defmodule Temple.Parser.TempleNamespaceNonvoidTest do
         |> Temple.Generator.to_eex()
 
       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
diff --git a/test/parser/utils_test.exs b/test/parser/utils_test.exs
index afbc348..1c0a844 100644
--- a/test/parser/utils_test.exs
+++ b/test/parser/utils_test.exs
@@ -8,17 +8,19 @@ defmodule Temple.Parser.UtilsTest do
       attrs_map = %{
         class: "text-red",
         id: "form1",
+        disabled: false,
         inner_block: %{}
       }
 
       attrs_kw = [
         class: "text-red",
         id: "form1",
+        disabled: true,
         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_kw)
+      assert {:safe, ~s| class="text-red" id="form1" disabled|} == Utils.runtime_attrs(attrs_kw)
     end
   end
 end
diff --git a/test/temple_test.exs b/test/temple_test.exs
index fe9339c..bc7b3ee 100644
--- a/test/temple_test.exs
+++ b/test/temple_test.exs
@@ -89,7 +89,8 @@ defmodule TempleTest do
         div class: foo <> " bar"
       end
 
-    assert result == ~s{<div class="<%= foo <> " bar" %>"></div>}
+    assert result ==
+             ~s|<div<%= {:safe, Temple.Parser.Utils.build_attr("class", foo <> " bar")} %>></div>|
   end
 
   test "renders an attribute on a div passed as a variable as eex" do
@@ -101,7 +102,7 @@ defmodule TempleTest do
       end
 
     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
 
   test "renders a for comprehension as eex" do
@@ -335,4 +336,20 @@ defmodule TempleTest do
     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>}
   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