Repository: mhanberg/temple
Pull request #140 (2021-06-27 04:14:11)

Class object syntax
Allows for conditionally setting classes on an element.

This is similar to Vue.js's [Class Bindings - Object Syntax](https://v3.vuejs.org/guide/class-and-style.html#object-syntax)

```elixir
div class: ["text-red-500": false, "text-green-500": true] do
  @user.name
end
```

results in

```html
<div class="text-green-500">
  Homer Simpson
</div>
```


mhanberg: Still needs docs

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 07c85b3..9b654b1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,7 @@
 ### Enhancements
 
 - [breaking] Attributes who values are boolean expressions will be emitted as boolean attributes.
+- Class "object" syntax. Conditionally add classes by passing a keyword list to the `class` attribute.
 
 ## 0.6.2
 
diff --git a/lib/temple.ex b/lib/temple.ex
index aaff4ea..9d6a253 100644
--- a/lib/temple.ex
+++ b/lib/temple.ex
@@ -27,6 +27,14 @@ defmodule Temple do
     input type: "text", disabled: false
     # <input type="text">
 
+    # The class attribute also can take a keyword list of classes to conditionally render, based on the boolean result of the value.
+
+    div class: ["text-red-500": false, "text-green-500": true ] do
+      "Alert!"
+    end
+
+    # <div class="text-green-500">Alert!</div>
+
     # if and unless expressions can be used to conditionally render content
     if 5 > 0 do
       p do
diff --git a/lib/temple/parser/utils.ex b/lib/temple/parser/utils.ex
index cbe2f49..9d8837e 100644
--- a/lib/temple/parser/utils.ex
+++ b/lib/temple/parser/utils.ex
@@ -18,12 +18,13 @@ defmodule Temple.Parser.Utils do
       for {name, value} <- attrs, into: "" do
         name = snake_to_kebab(name)
 
-        cond do
-          (not is_binary(value) && Macro.quoted_literal?(value)) || match?({_, _, _}, value) ->
-            ~s|<%= {:safe, Temple.Parser.Utils.build_attr("#{name}", #{Macro.to_string(value)})} %>|
-
+        with false <- not is_binary(value) && Macro.quoted_literal?(value),
+             false <- match?({_, _, _}, value),
+             false <- is_list(value) do
+          " " <> name <> "=\"" <> to_string(value) <> "\""
+        else
           true ->
-            " " <> name <> "=\"" <> to_string(value) <> "\""
+            ~s|<%= {:safe, Temple.Parser.Utils.build_attr("#{name}", #{Macro.to_string(value)})} %>|
         end
       end
     else
@@ -49,6 +50,12 @@ defmodule Temple.Parser.Utils do
     ""
   end
 
+  def build_attr("class", classes) when is_list(classes) do
+    value = String.trim_leading(for {class, true} <- classes, into: "", do: " #{class}")
+
+    " class" <> "=\"" <> value <> "\""
+  end
+
   def build_attr(name, value) do
     " " <> name <> "=\"" <> to_string(value) <> "\""
   end


Test patch:
diff --git a/test/parser/utils_test.exs b/test/parser/utils_test.exs
index 1c0a844..7030e9b 100644
--- a/test/parser/utils_test.exs
+++ b/test/parser/utils_test.exs
@@ -22,5 +22,11 @@ defmodule Temple.Parser.UtilsTest do
       assert {:safe, ~s| class="text-red" id="form1"|} == Utils.runtime_attrs(attrs_map)
       assert {:safe, ~s| class="text-red" id="form1" disabled|} == Utils.runtime_attrs(attrs_kw)
     end
+
+    test "class accepts a keyword list which conditionally emits classes" do
+      attrs = [class: ["text-red": false, "text-blue": true], id: "form1"]
+
+      assert {:safe, ~s| class="text-blue" id="form1"|} == Utils.runtime_attrs(attrs)
+    end
   end
 end
diff --git a/test/temple_test.exs b/test/temple_test.exs
index bc7b3ee..4ca8cc9 100644
--- a/test/temple_test.exs
+++ b/test/temple_test.exs
@@ -352,4 +352,17 @@ defmodule TempleTest do
     assert evaluate_template(result, assigns) ==
              ~s{<input type="text" disabled>\n<input type="text">\n<input type="text" disabled>\n<input type="text">}
   end
+
+  test "class attribute can be pass a keyword list of class/boolean pairs" do
+    assigns = %{is_true: true, is_false: false}
+
+    result =
+      temple do
+        div class: ["text-blue": @is_false, "text-red": true] do
+          "foo"
+        end
+      end
+
+    assert evaluate_template(result, assigns) == ~s{<div class="text-red">\nfoo\n\n</div>}
+  end
 end