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:
parent
73b6973a74
commit
5150a93e38
37 changed files with 306 additions and 211 deletions
42
.github/workflows/ci.yml
vendored
42
.github/workflows/ci.yml
vendored
|
@ -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-
|
||||
|
||||
|
|
52
README.md
52
README.md
|
@ -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
|
||||
|
|
1
integration_test/temple_demo/.tool-versions
Normal file
1
integration_test/temple_demo/.tool-versions
Normal file
|
@ -0,0 +1 @@
|
|||
elixir 1.8.2
|
|
@ -31,6 +31,7 @@ config :logger, :console,
|
|||
config :phoenix, :json_library, Jason
|
||||
|
||||
config :temple,
|
||||
mode: :normal,
|
||||
aliases: [
|
||||
label: :_label,
|
||||
link: :_link,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"},
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
background-color: #fcf8e3;
|
||||
border-color: #faebcc;
|
||||
}
|
||||
.alert-danger {
|
||||
.alert-error {
|
||||
color: #a94442;
|
||||
background-color: #f2dede;
|
||||
border-color: #ebccd1;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
defmodule Temple.Ast do
|
||||
@moduledoc false
|
||||
|
||||
def new(module, opts \\ []) do
|
||||
struct(module, opts)
|
||||
end
|
||||
|
|
|
@ -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
|
||||
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
|
||||
import Temple.Component, only: [render: 1]
|
||||
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
21
lib/temple/config.ex
Normal 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
|
|
@ -1,3 +1,5 @@
|
|||
defprotocol Temple.Generator do
|
||||
@moduledoc false
|
||||
|
||||
def to_eex(ast)
|
||||
end
|
||||
|
|
|
@ -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(%{}))
|
||||
|
||||
{nil, Map.put(named_slots, name, %{assigns: assigns, slot: slot})}
|
||||
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,57 +84,41 @@ 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__, %{",
|
||||
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),
|
||||
"]",
|
||||
" do %>",
|
||||
"\n",
|
||||
for(child <- children, do: Temple.Generator.to_eex(child)),
|
||||
"\n",
|
||||
"<% end %>"
|
||||
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),
|
||||
", ",
|
||||
"#{Macro.to_string(slot.assigns)}} -> %>\n",
|
||||
for(child <- slot.content, do: Temple.Generator.to_eex(child))
|
||||
]
|
||||
end,
|
||||
"<% end %>"
|
||||
]
|
||||
else
|
||||
" %>"
|
||||
end
|
||||
]
|
||||
end
|
||||
end
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
defmodule Temple.Parser.Slottable do
|
||||
@moduledoc false
|
||||
|
||||
defstruct content: nil, assigns: Macro.escape(%{}), name: nil
|
||||
end
|
||||
|
|
|
@ -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) <> "\""
|
||||
|
|
2
mix.exs
2
mix.exs
|
@ -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",
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
defmodule Temple.Components.VoidComponent do
|
||||
use Temple.Component
|
||||
import Temple.Component
|
||||
|
||||
render do
|
||||
div class: "void!!" do
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Reference in a new issue