emit live view compatible component and slot markup

- requires the development branch of live_view currently, if you are
  going to be using live view

ci

Docs

Raise minimum elixir version to 1.9

There is some bug in EEx that was fixed in 1.9 and I can't be bothered
to make it backwards compatible with the bug.

ugh

Remove commented out line
This commit is contained in:
Mitchell Hanberg 2021-05-12 21:40:12 -04:00
parent 73b6973a74
commit 5150a93e38
37 changed files with 306 additions and 211 deletions

View file

@ -11,7 +11,7 @@ jobs:
strategy:
matrix:
otp: [21.x]
otp: [23.x]
elixir: [1.9.x, 1.11.x]
steps:
@ -20,12 +20,15 @@ jobs:
with:
otp-version: ${{matrix.otp}}
elixir-version: ${{matrix.elixir}}
- uses: actions/cache@v1
id: cache
- uses: actions/cache@v2
with:
path: deps
key: ${{ runner.os }}-test-${{ matrix.elixir }}-${{ hashFiles(format('{0}{1}', github.workspace, '/mix.lock')) }}
restore-keys:
path: |
deps
_build
key: ${{ runner.os }}-mix-${{ hashFiles('**/mix.lock') }}
restore-keys: |
${{ runner.os }}-mix-
- name: Install Dependencies
if: steps.cache.outputs.cache-hit != 'true'
run: mix deps.get
@ -42,7 +45,7 @@ jobs:
strategy:
matrix:
otp: [21.x, 22.x]
otp: [23.x]
elixir: [1.9.x, 1.11.x]
services:
@ -55,7 +58,6 @@ jobs:
ports: ['5432:5432']
options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5
steps:
- uses: actions/checkout@v2
- uses: erlef/setup-beam@v1
@ -63,11 +65,14 @@ jobs:
otp-version: ${{matrix.otp}}
elixir-version: ${{matrix.elixir}}
- uses: actions/cache@v1
id: cache
- uses: actions/cache@v2
with:
path: deps
key: ${{ runner.os }}-integration-test-${{ matrix.elixir }}-${{ hashFiles(format('{0}{1}', github.workspace, '/integration_test/temple_demo/mix.lock')) }}
path: |
deps
_build
key: ${{ runner.os }}-mix-${{ hashFiles('**/mix.lock') }}
restore-keys: |
${{ runner.os }}-mix-
- name: Install Dependencies
if: steps.cache.outputs.cache-hit != 'true'
@ -86,19 +91,20 @@ jobs:
formatter:
runs-on: ubuntu-latest
name: Formatter (1.11.x/21.x)
name: Formatter (1.11.x.x/23.x)
steps:
- uses: actions/checkout@v2
- uses: erlef/setup-beam@v1
with:
otp-version: 21.x
otp-version: 23.x
elixir-version: 1.11.x
- uses: actions/cache@v1
id: cache
- uses: actions/cache@v2
with:
path: deps
key: ${{ runner.os }}-${{ matrix.elixir }}-${{ hashFiles(format('{0}{1}', github.workspace, '/mix.lock')) }}
path: |
deps
_build
key: ${{ runner.os }}-mix-${{ hashFiles('**/mix.lock') }}
restore-keys: |
${{ runner.os }}-mix-

View file

@ -17,7 +17,8 @@ Add `temple` to your list of dependencies in `mix.exs`:
def deps do
[
{:temple, "~> 0.6.0-rc.0"},
{:phoenix, ">= 1.5.0"} # requires at least Phoenix v1.5.0
{:phoenix, ">= 1.5.0"}, # requires at least Phoenix v1.5.0
{:phoenix_live_ivew, github: "phoenixframework/phoenix_live_ivew"} # currently requires an unreleased version of phoenix_live_ivew if you are using live view
]
end
```
@ -35,6 +36,7 @@ end
Currently Temple has the following things on which it won't compromise.
- Will only work with valid Elixir syntax.
- Should always work with normal EEx, as well as Phoenix and Phoenix LiveView.
## Usage
@ -74,17 +76,27 @@ end
### Components
Temple components are mostly a little syntax sugar over Phoenix's `render/3` and `render_layout/4` functions.
Temple components provide an ergonomic API for creating flexible and reusable views. Unlike normal partials, Temple components can take slots, which are similar [Vue](https://v3.vuejs.org/guide/component-slots.html#named-slots).
For example, if I were to define a `Flex` component, I would create the following module.
For example, if I were to define a `Card` component, I would create the following module.
```elixir
defmodule MyAppWeb.Components.Flex do
use Temple.Component
defmodule MyAppWeb.Components.Card do
import Temple.Component
render do
div class: "flex #\{@class}" do
@inner_content
section do
div do
slot :header
end
div do
slot :default
end
div do
slot :footer
end
end
end
end
@ -94,22 +106,30 @@ And we could use the component like so
```elixir
# lib/my_app_web/views/page_view.ex
alias MyAppWeb.Components.Flex
alias MyAppWeb.Components.Card
# lib/my_app_web/templates/page/index.html.exs
c Flex, class: "justify-between items-center", id: "arnold" do
div do: "Hi"
div do: "I'm"
div do: "Arnold"
div do: "Schwarzenegger"
c Card do
slot :header do
@user.full_name
end
@user.bio
slot :footer do
a href: "https://twitter.com/#{@user.twitter}" do
"@#{@user.twitter}"
end
a href: "https://github.com/#{@user.github}" do
"@#{@user.github}"
end
end
end
```
Please see the [discussion thread](https://github.com/mhanberg/temple/discussions/104) to share ideas and ask questions about the Component API 😄.
### Phoenix templates
Add the template engine to your Phoenix configuration.
To use temple as a Phoenix Template engine, you'll need to configure the right file extensions with the right Temple engine.
```elixir
# config.exs

View file

@ -0,0 +1 @@
elixir 1.8.2

View file

@ -31,6 +31,7 @@ config :logger, :console,
config :phoenix, :json_library, Jason
config :temple,
mode: :normal,
aliases: [
label: :_label,
link: :_link,

View file

@ -36,6 +36,7 @@ defmodule TempleDemoWeb do
# Import convenience functions from controllers
import Phoenix.Controller, only: [get_flash: 1, get_flash: 2, view_module: 1]
import Temple.Component
alias TempleDemoWeb.Component.Outer
alias TempleDemoWeb.Component.Flash
alias TempleDemoWeb.Component.Form

View file

@ -1,9 +1,9 @@
defmodule TempleDemoWeb.Component.Flash do
use Temple.Component
import Temple.Component
render do
div class: "alert alert-#{@type}", style: "border: solid 5px pink" do
@inner_content
slot :default
end
end
end

View file

@ -1,12 +1,12 @@
defmodule TempleDemoWeb.Component.Form do
use Temple.Component
import Temple.Component
render do
f = Phoenix.HTML.Form.form_for(@changeset, @action)
f
slot(:f, f: f)
slot :f, f: f
"</form>"
end

View file

@ -1,9 +1,9 @@
defmodule TempleDemoWeb.Component.Inner do
use Temple.Component
import Temple.Component
render do
div id: "inner", outer_id: @outer_id do
@inner_content
slot :default
end
end
end

View file

@ -1,10 +1,10 @@
defmodule TempleDemoWeb.Component.Outer do
use Temple.Component
import Temple.Component
alias TempleDemoWeb.Component.Inner
render do
c Inner, outer_id: "from-outer" do
@inner_content
slot :default
end
end
end

View file

@ -29,8 +29,11 @@ html lang: "en" do
end
main role: "main", class: "container" do
p class: "alert alert-info", role: "alert", compact: true, do: get_flash(@conn, :info)
p class: "alert alert-danger", role: "alert", compact: true, do: get_flash(@conn, :error)
for {type, message} <- get_flash(@conn) do
p class: "alert alert-#{type}", role: "alert" do
message
end
end
@inner_content
end

View file

@ -1,13 +1,12 @@
defmodule TempleDemoWeb.PostView do
use TempleDemoWeb, :view
import Temple.Component, only: [defcomp: 2]
def thing(), do: "foobar"
defcomp Headers do
thead id: PostView.thing() do
tr do
@inner_content
slot :default
end
end
end

View file

@ -1,37 +1,38 @@
%{
"certifi": {:hex, :certifi, "2.5.2", "b7cfeae9d2ed395695dd8201c57a2d019c0c43ecaf8b8bcb9320b40d6662f340", [:rebar3], [{:parse_trans, "~>3.3", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm", "3b3b5f36493004ac3455966991eaf6e768ce9884693d9968055aeeeb1e575040"},
"connection": {:hex, :connection, "1.1.0", "ff2a49c4b75b6fb3e674bfc5536451607270aac754ffd1bdfe175abe4a6d7a68", [:mix], [], "hexpm", "722c1eb0a418fbe91ba7bd59a47e28008a189d47e37e0e7bb85585a016b2869c"},
"cowboy": {:hex, :cowboy, "2.7.0", "91ed100138a764355f43316b1d23d7ff6bdb0de4ea618cb5d8677c93a7a2f115", [:rebar3], [{:cowlib, "~> 2.8.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.7.1", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "04fd8c6a39edc6aaa9c26123009200fc61f92a3a94f3178c527b70b767c6e605"},
"cowlib": {:hex, :cowlib, "2.8.0", "fd0ff1787db84ac415b8211573e9a30a3ebe71b5cbff7f720089972b2319c8a4", [:rebar3], [], "hexpm", "79f954a7021b302186a950a32869dbc185523d99d3e44ce430cd1f3289f41ed4"},
"cowboy": {:hex, :cowboy, "2.8.0", "f3dc62e35797ecd9ac1b50db74611193c29815401e53bac9a5c0577bd7bc667d", [:rebar3], [{:cowlib, "~> 2.9.1", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.7.1", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "4643e4fba74ac96d4d152c75803de6fad0b3fa5df354c71afdd6cbeeb15fac8a"},
"cowboy_telemetry": {:hex, :cowboy_telemetry, "0.3.1", "ebd1a1d7aff97f27c66654e78ece187abdc646992714164380d8a041eda16754", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "3a6efd3366130eab84ca372cbd4a7d3c3a97bdfcfb4911233b035d117063f0af"},
"cowlib": {:hex, :cowlib, "2.9.1", "61a6c7c50cf07fdd24b2f45b89500bb93b6686579b069a89f88cb211e1125c78", [:rebar3], [], "hexpm", "e4175dc240a70d996156160891e1c62238ede1729e45740bdd38064dad476170"},
"db_connection": {:hex, :db_connection, "2.3.1", "4c9f3ed1ef37471cbdd2762d6655be11e38193904d9c5c1c9389f1b891a3088e", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}], "hexpm", "abaab61780dde30301d840417890bd9f74131041afd02174cf4e10635b3a63f5"},
"decimal": {:hex, :decimal, "2.0.0", "a78296e617b0f5dd4c6caf57c714431347912ffb1d0842e998e9792b5642d697", [:mix], [], "hexpm", "34666e9c55dea81013e77d9d87370fe6cb6291d1ef32f46a1600230b1d44f577"},
"ecto": {:hex, :ecto, "3.5.5", "48219a991bb86daba6e38a1e64f8cea540cded58950ff38fbc8163e062281a07", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "98dd0e5e1de7f45beca6130d13116eae675db59adfa055fb79612406acf6f6f1"},
"ecto_sql": {:hex, :ecto_sql, "3.5.3", "1964df0305538364b97cc4661a2bd2b6c89d803e66e5655e4e55ff1571943efd", [:mix], [{:db_connection, "~> 2.2", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.5.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.3.0 or ~> 0.4.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.15.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "d2f53592432ce17d3978feb8f43e8dc0705e288b0890caf06d449785f018061c"},
"file_system": {:hex, :file_system, "0.2.8", "f632bd287927a1eed2b718f22af727c5aeaccc9a98d8c2bd7bff709e851dc986", [:mix], [], "hexpm", "97a3b6f8d63ef53bd0113070102db2ce05352ecf0d25390eb8d747c2bde98bca"},
"file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"},
"gettext": {:hex, :gettext, "0.18.0", "406d6b9e0e3278162c2ae1de0a60270452c553536772167e2d701f028116f870", [:mix], [], "hexpm", "c3f850be6367ebe1a08616c2158affe4a23231c70391050bf359d5f92f66a571"},
"hackney": {:hex, :hackney, "1.16.0", "5096ac8e823e3a441477b2d187e30dd3fff1a82991a806b2003845ce72ce2d84", [:rebar3], [{:certifi, "2.5.2", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "6.0.1", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.3.0", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.6", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm", "3bf0bebbd5d3092a3543b783bf065165fa5d3ad4b899b836810e513064134e18"},
"httpoison": {:hex, :httpoison, "1.7.0", "abba7d086233c2d8574726227b6c2c4f6e53c4deae7fe5f6de531162ce9929a0", [:mix], [{:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "975cc87c845a103d3d1ea1ccfd68a2700c211a434d8428b10c323dc95dc5b980"},
"idna": {:hex, :idna, "6.0.1", "1d038fb2e7668ce41fbf681d2c45902e52b3cb9e9c77b55334353b222c2ee50c", [:rebar3], [{:unicode_util_compat, "0.5.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "a02c8a1c4fd601215bb0b0324c8a6986749f807ce35f25449ec9e69758708122"},
"jason": {:hex, :jason, "1.2.2", "ba43e3f2709fd1aa1dce90aaabfd039d000469c05c56f0b8e31978e03fa39052", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "18a228f5f0058ee183f29f9eae0805c6e59d61c3b006760668d8d18ff0d12179"},
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"},
"mime": {:hex, :mime, "1.5.0", "203ef35ef3389aae6d361918bf3f952fa17a09e8e43b5aa592b93eba05d0fb8d", [:mix], [], "hexpm", "55a94c0f552249fc1a3dd9cd2d3ab9de9d3c89b559c2bd01121f824834f24746"},
"mime": {:hex, :mime, "1.6.0", "dabde576a497cef4bbdd60aceee8160e02a6c89250d6c0b29e56c0dfb00db3d2", [:mix], [], "hexpm", "31a1a8613f8321143dde1dafc36006a17d28d02bdfecb9e95a880fa7aabd19a7"},
"mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"},
"parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm", "17ef63abde837ad30680ea7f857dd9e7ced9476cdd7b0394432af4bfc241b960"},
"phoenix": {:hex, :phoenix, "1.5.2", "7ba05d6cb0024eefd3cb08b176e6f041a9edff094912de2f6a49e3ba67140fb3", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_html, "~> 2.13", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.10", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.2", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.1.2 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "3047022367d415a935dceda1176e67d9c7f2d41cd52a0419b53cfca66fc4c64e"},
"phoenix": {:hex, :phoenix, "1.5.8", "71cfa7a9bb9a37af4df98939790642f210e35f696b935ca6d9d9c55a884621a4", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_html, "~> 2.13", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.10", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.2", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.1.2 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "35ded0a32f4836168c7ab6c33b88822eccd201bcd9492125a9bea4c54332d955"},
"phoenix_ecto": {:hex, :phoenix_ecto, "4.2.1", "13f124cf0a3ce0f1948cf24654c7b9f2347169ff75c1123f44674afee6af3b03", [:mix], [{:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 2.15", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "478a1bae899cac0a6e02be1deec7e2944b7754c04e7d4107fc5a517f877743c0"},
"phoenix_html": {:hex, :phoenix_html, "2.14.3", "51f720d0d543e4e157ff06b65de38e13303d5778a7919bcc696599e5934271b8", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "efd697a7fff35a13eeeb6b43db884705cba353a1a41d127d118fda5f90c8e80f"},
"phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.2.4", "3080e8a89bab3ec08d4dd9a6858dfa24af9334464aae78c83e58a2db37c6f983", [:mix], [{:phoenix_html, "~> 2.14.1 or ~> 2.15", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.12.0 or ~> 0.13.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.4.0 or ~> 0.5.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "1c89595ef60f1b76ac07705e73f001823af451491792a4b0d5b2b2a3789b0a00"},
"phoenix_live_reload": {:hex, :phoenix_live_reload, "1.2.2", "38d94c30df5e2ef11000697a4fbe2b38d0fbf79239d492ff1be87bbc33bc3a84", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm", "a3dec3d28ddb5476c96a7c8a38ea8437923408bc88da43e5c45d97037b396280"},
"phoenix_live_view": {:hex, :phoenix_live_view, "0.13.0", "dec006b3da4ab164283d5bebe960724eb4d19cd0ed553e05fb99b260233e200f", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.4.17 or ~> 1.5.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14", [hex: :phoenix_html, repo: "hexpm", optional: false]}], "hexpm", "bd6f13b666fa9bfeca88b013db20414c693d5a5e6d19b1fc2602c282d626ed8e"},
"phoenix_pubsub": {:hex, :phoenix_pubsub, "2.0.0", "a1ae76717bb168cdeb10ec9d92d1480fec99e3080f011402c0a2d68d47395ffb", [:mix], [], "hexpm", "c52d948c4f261577b9c6fa804be91884b381a7f8f18450c5045975435350f771"},
"plug": {:hex, :plug, "1.11.0", "f17217525597628298998bc3baed9f8ea1fa3f1160aa9871aee6df47a6e4d38e", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "2d9c633f0499f9dc5c2fd069161af4e2e7756890b81adcbb2ceaa074e8308876"},
"plug_cowboy": {:hex, :plug_cowboy, "2.2.1", "fcf58aa33227a4322a050e4783ee99c63c031a2e7f9a2eb7340d55505e17f30f", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "3b43de24460d87c0971887286e7a20d40462e48eb7235954681a20cee25ddeb6"},
"plug_crypto": {:hex, :plug_crypto, "1.2.0", "1cb20793aa63a6c619dd18bb33d7a3aa94818e5fd39ad357051a67f26dfa2df6", [:mix], [], "hexpm", "a48b538ae8bf381ffac344520755f3007cc10bd8e90b240af98ea29b69683fc2"},
"plug": {:hex, :plug, "1.11.1", "f2992bac66fdae679453c9e86134a4201f6f43a687d8ff1cd1b2862d53c80259", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "23524e4fefbb587c11f0833b3910bfb414bf2e2534d61928e920f54e3a1b881f"},
"plug_cowboy": {:hex, :plug_cowboy, "2.5.0", "51c998f788c4e68fc9f947a5eba8c215fbb1d63a520f7604134cab0270ea6513", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "5b2c8925a5e2587446f33810a58c01e66b3c345652eeec809b76ba007acde71a"},
"plug_crypto": {:hex, :plug_crypto, "1.2.2", "05654514ac717ff3a1843204b424477d9e60c143406aa94daf2274fdd280794d", [:mix], [], "hexpm", "87631c7ad914a5a445f0a3809f99b079113ae4ed4b867348dd9eec288cecb6db"},
"poolboy": {:hex, :poolboy, "1.5.2", "392b007a1693a64540cead79830443abf5762f5d30cf50bc95cb2c1aaafa006b", [:rebar3], [], "hexpm", "dad79704ce5440f3d5a3681c8590b9dc25d1a561e8f5a9c995281012860901e3"},
"postgrex": {:hex, :postgrex, "0.15.7", "724410acd48abac529d0faa6c2a379fb8ae2088e31247687b16cacc0e0883372", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "88310c010ff047cecd73d5ceca1d99205e4b1ab1b9abfdab7e00f5c9d20ef8f9"},
"ranch": {:hex, :ranch, "1.7.1", "6b1fab51b49196860b733a49c07604465a47bdb78aa10c1c16a3d199f7f8c881", [:rebar3], [], "hexpm", "451d8527787df716d99dc36162fca05934915db0b6141bbdac2ea8d3c7afc7d7"},
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.6", "cf344f5692c82d2cd7554f5ec8fd961548d4fd09e7d22f5b62482e5aeaebd4b0", [:make, :mix, :rebar3], [], "hexpm", "bdb0d2471f453c88ff3908e7686f86f9be327d065cc1ec16fa4540197ea04680"},
"telemetry": {:hex, :telemetry, "0.4.2", "2808c992455e08d6177322f14d3bdb6b625fbcfd233a73505870d8738a2f4599", [:rebar3], [], "hexpm", "2d1419bd9dda6a206d7b5852179511722e2b18812310d304620c7bd92a13fcef"},
"telemetry": {:hex, :telemetry, "0.4.3", "a06428a514bdbc63293cd9a6263aad00ddeb66f608163bdec7c8995784080818", [:rebar3], [], "hexpm", "eb72b8365ffda5bed68a620d1da88525e326cb82a75ee61354fc24b844768041"},
"telemetry_metrics": {:hex, :telemetry_metrics, "0.5.0", "1b796e74add83abf844e808564275dfb342bcc930b04c7577ab780e262b0d998", [:mix], [{:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "31225e6ce7a37a421a0a96ec55244386aec1c190b22578bd245188a4a33298fd"},
"telemetry_poller": {:hex, :telemetry_poller, "0.5.0", "4770888ef85599ead39c7f51d6b4b62306e602d96c69b2625d54dea3d9a5204b", [:rebar3], [{:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "69e4e8e65b0ae077c9e14cd5f42c7cc486de0e07ac6e3409e6f0e52699a7872c"},
"tesla": {:hex, :tesla, "1.3.3", "26ae98627af5c406584aa6755ab5fc96315d70d69a24dd7f8369cfcb75094a45", [:mix], [{:castore, "~> 0.1", [hex: :castore, repo: "hexpm", optional: true]}, {:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:gun, "~> 1.3", [hex: :gun, repo: "hexpm", optional: true]}, {:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "~> 4.4.0", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.0", [hex: :mint, repo: "hexpm", optional: true]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "2648f1c276102f9250299e0b7b57f3071c67827349d9173f34c281756a1b124c"},

View file

@ -26,7 +26,7 @@
background-color: #fcf8e3;
border-color: #faebcc;
}
.alert-danger {
.alert-error {
color: #a94442;
background-color: #f2dede;
border-color: #ebccd1;

View file

@ -4,7 +4,7 @@ defmodule Temple do
@moduledoc """
Temple syntax is available inside the `temple`, and is compiled into EEx at build time.
### Usage
## Usage
```elixir
temple do
@ -55,20 +55,31 @@ defmodule Temple do
end
```
### Reserved keywords
## Configuration
You can pass a keyword list to an element as element attributes, but there is currently a reserved keyword.
### Mode
### Configuration
There are two "modes", `:normal` (the default) and `:live_view`.
#### Aliases
In `:live_view` mode, Temple emits markup that uses functions provided by Phoenix LiveView in order to be fully "diff trackable".
```elixir
config :temple, :mode, :normal # default
# or
config :temple, :mode, :live_view
```
### Aliases
You can add an alias for an element if there is a namespace collision with a function. If you are using `Phoenix.HTML`, there will be namespace collisions with the `<link>` and `<label>` elements.
```elixir
config :temple, :aliases,
label: :_label,
link: :_link
link: :_link,
select: :_select
temple do
_label do
@ -99,7 +110,7 @@ defmodule Temple do
@doc """
Context for temple markup.
Returns an EEx string.
Returns an EEx string that can be passed into an EEx template engine.
## Usage
@ -141,6 +152,8 @@ defmodule Temple do
@doc """
Compiles temple markup into a quoted expression using the given EEx Engine.
Returns the same output that Phoenix templates output into the `render/1` function of their view modules.
## Usage
```elixir
@ -152,7 +165,6 @@ defmodule Temple do
end
end
# Returns the same output that Phoenix templates output into the `render/1` function of their view modules.
```
"""
defmacro compile(engine, [do: block] = _block) do

View file

@ -1,4 +1,6 @@
defmodule Temple.Ast do
@moduledoc false
def new(module, opts \\ []) do
struct(module, opts)
end

View file

@ -6,11 +6,11 @@ defmodule Temple.Component do
Since component modules are view modules, the assigns you pass to the component are accessible via the `@` macro and the `assigns` variable.
## Usage
## Components
```elixir
defmodule MyAppWeb.Components.Flash do
use Temple.Component
import Temple.Component
def border_class(:info), do: "border-blue-500"
def border_class(:warning), do: "border-yellow-500"
@ -19,7 +19,7 @@ defmodule Temple.Component do
render do
div class: "border rounded p-2 #\{assigns[:class]} #\{border_class(@message_type)}" do
@inner_content
slot :default
end
end
end
@ -59,26 +59,52 @@ defmodule Temple.Component do
## Slots
Components can use slots, which are named placeholders that can be called like functions to be able to pass them data. This is very useful
when a component needs to pass data from the inside of the component back to the caller, like when rendering a form in LiveView.
Components can use slots, which are named placeholders for markup that can be passed to the component by the caller.
The definition of a slot happens at the call site of the component and you utilize that slot from inside of the component module.
Slots are invoked by using the `slot` keyword, followed by the name of the slot and any assigns you'd like to pass into the slot.
`slot` is a _**compile time keyword**_, not a function or a macro, so you won't see it in the generated documention.
```elixir
defmodule Flex do
import Temple.Component
render do
div class: "flex #\{@class}" do
slot :default
end
end
end
```
You can also use "named slots", which allow for data to be passed back into them. This is very useful
when a component needs to pass data from the inside of the component back to the caller, like when rendering a form in LiveView.
```elixir
defmodule Form do
use Temple.Component
import Temple.Component
render do
form = form_for(@changeset, @action, assigns)
form
slot(:f, form: form)
slot :f, form: form
"</form>"
end
end
```
By default, the body of a component fills the `:default` slot.
Named slots can be defined by invoking the `slot` keyword with the name of the slot and a do block.
You can also pattern match on any assigns that are being passed into the slot as if you were defining an anonymous function.
`slot` is a _**compile time keyword**_, not a function or a macro, so you won't see it in the generated documention.
```elixir
# lib/my_app_web/templates/post/new.html.lexs
c Form, changeset: @changeset,
@ -98,9 +124,46 @@ defmodule Temple.Component do
```
"""
defmacro __using__(_) do
@doc false
defmacro __component__(module, assigns \\ [], block \\ []) do
{inner_block, assigns} =
case {block, assigns} do
{[do: do_block], _} -> {rewrite_do(do_block), assigns}
{_, [do: do_block]} -> {rewrite_do(do_block), []}
{_, _} -> {nil, assigns}
end
if is_nil(inner_block) do
quote do
import Temple.Component, only: [render: 1]
Phoenix.View.render(unquote(module), :self, unquote(assigns))
end
else
quote do
Phoenix.View.render(
unquote(module),
:self,
Map.put(Map.new(unquote(assigns)), :inner_block, unquote(inner_block))
)
end
end
end
@doc false
defmacro __render_block__(inner_block, argument \\ []) do
quote do
unquote(inner_block).(unquote(argument))
end
end
defp rewrite_do([{:->, meta, _} | _] = do_block) do
{:fn, meta, do_block}
end
defp rewrite_do(do_block) do
quote do
fn _ ->
unquote(do_block)
end
end
end
@ -111,7 +174,7 @@ defmodule Temple.Component do
```elixir
defmodule MyAppWeb.Components.Flash do
use Temple.Component
import Temple.Component
def border_class(:info), do: "border-blue-500"
def border_class(:warning), do: "border-yellow-500"
@ -120,7 +183,7 @@ defmodule Temple.Component do
render do
div class: "border rounded p-2 #\{assigns[:class]} #\{border_class(@message_type)}" do
@inner_content
slot :default
end
end
end
@ -129,14 +192,20 @@ defmodule Temple.Component do
"""
defmacro render(block) do
quote do
def render(assigns), do: render(:self, assigns)
def render(var!(assigns)) do
require Temple
_ = var!(assigns)
Temple.compile(unquote(Temple.Component.__engine__()), unquote(block))
end
def render(:self, var!(assigns)) do
require Temple
_ = var!(assigns)
Temple.compile(unquote(Temple.Component.engine()), unquote(block))
Temple.compile(unquote(Temple.Component.__engine__()), unquote(block))
end
end
end
@ -163,7 +232,7 @@ defmodule Temple.Component do
button id: SomeView.foobar(), # `MyAppWeb.SomeView` is aliased for you.
class: "text-sm px-3 py-2 rounded #\{assigns[:extra_classes]}",
type: "submit" do
@inner_content
slot :default
end
end
end
@ -177,7 +246,7 @@ defmodule Temple.Component do
defmacro defcomp(module, [do: block] = _block) do
quote location: :keep do
defmodule unquote(module) do
use Temple.Component
import Temple.Component
alias unquote(__CALLER__.module)
render do
@ -188,7 +257,7 @@ defmodule Temple.Component do
end
@doc false
def engine() do
def __engine__() do
cond do
Code.ensure_loaded?(Phoenix.LiveView.Engine) ->
Phoenix.LiveView.Engine

21
lib/temple/config.ex Normal file
View file

@ -0,0 +1,21 @@
defmodule Temple.Config do
@moduledoc false
def mode do
case Application.get_env(:temple, :mode, :normal) do
:normal ->
%{
component_function: "Temple.Component.__component__",
render_block_function: "Temple.Component.__render_block__",
renderer: fn module -> Macro.to_string(module) end
}
:live_view ->
%{
component_function: "component",
render_block_function: "render_block",
renderer: fn module -> "&" <> Macro.to_string(module) <> ".render/1" end
}
end
end
end

View file

@ -1,3 +1,5 @@
defprotocol Temple.Generator do
@moduledoc false
def to_eex(ast)
end

View file

@ -25,10 +25,14 @@ defmodule Temple.Parser.Components do
children,
%{},
fn
{:slot, _, [name | args]}, named_slots ->
{:slot, _, [name | args]} = node, named_slots ->
{assigns, slot} = split_assigns_and_children(args, Macro.escape(%{}))
if is_nil(slot) do
{node, named_slots}
else
{nil, Map.put(named_slots, name, %{assigns: assigns, slot: slot})}
end
node, named_slots ->
{node, named_slots}
@ -80,58 +84,42 @@ defmodule Temple.Parser.Components do
end
defimpl Temple.Generator do
def to_eex(%{module: module, assigns: assigns, children: [], slots: slots}) do
[
"<%= Phoenix.View.render",
" ",
Macro.to_string(module),
", ",
":self,",
" ",
"[{:__temple_slots__, %{",
for slot <- slots do
[
to_string(slot.name),
": ",
"fn #{Macro.to_string(slot.assigns)} -> %>",
for(child <- slot.content, do: Temple.Generator.to_eex(child)),
"<% end, "
]
end,
"}} | ",
Macro.to_string(assigns),
"]",
" ",
"%>"
]
end
def to_eex(%{module: module, assigns: assigns, children: children, slots: slots}) do
component_function = Temple.Config.mode().component_function
renderer = Temple.Config.mode().renderer.(module)
[
"<%= Phoenix.View.render_layout ",
Macro.to_string(module),
"<%= #{component_function} ",
renderer,
", ",
":self,",
" ",
"[{:__temple_slots__, %{",
Macro.to_string(assigns),
if not Enum.empty?(children ++ slots) do
[
" do %>\n",
if not Enum.empty?(children) do
[
"<% {:default, _} -> %>\n",
for(child <- children, do: Temple.Generator.to_eex(child)),
"\n"
]
else
""
end,
for slot <- slots do
[
"<% {:",
to_string(slot.name),
": ",
"fn #{Macro.to_string(slot.assigns)} -> %>",
for(child <- slot.content, do: Temple.Generator.to_eex(child)),
"<% end, "
", ",
"#{Macro.to_string(slot.assigns)}} -> %>\n",
for(child <- slot.content, do: Temple.Generator.to_eex(child))
]
end,
"}} | ",
Macro.to_string(assigns),
"]",
" do %>",
"\n",
for(child <- children, do: Temple.Generator.to_eex(child)),
"\n",
"<% end %>"
]
else
" %>"
end
]
end
end
end

View file

@ -12,18 +12,29 @@ defmodule Temple.Parser.Slot do
def applicable?(_), do: false
@impl true
def run({:slot, _, [slot_name | [args]]}) do
def run({:slot, _, [slot_name | rest]}) do
args =
case rest do
[args] ->
args
_ ->
[]
end
Temple.Ast.new(__MODULE__, name: slot_name, args: args)
end
defimpl Temple.Generator do
def to_eex(%{name: name, args: args}) do
render_block_function = Temple.Config.mode().render_block_function
[
"<%= @__temple_slots__.",
"<%= #{render_block_function}(@inner_block, {:",
to_string(name),
".(",
", ",
Macro.to_string(quote(do: Enum.into(unquote(args), %{}))),
") %>"
"}) %>"
]
end
end

View file

@ -1,3 +1,5 @@
defmodule Temple.Parser.Slottable do
@moduledoc false
defstruct content: nil, assigns: Macro.escape(%{}), name: nil
end

View file

@ -34,7 +34,7 @@ defmodule Temple.Parser.Utils do
def runtime_attrs(attrs) do
{:safe,
for {name, value} <- attrs, name != :__temple_slots__, into: "" do
for {name, value} <- attrs, name not in [:inner_block, :inner_content], into: "" do
name = snake_to_kebab(name)
" " <> name <> "=\"" <> to_string(value) <> "\""

View file

@ -9,7 +9,7 @@ defmodule Temple.MixProject do
version: "0.6.0-rc.0",
package: package(),
elixirc_paths: elixirc_paths(Mix.env()),
elixir: "~> 1.7",
elixir: "~> 1.9",
start_permanent: Mix.env() == :prod,
deps: deps(),
source_url: "https://github.com/mhanberg/temple",

View file

@ -3,8 +3,6 @@ defmodule Temple.ComponentTest do
use Temple
use Temple.Support.Utils
# `Phoenix.View.render_layout/4` is a phoenix function used for rendering partials that contain inner_content.
# These are usually layouts, but components that contain children are basically the same thing
test "renders components using Phoenix.View.render_layout" do
result =
temple do
@ -19,9 +17,6 @@ defmodule Temple.ComponentTest do
end
end
assert result ==
~s|<div class="font-bold">Hello, world</div><%= Phoenix.View.render_layout Temple.Components.Component, :self, [{:__temple_slots__, %{}} \| []] do %><aside class="foobar">I'm a component!</aside><% end %>|
assert evaluate_template(result) ==
~s{<div class="font-bold">Hello, world</div><div><aside class="foobar">I'm a component!</aside></div>}
end
@ -38,31 +33,10 @@ defmodule Temple.ComponentTest do
end
end
assert result ==
~s|<div class="font-bold">Hello, world</div><%= Phoenix.View.render_layout Temple.Components.Component2, :self, [{:__temple_slots__, %{}} \| [class: "bg-red"]] do %>I'm a component!<% end %>|
assert evaluate_template(result) ==
~s{<div class="font-bold">Hello, world</div><div class="bg-red">I'm a component!</div>}
end
test "function components can accept local assigns that are variables" do
result =
temple do
div class: "font-bold" do
"Hello, world"
end
class = "bg-red"
c Temple.Components.Component2, class: class do
"I'm a component!"
end
end
assert result ==
~s|<div class="font-bold">Hello, world</div><% class = "bg-red" %><%= Phoenix.View.render_layout Temple.Components.Component2, :self, [{:__temple_slots__, %{}} \| [class: class]] do %>I'm a component!<% end %>|
end
test "function components can use other components" do
result =
temple do
@ -75,27 +49,12 @@ defmodule Temple.ComponentTest do
end
end
assert result ==
~s|<%= Phoenix.View.render_layout Temple.Components.Outer, :self, [{:__temple_slots__, %{}} \| []] do %>outer!\n<% end %><%= Phoenix.View.render_layout Temple.Components.Inner, :self, [{:__temple_slots__, %{}} \| [outer_id: "set by root inner"]] do %>inner!\n<% end %>|
assert evaluate_template(result) == ~s"""
<div id="inner" outer-id="from-outer">outer!</div>
<div id="inner" outer-id="set by root inner">inner!</div>
"""
end
test "normal functions with blocks should be treated like if expressions" do
result =
temple do
leenk to: "/route", class: "foo" do
div class: "hi"
end
end
assert result ==
~s{<%= leenk(to: "/route", class: "foo") do %><div class="hi"></div><% end %>}
end
test "components can use functions from their modules" do
result =
temple do
@ -104,9 +63,6 @@ defmodule Temple.ComponentTest do
end
end
assert result ==
~s|<%= Phoenix.View.render_layout Temple.Components.WithFuncs, :self, [{:__temple_slots__, %{}} \| [foo: :bar]] do %>doo doo<% end %>|
assert evaluate_template(result) == ~s{<div class="barbarbar">doo doo</div>}
end
@ -116,9 +72,6 @@ defmodule Temple.ComponentTest do
c Temple.Components.VoidComponent, foo: :bar
end
assert result ==
~s|<%= Phoenix.View.render Temple.Components.VoidComponent, :self, [{:__temple_slots__, %{}} \| [foo: :bar]] %>|
assert evaluate_template(result) == ~s{<div class="void!!">bar</div>}
end
@ -140,9 +93,6 @@ defmodule Temple.ComponentTest do
end
end
assert result ==
~s|<%= Phoenix.View.render_layout Temple.Components.WithSlot, :self, [{:__temple_slots__, %{header: fn %{value: val} -> %>\n<div>\n<%= "the value is \#{val}" %>\n</div><% end, }} \| []] do %>\n<button class="btn" phx-click="toggle">\n<%= @name %>\n\n</button>\n<% end %>|
assert evaluate_template(result, assigns) ==
~s{<div><div>the value is Header</div><div class="wrapped"><button class="btn" phx-click="toggle">bob</button></div></div>}
end

View file

@ -148,7 +148,7 @@ defmodule Temple.Parser.ComponentsTest do
|> Temple.Generator.to_eex()
assert result |> :erlang.iolist_to_binary() ==
~s|<%= Phoenix.View.render_layout SomeModule, :self, [{:__temple_slots__, %{}} \| [foo: :bar]] do %>\nI'm a component!\n<% end %>|
~s|<%= Temple.Component.__component__ SomeModule, [foo: :bar] do %>\n<% {:default, _} -> %>\nI'm a component!\n<% end %>|
end
test "emits eex for void component with slots" do
@ -169,7 +169,7 @@ defmodule Temple.Parser.ComponentsTest do
|> Temple.Generator.to_eex()
assert result |> :erlang.iolist_to_binary() ==
~s|<%= Phoenix.View.render SomeModule, :self, [{:__temple_slots__, %{foo: fn %{form: form} -> %><div>\nin the slot\n\n</div><% end, }} \| [foo: :bar]] %>|
~s|<%= Temple.Component.__component__ SomeModule, [foo: :bar] do %>\n<% {:foo, %{form: form}} -> %>\n<div>\nin the slot\n\n</div>\n<% end %>|
end
test "emits eex for nonvoid component with slots" do
@ -194,7 +194,7 @@ defmodule Temple.Parser.ComponentsTest do
|> Temple.Generator.to_eex()
assert result |> :erlang.iolist_to_binary() ==
~s|<%= Phoenix.View.render_layout SomeModule, :self, [{:__temple_slots__, %{foo: fn %{form: form} -> %><div>\nin the slot\n\n</div><% end, }} \| [foo: :bar]] do %>\n<div>\ninner content</div>\n<% end %>|
~s|<%= Temple.Component.__component__ SomeModule, [foo: :bar] do %>\n<% {:default, _} -> %>\n<div>\ninner content\n\n</div>\n<% {:foo, %{form: form}} -> %><div>\nin the slot\n\n</div><% end %>|
end
test "emits eex for void component" do
@ -209,7 +209,7 @@ defmodule Temple.Parser.ComponentsTest do
|> Temple.Generator.to_eex()
assert result |> :erlang.iolist_to_binary() ==
~s|<%= Phoenix.View.render SomeModule, :self, [{:__temple_slots__, %{}} \| [foo: :bar]] %>|
~s|<%= Temple.Component.__component__ SomeModule, [foo: :bar] %>|
end
end
end

View file

@ -42,7 +42,7 @@ defmodule Temple.Parser.SlotTest do
|> Temple.Generator.to_eex()
assert result |> :erlang.iolist_to_binary() ==
~s|<%= @__temple_slots__.header.(Enum.into([value: Form.form_for(changeset, action)], %{})) %>|
~s|<%= Temple.Component.__render_block__(@inner_block, {:header, Enum.into([value: Form.form_for(changeset, action)], %{})}) %>|
end
end
end

View file

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

View file

@ -1,9 +1,9 @@
defmodule Temple.Components.Component do
use Temple.Component
import Temple.Component
render do
div do
@inner_content
slot :default
end
end
end

View file

@ -1,9 +1,9 @@
defmodule Temple.Components.Component2 do
use Temple.Component
import Temple.Component
render do
div class: @class do
@inner_content
slot :default
end
end
end

View file

@ -1,9 +1,9 @@
defmodule Temple.Components.HasTemple do
use Temple.Component
import Temple.Component
render do
div class: @temple[:class] do
@inner_content
slot :default
end
end
end

View file

@ -1,9 +1,9 @@
defmodule Temple.Components.Inner do
use Temple.Component
import Temple.Component
render do
div id: "inner", outer_id: @outer_id do
@inner_content
slot :default
end
end
end

View file

@ -1,9 +1,9 @@
defmodule Temple.Components.Outer do
use Temple.Component
import Temple.Component
render do
c Temple.Components.Inner, outer_id: "from-outer" do
@inner_content
slot :default, %{}
end
end
end

View file

@ -1,9 +1,9 @@
defmodule Temple.Components.Section do
use Temple.Component
import Temple.Component
render do
section class: "foo!" do
@inner_content
slot :default
end
end
end

View file

@ -1,5 +1,5 @@
defmodule Temple.Components.VoidComponent do
use Temple.Component
import Temple.Component
render do
div class: "void!!" do

View file

@ -1,5 +1,5 @@
defmodule Temple.Components.WithFuncs do
use Temple.Component
import Temple.Component
def get_class(:bar) do
"barbarbar"
@ -11,7 +11,7 @@ defmodule Temple.Components.WithFuncs do
render do
div class: get_class(@foo) do
@inner_content
slot :default
end
end
end

View file

@ -1,12 +1,12 @@
defmodule Temple.Components.WithSlot do
use Temple.Component
import Temple.Component
render do
div do
slot :header, value: "Header"
div class: "wrapped" do
@inner_content
slot :default
end
end
end

View file

@ -20,10 +20,16 @@ defmodule Temple.Support.Utils do
Kernel.=~(a, b)
end
def env do
require Temple.Component
__ENV__
end
def evaluate_template(template, assigns \\ %{}) do
template
|> EEx.compile_string(engine: Phoenix.HTML.Engine)
|> Code.eval_quoted(assigns: assigns)
|> Code.eval_quoted([assigns: assigns], env())
|> elem(0)
|> Phoenix.HTML.safe_to_string()
end