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

View file

@ -17,7 +17,8 @@ Add `temple` to your list of dependencies in `mix.exs`:
def deps do def deps do
[ [
{:temple, "~> 0.6.0-rc.0"}, {: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 end
``` ```
@ -35,6 +36,7 @@ end
Currently Temple has the following things on which it won't compromise. Currently Temple has the following things on which it won't compromise.
- Will only work with valid Elixir syntax. - Will only work with valid Elixir syntax.
- Should always work with normal EEx, as well as Phoenix and Phoenix LiveView.
## Usage ## Usage
@ -74,17 +76,27 @@ end
### Components ### 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 ```elixir
defmodule MyAppWeb.Components.Flex do defmodule MyAppWeb.Components.Card do
use Temple.Component import Temple.Component
render do render do
div class: "flex #\{@class}" do section do
@inner_content div do
slot :header
end
div do
slot :default
end
div do
slot :footer
end
end end
end end
end end
@ -94,22 +106,30 @@ And we could use the component like so
```elixir ```elixir
# lib/my_app_web/views/page_view.ex # 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 # lib/my_app_web/templates/page/index.html.exs
c Flex, class: "justify-between items-center", id: "arnold" do c Card do
div do: "Hi" slot :header do
div do: "I'm" @user.full_name
div do: "Arnold" end
div do: "Schwarzenegger"
@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 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 ### 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 ```elixir
# config.exs # 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 :phoenix, :json_library, Jason
config :temple, config :temple,
mode: :normal,
aliases: [ aliases: [
label: :_label, label: :_label,
link: :_link, link: :_link,

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,13 +1,12 @@
defmodule TempleDemoWeb.PostView do defmodule TempleDemoWeb.PostView do
use TempleDemoWeb, :view use TempleDemoWeb, :view
import Temple.Component, only: [defcomp: 2]
def thing(), do: "foobar" def thing(), do: "foobar"
defcomp Headers do defcomp Headers do
thead id: PostView.thing() do thead id: PostView.thing() do
tr do tr do
@inner_content slot :default
end end
end 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"}, "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"}, "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"}, "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"},
"cowlib": {:hex, :cowlib, "2.8.0", "fd0ff1787db84ac415b8211573e9a30a3ebe71b5cbff7f720089972b2319c8a4", [:rebar3], [], "hexpm", "79f954a7021b302186a950a32869dbc185523d99d3e44ce430cd1f3289f41ed4"}, "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"}, "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"}, "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": {: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"}, "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"}, "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"}, "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"}, "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"}, "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"}, "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"}, "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"}, "mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"},
"parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm", "17ef63abde837ad30680ea7f857dd9e7ced9476cdd7b0394432af4bfc241b960"}, "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_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_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_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_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_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"}, "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": {: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.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_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.0", "1cb20793aa63a6c619dd18bb33d7a3aa94818e5fd39ad357051a67f26dfa2df6", [:mix], [], "hexpm", "a48b538ae8bf381ffac344520755f3007cc10bd8e90b240af98ea29b69683fc2"}, "plug_crypto": {:hex, :plug_crypto, "1.2.2", "05654514ac717ff3a1843204b424477d9e60c143406aa94daf2274fdd280794d", [:mix], [], "hexpm", "87631c7ad914a5a445f0a3809f99b079113ae4ed4b867348dd9eec288cecb6db"},
"poolboy": {:hex, :poolboy, "1.5.2", "392b007a1693a64540cead79830443abf5762f5d30cf50bc95cb2c1aaafa006b", [:rebar3], [], "hexpm", "dad79704ce5440f3d5a3681c8590b9dc25d1a561e8f5a9c995281012860901e3"}, "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"}, "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"}, "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"}, "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_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"}, "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"}, "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; background-color: #fcf8e3;
border-color: #faebcc; border-color: #faebcc;
} }
.alert-danger { .alert-error {
color: #a94442; color: #a94442;
background-color: #f2dede; background-color: #f2dede;
border-color: #ebccd1; border-color: #ebccd1;

View file

@ -4,7 +4,7 @@ defmodule Temple do
@moduledoc """ @moduledoc """
Temple syntax is available inside the `temple`, and is compiled into EEx at build time. Temple syntax is available inside the `temple`, and is compiled into EEx at build time.
### Usage ## Usage
```elixir ```elixir
temple do temple do
@ -55,20 +55,31 @@ defmodule Temple do
end 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. 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 ```elixir
config :temple, :aliases, config :temple, :aliases,
label: :_label, label: :_label,
link: :_link link: :_link,
select: :_select
temple do temple do
_label do _label do
@ -99,7 +110,7 @@ defmodule Temple do
@doc """ @doc """
Context for temple markup. Context for temple markup.
Returns an EEx string. Returns an EEx string that can be passed into an EEx template engine.
## Usage ## Usage
@ -141,6 +152,8 @@ defmodule Temple do
@doc """ @doc """
Compiles temple markup into a quoted expression using the given EEx Engine. 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 ## Usage
```elixir ```elixir
@ -152,7 +165,6 @@ defmodule Temple do
end end
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 defmacro compile(engine, [do: block] = _block) do

View file

@ -1,4 +1,6 @@
defmodule Temple.Ast do defmodule Temple.Ast do
@moduledoc false
def new(module, opts \\ []) do def new(module, opts \\ []) do
struct(module, opts) struct(module, opts)
end 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. 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 ```elixir
defmodule MyAppWeb.Components.Flash do defmodule MyAppWeb.Components.Flash do
use Temple.Component import Temple.Component
def border_class(:info), do: "border-blue-500" def border_class(:info), do: "border-blue-500"
def border_class(:warning), do: "border-yellow-500" def border_class(:warning), do: "border-yellow-500"
@ -19,7 +19,7 @@ defmodule Temple.Component do
render do render do
div class: "border rounded p-2 #\{assigns[:class]} #\{border_class(@message_type)}" do div class: "border rounded p-2 #\{assigns[:class]} #\{border_class(@message_type)}" do
@inner_content slot :default
end end
end end
end end
@ -59,26 +59,52 @@ defmodule Temple.Component do
## Slots ## 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 Components can use slots, which are named placeholders for markup that can be passed to the component by the caller.
when a component needs to pass data from the inside of the component back to the caller, like when rendering a form in LiveView.
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 ```elixir
defmodule Form do defmodule Form do
use Temple.Component import Temple.Component
render do render do
form = form_for(@changeset, @action, assigns) form = form_for(@changeset, @action, assigns)
form form
slot(:f, form: form) slot :f, form: form
"</form>" "</form>"
end end
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 # lib/my_app_web/templates/post/new.html.lexs
c Form, changeset: @changeset, 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 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
end end
@ -111,7 +174,7 @@ defmodule Temple.Component do
```elixir ```elixir
defmodule MyAppWeb.Components.Flash do defmodule MyAppWeb.Components.Flash do
use Temple.Component import Temple.Component
def border_class(:info), do: "border-blue-500" def border_class(:info), do: "border-blue-500"
def border_class(:warning), do: "border-yellow-500" def border_class(:warning), do: "border-yellow-500"
@ -120,7 +183,7 @@ defmodule Temple.Component do
render do render do
div class: "border rounded p-2 #\{assigns[:class]} #\{border_class(@message_type)}" do div class: "border rounded p-2 #\{assigns[:class]} #\{border_class(@message_type)}" do
@inner_content slot :default
end end
end end
end end
@ -129,14 +192,20 @@ defmodule Temple.Component do
""" """
defmacro render(block) do defmacro render(block) do
quote 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 def render(:self, var!(assigns)) do
require Temple require Temple
_ = var!(assigns) _ = var!(assigns)
Temple.compile(unquote(Temple.Component.engine()), unquote(block)) Temple.compile(unquote(Temple.Component.__engine__()), unquote(block))
end end
end end
end end
@ -163,7 +232,7 @@ defmodule Temple.Component do
button id: SomeView.foobar(), # `MyAppWeb.SomeView` is aliased for you. button id: SomeView.foobar(), # `MyAppWeb.SomeView` is aliased for you.
class: "text-sm px-3 py-2 rounded #\{assigns[:extra_classes]}", class: "text-sm px-3 py-2 rounded #\{assigns[:extra_classes]}",
type: "submit" do type: "submit" do
@inner_content slot :default
end end
end end
end end
@ -177,7 +246,7 @@ defmodule Temple.Component do
defmacro defcomp(module, [do: block] = _block) do defmacro defcomp(module, [do: block] = _block) do
quote location: :keep do quote location: :keep do
defmodule unquote(module) do defmodule unquote(module) do
use Temple.Component import Temple.Component
alias unquote(__CALLER__.module) alias unquote(__CALLER__.module)
render do render do
@ -188,7 +257,7 @@ defmodule Temple.Component do
end end
@doc false @doc false
def engine() do def __engine__() do
cond do cond do
Code.ensure_loaded?(Phoenix.LiveView.Engine) -> Code.ensure_loaded?(Phoenix.LiveView.Engine) ->
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 defprotocol Temple.Generator do
@moduledoc false
def to_eex(ast) def to_eex(ast)
end end

View file

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

View file

@ -12,18 +12,29 @@ defmodule Temple.Parser.Slot do
def applicable?(_), do: false def applicable?(_), do: false
@impl true @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) Temple.Ast.new(__MODULE__, name: slot_name, args: args)
end end
defimpl Temple.Generator do defimpl Temple.Generator do
def to_eex(%{name: name, args: args}) 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), to_string(name),
".(", ", ",
Macro.to_string(quote(do: Enum.into(unquote(args), %{}))), Macro.to_string(quote(do: Enum.into(unquote(args), %{}))),
") %>" "}) %>"
] ]
end end
end end

View file

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

View file

@ -34,7 +34,7 @@ defmodule Temple.Parser.Utils do
def runtime_attrs(attrs) do def runtime_attrs(attrs) do
{:safe, {: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 = snake_to_kebab(name)
" " <> name <> "=\"" <> to_string(value) <> "\"" " " <> name <> "=\"" <> to_string(value) <> "\""

View file

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

View file

@ -3,8 +3,6 @@ defmodule Temple.ComponentTest do
use Temple use Temple
use Temple.Support.Utils 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 test "renders components using Phoenix.View.render_layout" do
result = result =
temple do temple do
@ -19,9 +17,6 @@ defmodule Temple.ComponentTest do
end end
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) == assert evaluate_template(result) ==
~s{<div class="font-bold">Hello, world</div><div><aside class="foobar">I'm a component!</aside></div>} ~s{<div class="font-bold">Hello, world</div><div><aside class="foobar">I'm a component!</aside></div>}
end end
@ -38,31 +33,10 @@ defmodule Temple.ComponentTest do
end end
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) == assert evaluate_template(result) ==
~s{<div class="font-bold">Hello, world</div><div class="bg-red">I'm a component!</div>} ~s{<div class="font-bold">Hello, world</div><div class="bg-red">I'm a component!</div>}
end 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 test "function components can use other components" do
result = result =
temple do temple do
@ -75,27 +49,12 @@ defmodule Temple.ComponentTest do
end end
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""" assert evaluate_template(result) == ~s"""
<div id="inner" outer-id="from-outer">outer!</div> <div id="inner" outer-id="from-outer">outer!</div>
<div id="inner" outer-id="set by root inner">inner!</div> <div id="inner" outer-id="set by root inner">inner!</div>
""" """
end 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 test "components can use functions from their modules" do
result = result =
temple do temple do
@ -104,9 +63,6 @@ defmodule Temple.ComponentTest do
end end
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>} assert evaluate_template(result) == ~s{<div class="barbarbar">doo doo</div>}
end end
@ -116,9 +72,6 @@ defmodule Temple.ComponentTest do
c Temple.Components.VoidComponent, foo: :bar c Temple.Components.VoidComponent, foo: :bar
end 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>} assert evaluate_template(result) == ~s{<div class="void!!">bar</div>}
end end
@ -140,9 +93,6 @@ defmodule Temple.ComponentTest do
end end
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) == 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>} ~s{<div><div>the value is Header</div><div class="wrapped"><button class="btn" phx-click="toggle">bob</button></div></div>}
end end

View file

@ -148,7 +148,7 @@ defmodule Temple.Parser.ComponentsTest do
|> Temple.Generator.to_eex() |> Temple.Generator.to_eex()
assert result |> :erlang.iolist_to_binary() == 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 end
test "emits eex for void component with slots" do test "emits eex for void component with slots" do
@ -169,7 +169,7 @@ defmodule Temple.Parser.ComponentsTest do
|> Temple.Generator.to_eex() |> Temple.Generator.to_eex()
assert result |> :erlang.iolist_to_binary() == 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 end
test "emits eex for nonvoid component with slots" do test "emits eex for nonvoid component with slots" do
@ -194,7 +194,7 @@ defmodule Temple.Parser.ComponentsTest do
|> Temple.Generator.to_eex() |> Temple.Generator.to_eex()
assert result |> :erlang.iolist_to_binary() == 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 end
test "emits eex for void component" do test "emits eex for void component" do
@ -209,7 +209,7 @@ defmodule Temple.Parser.ComponentsTest do
|> Temple.Generator.to_eex() |> Temple.Generator.to_eex()
assert result |> :erlang.iolist_to_binary() == 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 end
end end

View file

@ -42,7 +42,7 @@ defmodule Temple.Parser.SlotTest do
|> Temple.Generator.to_eex() |> Temple.Generator.to_eex()
assert result |> :erlang.iolist_to_binary() == 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 end
end end

View file

@ -8,13 +8,13 @@ defmodule Temple.Parser.UtilsTest do
attrs_map = %{ attrs_map = %{
class: "text-red", class: "text-red",
id: "form1", id: "form1",
__temple_slots__: %{} inner_block: %{}
} }
attrs_kw = [ attrs_kw = [
class: "text-red", class: "text-red",
id: "form1", id: "form1",
__temple_slots__: %{} 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)

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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