Repository: mhanberg/temple
Pull request #188 (2022-11-03 15:33:30)

Dynamic boolean attributes
#187



Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 85827d2..3f579fe 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,12 @@
 
 ## Main
 
+### Breaking Changes
+
+- Rendering slots is now done by passing the assign with the slot name to the `slot` keyword instead of name as an atom. If this slot has multiple definitions, you can loop through them and render each one individually, or render them all at once. Please see the migration guide for more information.
+- The `:default` slot has been renamed to `:inner_block`. This is to be easily compatible with HEEx/Surface. Please see the migration guide for more information.
+- Capturing the data being passed into a slot is now defined using the `:let` attribute. Please see the migration guide for more information.
+
 ### Enhancements
 
 - Temple components are now compatible with HEEx/Surface components! Some small tweaks to the component implementation has made this possible. Please see the guides for more information.
@@ -9,11 +15,9 @@
 - You can now pass arbitrary data to slots, and it does not need to be a map or a keyword list. I don't think this is a breaking change, but please submit an issue if you notice it is.
 - Slot attributes. You can now pass data into a slot from the definition site and use it at the call site (inside the component).
 
-### Breaking Changes
+### Fixes
 
-- Rendering slots is now done by passing the assign with the slot name to the `slot` keyword instead of name as an atom. If this slot has multiple definitions, you can loop through them and render each one individually, or render them all at once. Please see the migration guide for more information.
-- The `:default` slot has been renamed to `:inner_block`. This is to be easily compatible with HEEx/Surface. Please see the migration guide for more information.
-- Capturing the data being passed into a slot is now defined using the `:let` attribute. Please see the migration guide for more information.
+- Attributes with runtime values that evaluate to true or false will be rendered correctly as boolean attributes.
 
 ### 0.10.0
 
diff --git a/guides/your-first-template.md b/guides/your-first-template.md
index e2bd592..035d035 100644
--- a/guides/your-first-template.md
+++ b/guides/your-first-template.md
@@ -125,8 +125,8 @@ Attributes are declared as a keyword list.
 
 - Keys with underscores are converted to the kebab syntax.
 - Values can be Elixir expressions.
-- Values that are compile time `true` will be emitted as a boolean attribute. `disabled` and `checked` are examples of boolean attributes.
-- Values that are compile time `false` will not be emitted into the document at all.
+- Values that evaluate to `true` will be emitted as a boolean attribute. `disabled` and `checked` are examples of boolean attributes.
+- Values that evaluate `false` will not be emitted into the document at all.
 - The class attribute has a special "object syntax" that allows you to specify classes as a keyword list, only emitting classes that evaluate to true into the final class.
 
 Let's look at an example.
diff --git a/lib/temple/ast/utils.ex b/lib/temple/ast/utils.ex
index a1513d2..fad2f14 100644
--- a/lib/temple/ast/utils.ex
+++ b/lib/temple/ast/utils.ex
@@ -49,7 +49,16 @@ defmodule Temple.Ast.Utils do
   end
 
   def build_attr(name, {_, _, _} = value) do
-    [{:text, ~s' #{name}="'}, {:expr, value}, {:text, ~s'"'}]
+    expr =
+      quote do
+        case unquote(value) do
+          true -> " " <> unquote(name)
+          false -> ""
+          _ -> ~s' #{unquote(name)}="#{unquote(value)}"'
+        end
+      end
+
+    [{:expr, expr}]
   end
 
   def build_attr("class", classes) when is_list(classes) do


Test patch:
diff --git a/test/parser/utils_test.exs b/test/parser/utils_test.exs
index f03b374..31d6343 100644
--- a/test/parser/utils_test.exs
+++ b/test/parser/utils_test.exs
@@ -19,20 +19,19 @@ defmodule Temple.Ast.UtilsTest do
 
     test "returns a list of text and expr nodes for attributes with runtime values" do
       class_ast = quote(do: @class)
-      id_ast = quote(do: @id)
-      attrs = [class: class_ast, id: id_ast, disabled: false, checked: true]
+      attrs = [class: class_ast, id: "foo"]
 
-      actual = Utils.compile_attrs(attrs)
+      assert [{:expr, actual}, {:text, ~s' id="foo"'}] = Utils.compile_attrs(attrs)
 
-      assert [
-               {:text, ~s' class="'},
-               {:expr, class_ast},
-               {:text, ~s'"'},
-               {:text, ~s' id="'},
-               {:expr, id_ast},
-               {:text, ~s'"'},
-               {:text, ~s' checked'}
-             ] == actual
+      assert Macro.to_string(
+               quote do
+                 case @class do
+                   true -> " " <> "class"
+                   false -> ""
+                   _ -> ~s' #{"class"}="#{@class}"'
+                 end
+               end
+             ) == Macro.to_string(actual)
     end
 
     test "returns a list of text and expr nodes for the class object syntax" do
diff --git a/test/temple/renderer_test.exs b/test/temple/renderer_test.exs
index 74a87ff..4e9cfe6 100644
--- a/test/temple/renderer_test.exs
+++ b/test/temple/renderer_test.exs
@@ -510,6 +510,23 @@ defmodule Temple.RendererTest do
       assert expected == result
     end
 
+    test "runtime boolean attributes emit the right values" do
+      truthy = true
+      falsey = false
+
+      result =
+        Renderer.compile do
+          input type: "text", disabled: falsey, checked: truthy, placeholder: "Enter some text..."
+        end
+
+      # html
+      expected = """
+      <input type="text" checked placeholder="Enter some text...">
+      """
+
+      assert expected == result
+    end
+
     test "multiple slots" do
       assigns = %{}