feat: New Component API
This commit is contained in:
parent
271567dc8f
commit
ced2f6ab66
|
@ -1,5 +1,5 @@
|
|||
locals_without_parens = ~w[
|
||||
temple
|
||||
temple c
|
||||
html head title style script
|
||||
noscript template
|
||||
body section nav article aside h1 h2 h3 h4 h5 h6
|
||||
|
|
53
CHANGELOG.md
53
CHANGELOG.md
|
@ -30,7 +30,58 @@ end
|
|||
|
||||
### Breaking
|
||||
|
||||
Components are now defined using modules. You can convert your existing components by configuring your component prefix and wrapping your current component files in the `Temple.Component` behaviour implementation.
|
||||
#### Components
|
||||
|
||||
Components are now a thin layer over template partials, compiling to calls to `render/3` and `render_layout/4` under the hood.
|
||||
|
||||
To upgrade your components the new syntax, you can copy your component markup and paste it into the `render/1` macro inside the component module and references to `@children` can be updated to `@inner_content`.
|
||||
|
||||
Components can are also referenced differently than before when using them. Before, one would simply call `flex` to render a component named `Flex`. Now, one must use the keyword `c` to render a component, passing the keyword the component module along with any assigns.
|
||||
|
||||
##### Before
|
||||
|
||||
```elixir
|
||||
# definition
|
||||
div class: "flex #{@class}" do
|
||||
@children
|
||||
end
|
||||
|
||||
# usage
|
||||
|
||||
flex class: "justify-between" do
|
||||
for item <- @items do
|
||||
div do
|
||||
item.name
|
||||
end
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
##### After
|
||||
|
||||
```elixir
|
||||
# definition
|
||||
defmodule MyAppWeb.Component.Flex do
|
||||
use Temple.Component
|
||||
|
||||
render do
|
||||
div class: "flex #{@class}" do
|
||||
@inner_content
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# usage
|
||||
alias MyApp.Component.Flex # probably located in my_app_web.ex
|
||||
|
||||
c Flex, class: "justify-between" do
|
||||
for item <- @items do
|
||||
div do
|
||||
item.name
|
||||
end
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
### Bugs
|
||||
|
||||
|
|
2
LICENSE
2
LICENSE
|
@ -1,6 +1,6 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2019 Mitchell Hanberg
|
||||
Copyright (c) 2021 Mitchell Hanberg
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
|
65
README.md
65
README.md
|
@ -6,7 +6,7 @@
|
|||
|
||||
> You are looking at the README for the master branch. The README for the latest stable release is located [here](https://github.com/mhanberg/temple/tree/v0.5.0).
|
||||
|
||||
Temple is a DSL for writing HTML using Elixir.
|
||||
Temple is a DSL for writing HTML and EEx using Elixir.
|
||||
|
||||
You're probably here because you want to use Temple to write Phoenix templates, which is why Temple includes a [Phoenix template engine](#phoenix-templates).
|
||||
|
||||
|
@ -16,7 +16,10 @@ Add `temple` to your list of dependencies in `mix.exs`:
|
|||
|
||||
```elixir
|
||||
def deps do
|
||||
[{:temple, "~> 0.6.0-alpha.4"}]
|
||||
[
|
||||
{:temple, "~> 0.6.0-alpha.4"},
|
||||
{:phoenix, ">= 1.5.0"} # requires at least Phoenix v1.5.0
|
||||
]
|
||||
end
|
||||
```
|
||||
|
||||
|
@ -30,9 +33,9 @@ end
|
|||
|
||||
## Usage
|
||||
|
||||
Using Temple is a as simple as using the DSL inside of an `temple/1` block. This returns an EEx string at compile time.
|
||||
Using Temple is as simple as using the DSL inside of an `temple/1` block. This returns an EEx string at compile time.
|
||||
|
||||
See the [documentation](https://hexdocs.pm/temple/Temple.Html.html) for more details.
|
||||
See the [documentation](https://hexdocs.pm/temple/Temple.html) for more details.
|
||||
|
||||
```elixir
|
||||
use Temple
|
||||
|
@ -66,33 +69,17 @@ end
|
|||
|
||||
### Components
|
||||
|
||||
To define a component, you can define a module that that starts with your defined components prefix. The last name in the module should be a came-cases version of the component name.
|
||||
Temple components are mostly a little syntax sugar over Phoenix's `render/3` and `render_layout/4` functions.
|
||||
|
||||
This module should implement the `Temple.Component` behaviour.
|
||||
|
||||
```elixir
|
||||
# config/config.exs
|
||||
|
||||
config :temple, :component_prefix, MyAppWeb.Components
|
||||
|
||||
# also set the path so recompiling will work in Phoenix projects
|
||||
config :temple, :components_path, "./lib/my_app_web/components"
|
||||
```
|
||||
|
||||
You can then use this component in any other temple template.
|
||||
|
||||
For example, if I were to define a `flex` component, I would create the following module.
|
||||
For example, if I were to define a `Flex` component, I would create the following module.
|
||||
|
||||
```elixir
|
||||
defmodule MyAppWeb.Components.Flex do
|
||||
@behaviour Temple.Component
|
||||
use Temple.Component
|
||||
|
||||
@impl Temple.Component
|
||||
def render do
|
||||
quote do
|
||||
div class: "flex #{@temple[:class]}", id: @id do
|
||||
@children
|
||||
end
|
||||
render do
|
||||
div class: "flex #\{@class}" do
|
||||
@inner_content
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -101,7 +88,9 @@ end
|
|||
And we could use the component like so
|
||||
|
||||
```elixir
|
||||
flex class: "justify-between items-center", id: "arnold" do
|
||||
alias MyAppWeb.Components.Flex
|
||||
|
||||
c Flex, class: "justify-between items-center", id: "arnold" do
|
||||
div do: "Hi"
|
||||
div do: "I'm"
|
||||
div do: "Arnold"
|
||||
|
@ -109,29 +98,9 @@ flex class: "justify-between items-center", id: "arnold" do
|
|||
end
|
||||
```
|
||||
|
||||
We've demonstrated several features to components in this example.
|
||||
|
||||
We can pass assigns to our component, and access them just like we would in a normal phoenix template. If they don't match up with any assigns we passed to our component, they will be rendered as-is, and will become a normal Phoenix assign.
|
||||
|
||||
You can also access a special `@temple` assign. This allows you do optionally pass an assign, and not have the `@my_assign` pass through. If you didn't pass it to your component, it will evaluate to nil.
|
||||
|
||||
The block passed to your component can be accessed as `@children`. This allows your components to wrap a body of markup from the call site.
|
||||
|
||||
In order for components to trigger a recompile when they are changed, you can call `use Temple.Recompiler` in your `lib/my_app_web.ex` file, in the `view`, `live_view`, and `live_component` functions
|
||||
|
||||
```elixir
|
||||
def view do
|
||||
quote do
|
||||
# ...
|
||||
use Temple.Recompiler
|
||||
# ...
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
### Phoenix templates
|
||||
|
||||
Add the templating engine to your Phoenix configuration.
|
||||
Add the template engine to your Phoenix configuration.
|
||||
|
||||
```elixir
|
||||
# config.exs
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
use Mix.Config
|
||||
|
||||
config :temple, :component_prefix, Temple.Components
|
||||
import_config "#{Mix.env()}.exs"
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
use Mix.Config
|
||||
|
||||
config :temple, components_path: "./test/support/components"
|
||||
# this is to make the warning go away,
|
||||
# Temple does not use a json_library
|
||||
config :phoenix, json_library: Temple
|
||||
|
|
|
@ -35,8 +35,7 @@ config :temple,
|
|||
label: :_label,
|
||||
link: :_link,
|
||||
textarea: :_textarea
|
||||
],
|
||||
component_prefix: TempleDemoWeb.Component
|
||||
]
|
||||
|
||||
# Import environment specific config. This must remain at the bottom
|
||||
# of this file so it overrides the configuration defined above.
|
||||
|
|
|
@ -52,7 +52,7 @@ config :temple_demo, TempleDemoWeb.Endpoint,
|
|||
patterns: [
|
||||
~r"priv/static/.*(js|css|png|jpeg|jpg|gif|svg)$",
|
||||
~r"priv/gettext/.*(po)$",
|
||||
~r"lib/temple_demo_web/(live|views)/.*(ex)$",
|
||||
~r"lib/temple_demo_web/(live|views|components)/.*(ex)$",
|
||||
~r"lib/temple_demo_web/templates/.*(eex|exs)$"
|
||||
]
|
||||
]
|
||||
|
|
|
@ -21,6 +21,10 @@ config :temple_demo, TempleDemoWeb.Endpoint,
|
|||
config :temple_demo, :sql_sandbox, true
|
||||
|
||||
config :wallaby,
|
||||
chromedriver: [
|
||||
# headless: false,
|
||||
binary: System.get_env("CHROME_BROWSER")
|
||||
],
|
||||
base_url: "http://localhost:4002",
|
||||
otp_app: :temple_demo,
|
||||
screenshot_on_failure: true
|
||||
|
|
|
@ -36,6 +36,9 @@ defmodule TempleDemoWeb do
|
|||
# Import convenience functions from controllers
|
||||
import Phoenix.Controller, only: [get_flash: 1, get_flash: 2, view_module: 1]
|
||||
|
||||
alias TempleDemoWeb.Component.Outer
|
||||
alias TempleDemoWeb.Component.Flash
|
||||
|
||||
# Include shared imports and aliases for views
|
||||
unquote(view_helpers())
|
||||
end
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
defmodule TempleDemoWeb.Component.Flash do
|
||||
use Temple.Component
|
||||
|
||||
render do
|
||||
div class: "alert alert-#{@type}", style: "border: solid 5px pink" do
|
||||
@inner_content
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,9 @@
|
|||
defmodule TempleDemoWeb.Component.Inner do
|
||||
use Temple.Component
|
||||
|
||||
render do
|
||||
div id: "inner", outer_id: @outer_id do
|
||||
@inner_content
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,10 @@
|
|||
defmodule TempleDemoWeb.Component.Outer do
|
||||
use Temple.Component
|
||||
alias TempleDemoWeb.Component.Inner
|
||||
|
||||
render do
|
||||
c Inner, outer_id: "from-outer" do
|
||||
@inner_content
|
||||
end
|
||||
end
|
||||
end
|
|
@ -3,6 +3,10 @@ section class: "phx-hero" do
|
|||
gettext("Welcome to %{name}!", name: "Phoenix")
|
||||
end
|
||||
|
||||
c Outer, outer_id: "hello" do
|
||||
"inner content of outer"
|
||||
end
|
||||
|
||||
case @text do
|
||||
"staging" ->
|
||||
p do
|
||||
|
|
|
@ -1,25 +1,25 @@
|
|||
form_for @changeset, @action, fn f ->
|
||||
if @changeset.action do
|
||||
div class: "alert alert-danger" do
|
||||
c Flash, type: :info do
|
||||
p do: "Oops, something went wrong! Please check the errors below."
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
label f, :title
|
||||
text_input f, :title
|
||||
error_tag(f, :title)
|
||||
error_tag(f, :title)
|
||||
|
||||
label f, :body
|
||||
textarea f, :body
|
||||
error_tag(f, :body)
|
||||
error_tag(f, :body)
|
||||
|
||||
label f, :published_at
|
||||
datetime_select f, :published_at
|
||||
error_tag(f, :published_at)
|
||||
error_tag(f, :published_at)
|
||||
|
||||
label f, :author
|
||||
text_input f, :author
|
||||
error_tag(f, :author)
|
||||
error_tag(f, :author)
|
||||
|
||||
div do
|
||||
submit "Save"
|
||||
|
|
|
@ -1,27 +1,26 @@
|
|||
h1 do: "Listing Posts"
|
||||
|
||||
table do
|
||||
thead do
|
||||
tr do
|
||||
th do: "Title"
|
||||
th do: "Body"
|
||||
th do: "Published at"
|
||||
th do: "Author"
|
||||
th()
|
||||
end
|
||||
tbody do
|
||||
for post <- @posts do
|
||||
tr do
|
||||
td do: post.title
|
||||
td do: post.body
|
||||
td do: post.published_at
|
||||
td do: post.author
|
||||
td do
|
||||
link "Show", to: Routes.post_path(@conn, :show, post)
|
||||
link "Edit", to: Routes.post_path(@conn, :edit, post)
|
||||
link "Delete", to: Routes.post_path(@conn, :delete, post),
|
||||
method: :delete, data: [confirm: "Are you sure?"]
|
||||
end
|
||||
c Headers do
|
||||
th do: "Title"
|
||||
th do: "Body"
|
||||
th do: "Published at"
|
||||
th do: "Author"
|
||||
th do: "BOB"
|
||||
end
|
||||
|
||||
tbody do
|
||||
for post <- @posts do
|
||||
tr do
|
||||
td do: post.title
|
||||
td do: post.body
|
||||
td do: post.published_at
|
||||
td do: post.author
|
||||
td do
|
||||
link "Show", to: Routes.post_path(@conn, :show, post)
|
||||
link "Edit", to: Routes.post_path(@conn, :edit, post)
|
||||
link "Delete", to: Routes.post_path(@conn, :delete, post),
|
||||
method: :delete, data: [confirm: "Are you sure?"]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,3 +1,14 @@
|
|||
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
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -45,7 +45,7 @@ defmodule TempleDemo.MixProject do
|
|||
{:gettext, "~> 0.11"},
|
||||
{:jason, "~> 1.0"},
|
||||
{:plug_cowboy, "~> 2.0"},
|
||||
{:wallaby, "~> 0.26.0", only: :test},
|
||||
{:wallaby, "~> 0.28.0", only: :test},
|
||||
{:tzdata, "~> 1.0.3"},
|
||||
{:temple, path: "../../"}
|
||||
]
|
||||
|
|
|
@ -1,42 +1,42 @@
|
|||
%{
|
||||
"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.0.4", "a1cae72211f0eef17705aaededacac3eb30e6625b04a6117c1b2db6ace7d5976", [:mix], [], "hexpm", "4a0850c9be22a43af9920a71ab17c051f5f7d45c209e40269a1938832510e4d9"},
|
||||
"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"},
|
||||
"db_connection": {:hex, :db_connection, "2.2.2", "3bbca41b199e1598245b716248964926303b5d4609ff065125ce98bcd368939e", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}], "hexpm", "642af240d8a8affb93b4ba5a6fcd2bbcbdc327e1a524b825d383711536f8070c"},
|
||||
"decimal": {:hex, :decimal, "1.8.1", "a4ef3f5f3428bdbc0d35374029ffcf4ede8533536fa79896dd450168d9acdf3c", [:mix], [], "hexpm", "3cb154b00225ac687f6cbd4acc4b7960027c757a5152b369923ead9ddbca7aec"},
|
||||
"ecto": {:hex, :ecto, "3.4.5", "2bcd262f57b2c888b0bd7f7a28c8a48aa11dc1a2c6a858e45dd8f8426d504265", [: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", "8c6d1d4d524559e9b7a062f0498e2c206122552d63eacff0a6567ffe7a8e8691"},
|
||||
"ecto_sql": {:hex, :ecto_sql, "3.4.4", "d28bac2d420f708993baed522054870086fd45016a9d09bb2cd521b9c48d32ea", [:mix], [{:db_connection, "~> 2.2", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.4.3", [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.0", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "edb49af715dd72f213b66adfd0f668a43c17ed510b5d9ac7528569b23af57fe8"},
|
||||
"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"},
|
||||
"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.1", "12b22825e22f468c02eb3e4b9985f3d0cb8dc40b9bd704730efa11abd2708c44", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "b659b8571deedf60f79c5a608e15414085fa141344e2716fbd6988a084b5f993"},
|
||||
"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.3.1", "30ce04ab3175b6ad0bdce0035cba77bba68b813d523d1aac73d9781b4d193cf8", [:mix], [], "hexpm", "6cbe761d6a0ca5a31a0931bf4c63204bceb64538e664a8ecf784a9a6f3b875f1"},
|
||||
"mime": {:hex, :mime, "1.5.0", "203ef35ef3389aae6d361918bf3f952fa17a09e8e43b5aa592b93eba05d0fb8d", [:mix], [], "hexpm", "55a94c0f552249fc1a3dd9cd2d3ab9de9d3c89b559c2bd01121f824834f24746"},
|
||||
"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_ecto": {:hex, :phoenix_ecto, "4.1.0", "a044d0756d0464c5a541b4a0bf4bcaf89bffcaf92468862408290682c73ae50d", [:mix], [{:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.9", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "c5e666a341ff104d0399d8f0e4ff094559b2fde13a5985d4cb5023b2c2ac558b"},
|
||||
"phoenix_html": {:hex, :phoenix_html, "2.14.2", "b8a3899a72050f3f48a36430da507dd99caf0ac2d06c77529b1646964f3d563e", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "58061c8dfd25da5df1ea0ca47c972f161beb6c875cd293917045b92ffe1bf617"},
|
||||
"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.10.3", "c9cebe917637d8db0e759039cc106adca069874e1a9034fd6e3fdd427fd3c283", [: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", "01f9037a2a1de1d633b5a881101e6a444bcabb1d386ca1e00bb273a1f1d9d939"},
|
||||
"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.1.2", "bdd187572cc26dbd95b87136290425f2b580a116d3fb1f564216918c9730d227", [:mix], [], "hexpm", "6b8b608f895b6ffcfad49c37c7883e8df98ae19c6a28113b02aa1e9c5b22d6b5"},
|
||||
"plug_crypto": {:hex, :plug_crypto, "1.2.0", "1cb20793aa63a6c619dd18bb33d7a3aa94818e5fd39ad357051a67f26dfa2df6", [:mix], [], "hexpm", "a48b538ae8bf381ffac344520755f3007cc10bd8e90b240af98ea29b69683fc2"},
|
||||
"poolboy": {:hex, :poolboy, "1.5.2", "392b007a1693a64540cead79830443abf5762f5d30cf50bc95cb2c1aaafa006b", [:rebar3], [], "hexpm", "dad79704ce5440f3d5a3681c8590b9dc25d1a561e8f5a9c995281012860901e3"},
|
||||
"postgrex": {:hex, :postgrex, "0.15.5", "aec40306a622d459b01bff890fa42f1430dac61593b122754144ad9033a2152f", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "ed90c81e1525f65a2ba2279dbcebf030d6d13328daa2f8088b9661eb9143af7f"},
|
||||
"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.1", "ae2718484892448a24470e6aa341bc847c3277bfb8d4e9289f7474d752c09c7f", [:rebar3], [], "hexpm", "4738382e36a0a9a2b6e25d67c960e40e1a2c95560b9f936d8e29de8cd858480f"},
|
||||
"telemetry": {:hex, :telemetry, "0.4.2", "2808c992455e08d6177322f14d3bdb6b625fbcfd233a73505870d8738a2f4599", [:rebar3], [], "hexpm", "2d1419bd9dda6a206d7b5852179511722e2b18812310d304620c7bd92a13fcef"},
|
||||
"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"},
|
||||
"tzdata": {:hex, :tzdata, "1.0.3", "73470ad29dde46e350c60a66e6b360d3b99d2d18b74c4c349dbebbc27a09a3eb", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "a6e1ee7003c4d04ecbd21dd3ec690d4c6662db5d3bbdd7262d53cdf5e7c746c1"},
|
||||
"unicode_util_compat": {:hex, :unicode_util_compat, "0.5.0", "8516502659002cec19e244ebd90d312183064be95025a319a6c7e89f4bccd65b", [:rebar3], [], "hexpm", "d48d002e15f5cc105a696cf2f1bbb3fc72b4b770a184d8420c8db20da2674b38"},
|
||||
"wallaby": {:hex, :wallaby, "0.26.0", "170b05b2fe572ec38071dbe45a908123959d5245f389f657e9a79eb463dc0431", [:mix], [{:ecto_sql, ">= 3.0.0", [hex: :ecto_sql, repo: "hexpm", optional: true]}, {:httpoison, "~> 0.12 or ~> 1.0", [hex: :httpoison, repo: "hexpm", optional: false]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:phoenix_ecto, ">= 3.0.0", [hex: :phoenix_ecto, repo: "hexpm", optional: true]}, {:poolboy, "~> 1.5", [hex: :poolboy, repo: "hexpm", optional: false]}, {:web_driver_client, "~> 0.1.0", [hex: :web_driver_client, repo: "hexpm", optional: false]}], "hexpm", "07a437e75c9276900288e4fe5c1814a5486f10a54940aa524ea65ce22b40c182"},
|
||||
"wallaby": {:hex, :wallaby, "0.28.0", "2ff217c0f245cadb3e5d91748ebcf0102873ceb9ef8a3507717c8bdd73915668", [:mix], [{:ecto_sql, ">= 3.0.0", [hex: :ecto_sql, repo: "hexpm", optional: true]}, {:httpoison, "~> 0.12 or ~> 1.0", [hex: :httpoison, repo: "hexpm", optional: false]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:phoenix_ecto, ">= 3.0.0", [hex: :phoenix_ecto, repo: "hexpm", optional: true]}, {:web_driver_client, "~> 0.1.0", [hex: :web_driver_client, repo: "hexpm", optional: false]}], "hexpm", "e58112650d0b51e81714a626eab7d486d7a77342c9bbc2ba262b6653f9b22558"},
|
||||
"web_driver_client": {:hex, :web_driver_client, "0.1.0", "19466a989c76b7ec803c796cec0fec4611a64f445fd5120ce50c9e3817e09c2c", [:mix], [{:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:tesla, "~> 1.3.0", [hex: :tesla, repo: "hexpm", optional: false]}], "hexpm", "c9c031ca915e8fc75b5e24ac93503244f3cc406dd7f53047087a45aa62d60e9e"},
|
||||
}
|
||||
|
|
|
@ -2,33 +2,28 @@ defmodule TempleDemoWeb.TempleFeatureTest do
|
|||
use ExUnit.Case, async: false
|
||||
use Wallaby.Feature
|
||||
alias TempleDemoWeb.Router.Helpers, as: Routes
|
||||
alias TempleDemoWeb.Endpoint, as: E
|
||||
@endpoint TempleDemoWeb.Endpoint
|
||||
|
||||
feature "renders the homepage", %{session: session} do
|
||||
session
|
||||
|> visit("/")
|
||||
|> assert_text("Welcome to Phoenix!")
|
||||
|> assert_text("inner content of outer")
|
||||
end
|
||||
|
||||
feature "case statements work", %{session: session} do
|
||||
session =
|
||||
session
|
||||
|> visit("/?text=staging")
|
||||
|
||||
session |> assert_text("Welcome to Phoenix!")
|
||||
session |> assert_text("Peace-of-mind from prototype to staging")
|
||||
|
||||
session =
|
||||
session
|
||||
|> visit("/?text=foobar")
|
||||
|
||||
session |> assert_text("Welcome to Phoenix!")
|
||||
session |> assert_text("Peace-of-mind from prototype to production")
|
||||
session
|
||||
|> visit("/?text=staging")
|
||||
|> assert_text("Welcome to Phoenix!")
|
||||
|> assert_text("Peace-of-mind from prototype to staging")
|
||||
|> visit("/?text=foobar")
|
||||
|> assert_text("Welcome to Phoenix!")
|
||||
|> assert_text("Peace-of-mind from prototype to production")
|
||||
end
|
||||
|
||||
feature "can create a new post", %{session: session} do
|
||||
session
|
||||
|> visit(Routes.post_path(E, :index))
|
||||
|> visit(Routes.post_path(@endpoint, :index))
|
||||
|> click(Query.link("New Post"))
|
||||
|> fill_in(Query.text_field("Title"), with: "Temple is awesome!")
|
||||
|> fill_in(Query.text_field("Body"), with: "In this post I will show you how to use Temple")
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
defmodule Temple.Buffer do
|
||||
@moduledoc false
|
||||
|
||||
use Agent
|
||||
|
||||
def start_link(state \\ []) do
|
||||
Agent.start_link(fn -> state end)
|
||||
end
|
||||
|
|
|
@ -2,9 +2,7 @@ defmodule Temple do
|
|||
alias Temple.Parser
|
||||
|
||||
@moduledoc """
|
||||
> Warning: Docs are WIP
|
||||
|
||||
Temple syntax is available inside the `temple` and `live_temple` macros, and is compiled into EEx at build time.
|
||||
Temple syntax is available inside the `temple`, and is compiled into EEx at build time.
|
||||
|
||||
### Usage
|
||||
|
||||
|
@ -47,7 +45,7 @@ defmodule Temple do
|
|||
text_input f, :name
|
||||
end
|
||||
|
||||
# You can explicitly call a tag by prefixing with the Temple module
|
||||
# You can explicitly emit a tag by prefixing with the Temple module
|
||||
Temple.div do
|
||||
"Foo"
|
||||
end
|
||||
|
@ -59,11 +57,11 @@ defmodule Temple do
|
|||
|
||||
### Reserved keywords
|
||||
|
||||
You can pass a keyword list to an element as element attributes, but there are several reserved keywords.
|
||||
You can pass a keyword list to an element as element attributes, but there is currently a reserved keyword.
|
||||
|
||||
#### Compact
|
||||
|
||||
Passing `compact: true` will not rendering new lines from within the element. This is useful if you are trying to use the `:empty` psuedo selector.
|
||||
Passing `compact: true` will not emit a new line between the opening tag, the content, and the closing tag. This is useful if you are trying to use the `:empty` psuedo selector.
|
||||
|
||||
```elixir
|
||||
temple do
|
||||
|
@ -122,6 +120,27 @@ defmodule Temple do
|
|||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Context for temple markup.
|
||||
|
||||
Returns an EEx string.
|
||||
|
||||
## Usage
|
||||
|
||||
```elixir
|
||||
import Temple
|
||||
|
||||
temple do
|
||||
div class: @class do
|
||||
"Hello, world!"
|
||||
end
|
||||
end
|
||||
|
||||
# <div class="<%= @class %>">
|
||||
# Hello, world!
|
||||
# </div>
|
||||
```
|
||||
"""
|
||||
defmacro temple([do: block] = _block) do
|
||||
markup = Parser.parse(block)
|
||||
|
||||
|
@ -136,9 +155,26 @@ defmodule Temple do
|
|||
end
|
||||
end
|
||||
|
||||
defmacro live_temple([do: block] = _block) do
|
||||
@doc """
|
||||
Compiles temple markup into a quoted expression using the given EEx Engine.
|
||||
|
||||
## Usage
|
||||
|
||||
```elixir
|
||||
require Temple
|
||||
|
||||
Temple.compile Phoenix.HTML.Engine do
|
||||
div class: @class do
|
||||
"Hello, world!"
|
||||
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
|
||||
markup = Parser.parse(block)
|
||||
|
||||
EEx.compile_string(markup, engine: Phoenix.LiveView.Engine)
|
||||
EEx.compile_string(markup, engine: engine, line: __CALLER__.line, file: __CALLER__.file)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,23 +1,161 @@
|
|||
defmodule Temple.Component do
|
||||
@moduledoc """
|
||||
Behaviour for defining temple components.
|
||||
"""
|
||||
API for defining components.
|
||||
|
||||
@doc """
|
||||
The render callback must return AST to be inserted into the markup.
|
||||
Component modules are basically normal Phoenix View modules. The contents of the `render` macro are compiled into a `render/2` function. This means that you can define functions in your component module and use them in your component markup.
|
||||
|
||||
Since you need to return AST, it is typical to wrap the contents of the function in a `quote` block like:
|
||||
Since component modules are view modules, the assigns you pass to the component are accessible via the `@` macro and the `assigns` variable.
|
||||
|
||||
## Usage
|
||||
|
||||
```elixir
|
||||
defmodule MyAppWeb.Components.Flash do
|
||||
use Temple.Component
|
||||
|
||||
def border_class(:info), do: "border-blue-500"
|
||||
def border_class(:warning), do: "border-yellow-500"
|
||||
def border_class(:error), do: "border-red-500"
|
||||
def border_class(:success), do: "border-green-500"
|
||||
|
||||
render do
|
||||
div class: "border rounded p-2 #\{assigns[:class]} #\{border_class(@message_type)}" do
|
||||
@inner_content
|
||||
end
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
Components are used by calling the `c` keyword, followed by the component module and any assigns you need to pass to the template.
|
||||
|
||||
`c` is a _**compile time keyword**_, not a function or a macro, so you won't see it in the generated documention.
|
||||
|
||||
```
|
||||
@impl Temple.Component
|
||||
def render() do
|
||||
quote do
|
||||
div do
|
||||
@children
|
||||
c MyAppWeb.Components.Flash, class: "font-bold", message_type: :info do
|
||||
ul do
|
||||
for info <- infos do
|
||||
li class: "p-4" do
|
||||
info.message
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
Since components are just modules, if you alias your module, you can use them more ergonomically.
|
||||
|
||||
```
|
||||
alias MyAppWeb.Components.Flex
|
||||
|
||||
c Flex, class: "justify-between items center" do
|
||||
for item <- items do
|
||||
div class: "p-4" do
|
||||
item.name
|
||||
end
|
||||
end
|
||||
end
|
||||
```
|
||||
"""
|
||||
@callback render() :: Macro.t()
|
||||
|
||||
defmacro __using__(_) do
|
||||
quote do
|
||||
import Temple.Component, only: [render: 1]
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Defines a component template.
|
||||
|
||||
## Usage
|
||||
|
||||
```elixir
|
||||
defmodule MyAppWeb.Components.Flash do
|
||||
use Temple.Component
|
||||
|
||||
def border_class(:info), do: "border-blue-500"
|
||||
def border_class(:warning), do: "border-yellow-500"
|
||||
def border_class(:error), do: "border-red-500"
|
||||
def border_class(:success), do: "border-green-500"
|
||||
|
||||
render do
|
||||
div class: "border rounded p-2 #\{assigns[:class]} #\{border_class(@message_type)}" do
|
||||
@inner_content
|
||||
end
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
"""
|
||||
defmacro render(block) do
|
||||
quote do
|
||||
def render(assigns), do: render(:self, assigns)
|
||||
|
||||
def render(:self, var!(assigns)) do
|
||||
require Temple
|
||||
|
||||
_ = var!(assigns)
|
||||
|
||||
Temple.compile(unquote(Temple.Component.engine()), unquote(block))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Defines a component module.
|
||||
|
||||
This macro makes it easy to define components without creating a separate file. It literally inlines a component module.
|
||||
|
||||
Since it defines a module inside of the current module, local function calls from the outer module won't be available. For convenience, the outer module is aliased for you, so you can call remote functions with a shorter module name.
|
||||
|
||||
## Usage
|
||||
|
||||
```elixir
|
||||
def MyAppWeb.SomeView do
|
||||
use MyAppWeb.SomeView, :view
|
||||
import Temple.Component, only: [defcomp: 2]
|
||||
|
||||
# define a function in outer module
|
||||
def foobar(), do: "foobar"
|
||||
|
||||
# define a component
|
||||
defcomp Button 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
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# use the component in a SomeView template. Or else, you must alias `MyAppWeb.SomeView.Button`
|
||||
c Button, extra_classes: "border-2 border-red-500" do
|
||||
"Submit!"
|
||||
end
|
||||
```
|
||||
"""
|
||||
defmacro defcomp(module, [do: block] = _block) do
|
||||
quote location: :keep do
|
||||
defmodule unquote(module) do
|
||||
use Temple.Component
|
||||
alias unquote(__CALLER__.module)
|
||||
|
||||
render do
|
||||
unquote(block)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
def engine() do
|
||||
cond do
|
||||
Code.ensure_loaded?(Phoenix.LiveView.Engine) ->
|
||||
Phoenix.LiveView.Engine
|
||||
|
||||
Code.ensure_loaded?(Phoenix.HTML.Engine) ->
|
||||
Phoenix.HTML.Engine
|
||||
|
||||
true ->
|
||||
nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,19 +1,44 @@
|
|||
defmodule Temple.Engine do
|
||||
@behaviour Phoenix.Template.Engine
|
||||
|
||||
@moduledoc false
|
||||
@moduledoc """
|
||||
The Temple HTML engine makes it possible to use Temple with Phoenix controllers.
|
||||
|
||||
To get started, you will configure Phoenix to use this module for `.exs` files.
|
||||
|
||||
```elixir
|
||||
# config.exs
|
||||
config :phoenix, :template_engines,
|
||||
# this will work for files named like `index.html.exs`
|
||||
exs: Temple.Engine
|
||||
|
||||
# config/dev.exs
|
||||
config :your_app, YourAppWeb.Endpoint,
|
||||
live_reload: [
|
||||
patterns: [
|
||||
~r"lib/myapp_web/(live|views)/.*(ex|exs|lexs)$",
|
||||
~r"lib/myapp_web/templates/.*(eex|exs|lexs)$"
|
||||
]
|
||||
]
|
||||
|
||||
# my_app/
|
||||
# lib/
|
||||
# my_app/
|
||||
# my_app_web/
|
||||
# templates/
|
||||
# posts/
|
||||
# show.html.exs
|
||||
```
|
||||
|
||||
Now you can get started by writing `exs` files in the templates directory and they will be compiled as you would expect.
|
||||
"""
|
||||
|
||||
def compile(path, _name) do
|
||||
require Temple
|
||||
|
||||
template = path |> File.read!() |> Code.string_to_quoted!(file: path)
|
||||
|
||||
ast =
|
||||
quote do
|
||||
unquote(template)
|
||||
end
|
||||
|
||||
Temple.temple(ast)
|
||||
Temple.temple(template)
|
||||
|> EEx.compile_string(engine: Phoenix.HTML.Engine, file: path, line: 1)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,14 +1,46 @@
|
|||
defmodule Temple.LiveViewEngine do
|
||||
@behaviour Phoenix.Template.Engine
|
||||
|
||||
@moduledoc false
|
||||
@moduledoc """
|
||||
The Temple LiveView engine makes it possible to use Temple with Phoenix LiveView.
|
||||
|
||||
To get started, you will configure Phoenix to use this module for `.lexs` files.
|
||||
|
||||
```elixir
|
||||
# config.exs
|
||||
config :phoenix, :template_engines,
|
||||
# this will work for files named like `index.html.lexs`
|
||||
# you can enable Elixir syntax highlighting in your editor for this extension
|
||||
lexs: Temple.LiveViewEngine
|
||||
|
||||
# config/dev.exs
|
||||
config :your_app, YourAppWeb.Endpoint,
|
||||
live_reload: [
|
||||
patterns: [
|
||||
~r"lib/myapp_web/(live|views)/.*(ex|exs|lexs)$",
|
||||
~r"lib/myapp_web/templates/.*(eex|exs|lexs)$"
|
||||
]
|
||||
]
|
||||
|
||||
# my_app/
|
||||
# lib/
|
||||
# my_app/
|
||||
# my_app_web/
|
||||
# live/
|
||||
# posts_live/
|
||||
# show.ex
|
||||
# show.html.lexs
|
||||
```
|
||||
|
||||
Now you can get started by writing `lexs` files co-located with your live views and they will be compiled as you would expect.
|
||||
"""
|
||||
|
||||
def compile(path, _name) do
|
||||
require Temple
|
||||
|
||||
ast = path |> File.read!() |> Code.string_to_quoted!(file: path)
|
||||
template = path |> File.read!() |> Code.string_to_quoted!(file: path)
|
||||
|
||||
Temple.temple(ast)
|
||||
Temple.temple(template)
|
||||
|> EEx.compile_string(engine: Phoenix.LiveView.Engine, file: path, line: 1)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
defmodule Temple.Parser do
|
||||
@moduledoc false
|
||||
|
||||
@doc """
|
||||
Should return true if the parser should apply for the given AST.
|
||||
"""
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
defmodule Temple.Parser.AnonymousFunctions do
|
||||
@moduledoc false
|
||||
@behaviour Temple.Parser
|
||||
|
||||
alias Temple.Parser
|
||||
|
|
|
@ -1,21 +1,18 @@
|
|||
defmodule Temple.Parser.Components do
|
||||
@moduledoc false
|
||||
@behaviour Temple.Parser
|
||||
@component_prefix Application.fetch_env!(:temple, :component_prefix)
|
||||
|
||||
alias Temple.Parser
|
||||
alias Temple.Buffer
|
||||
|
||||
def applicable?({name, meta, _}) when is_atom(name) do
|
||||
!meta[:temple_component_applied] &&
|
||||
match?({:module, _}, name |> component_module() |> Code.ensure_compiled())
|
||||
def applicable?({:c, _, _}) do
|
||||
true
|
||||
end
|
||||
|
||||
def applicable?(_), do: false
|
||||
|
||||
defp component_module(name) do
|
||||
Module.concat([@component_prefix, Macro.camelize(to_string(name))])
|
||||
end
|
||||
def run({:c, _meta, [component_module | args]}, buffer) do
|
||||
import Temple.Parser.Private
|
||||
|
||||
def run({name, _meta, args}, _buffer) do
|
||||
{assigns, children} =
|
||||
case args do
|
||||
[assigns, [do: block]] ->
|
||||
|
@ -31,45 +28,26 @@ defmodule Temple.Parser.Components do
|
|||
{[], nil}
|
||||
end
|
||||
|
||||
component_module = Module.concat([@component_prefix, Macro.camelize(to_string(name))])
|
||||
if children do
|
||||
Buffer.put(
|
||||
buffer,
|
||||
"<%= Phoenix.View.render_layout #{Macro.to_string(component_module)}, :self, #{
|
||||
Macro.to_string(assigns)
|
||||
} do %>"
|
||||
)
|
||||
|
||||
ast = apply(component_module, :render, [])
|
||||
traverse(buffer, children)
|
||||
|
||||
{name, meta, args} =
|
||||
ast
|
||||
|> Macro.prewalk(fn
|
||||
{:@, _, [{:children, _, _}]} ->
|
||||
children
|
||||
Buffer.put(buffer, "<% end %>")
|
||||
else
|
||||
Buffer.put(
|
||||
buffer,
|
||||
"<%= Phoenix.View.render #{Macro.to_string(component_module)}, :self, #{
|
||||
Macro.to_string(assigns)
|
||||
} %>"
|
||||
)
|
||||
end
|
||||
|
||||
{:@, _, [{:temple, _, _}]} ->
|
||||
assigns
|
||||
|
||||
{:@, _, [{name, _, _}]} = node ->
|
||||
if name in Keyword.keys(assigns) do
|
||||
Keyword.get(assigns, name, nil)
|
||||
else
|
||||
node
|
||||
end
|
||||
|
||||
node ->
|
||||
node
|
||||
end)
|
||||
|
||||
ast =
|
||||
if Enum.any?(
|
||||
[
|
||||
Parser.nonvoid_elements(),
|
||||
Parser.nonvoid_elements_aliases(),
|
||||
Parser.void_elements(),
|
||||
Parser.void_elements_aliases()
|
||||
],
|
||||
fn elements -> name in elements end
|
||||
) do
|
||||
{name, Keyword.put(meta, :temple_component_applied, true), args}
|
||||
else
|
||||
{name, meta, args}
|
||||
end
|
||||
|
||||
{:component_applied, ast}
|
||||
:ok
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
defmodule Temple.Parser.Default do
|
||||
@moduledoc false
|
||||
@behaviour Temple.Parser
|
||||
|
||||
alias Temple.Parser
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
defmodule Temple.Parser.DoExpressions do
|
||||
@moduledoc false
|
||||
@behaviour Temple.Parser
|
||||
|
||||
alias Temple.Parser
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
defmodule Temple.Parser.Empty do
|
||||
@moduledoc false
|
||||
@behaviour Temple.Parser
|
||||
|
||||
alias Temple.Parser
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
defmodule Temple.Parser.Match do
|
||||
@moduledoc false
|
||||
@behaviour Temple.Parser
|
||||
|
||||
alias Temple.Parser
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
defmodule Temple.Parser.NonvoidElementsAliases do
|
||||
@moduledoc false
|
||||
@behaviour Temple.Parser
|
||||
|
||||
alias Temple.Parser
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
defmodule Temple.Parser.RightArrow do
|
||||
@moduledoc false
|
||||
@behaviour Temple.Parser
|
||||
|
||||
alias Temple.Parser
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
defmodule Temple.Parser.TempleNamespaceNonvoid do
|
||||
@moduledoc false
|
||||
@behaviour Temple.Parser
|
||||
|
||||
alias Temple.Parser
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
defmodule Temple.Parser.TempleNamespaceVoid do
|
||||
@moduledoc false
|
||||
@behaviour Temple.Parser
|
||||
|
||||
alias Temple.Parser
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
defmodule Temple.Parser.Text do
|
||||
@moduledoc false
|
||||
@behaviour Temple.Parser
|
||||
|
||||
alias Temple.Buffer
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
defmodule Temple.Parser.VoidElementsAliases do
|
||||
@moduledoc false
|
||||
@behaviour Temple.Parser
|
||||
|
||||
alias Temple.Parser
|
||||
|
|
|
@ -1,15 +0,0 @@
|
|||
defmodule Temple.Recompiler do
|
||||
defmacro __using__(_) do
|
||||
quote do
|
||||
component_path = Application.get_env(:temple, :components_path)
|
||||
|
||||
for f <- File.ls!(component_path),
|
||||
do:
|
||||
Module.put_attribute(
|
||||
__MODULE__,
|
||||
:external_resource,
|
||||
Path.join(component_path, f)
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,8 +0,0 @@
|
|||
defmodule Temple.Utils do
|
||||
@moduledoc false
|
||||
def puts(binary) do
|
||||
IO.puts(binary)
|
||||
|
||||
binary
|
||||
end
|
||||
end
|
8
mix.exs
8
mix.exs
|
@ -15,10 +15,7 @@ defmodule Temple.MixProject do
|
|||
source_url: "https://github.com/mhanberg/temple",
|
||||
docs: [
|
||||
main: "Temple",
|
||||
extras: ["README.md"],
|
||||
deps: [
|
||||
phoenix_html: "https://hexdocs.pm/phoenix_html/"
|
||||
]
|
||||
extras: ["README.md"]
|
||||
]
|
||||
]
|
||||
end
|
||||
|
@ -48,7 +45,8 @@ defmodule Temple.MixProject do
|
|||
[
|
||||
{:ex_doc, "~> 0.22.0", only: :dev, runtime: false},
|
||||
{:phoenix, ">= 0.0.0", optional: true},
|
||||
{:phoenix_html, ">= 0.0.0", only: :test}
|
||||
{:phoenix_html, ">= 0.0.0", only: :test},
|
||||
{:phoenix_live_view, ">= 0.0.0", only: :test}
|
||||
]
|
||||
end
|
||||
end
|
||||
|
|
1
mix.lock
1
mix.lock
|
@ -8,6 +8,7 @@
|
|||
"nimble_parsec": {:hex, :nimble_parsec, "0.6.0", "32111b3bf39137144abd7ba1cce0914533b2d16ef35e8abc5ec8be6122944263", [:mix], [], "hexpm", "27eac315a94909d4dc68bc07a4a83e06c8379237c5ea528a9acff4ca1c873c52"},
|
||||
"phoenix": {:hex, :phoenix, "1.5.3", "bfe0404e48ea03dfe17f141eff34e1e058a23f15f109885bbdcf62be303b49ff", [: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", "8e16febeb9640d8b33895a691a56481464b82836d338bb3a23125cd7b6157c25"},
|
||||
"phoenix_html": {:hex, :phoenix_html, "2.14.2", "b8a3899a72050f3f48a36430da507dd99caf0ac2d06c77529b1646964f3d563e", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "58061c8dfd25da5df1ea0ca47c972f161beb6c875cd293917045b92ffe1bf617"},
|
||||
"phoenix_live_view": {:hex, :phoenix_live_view, "0.13.3", "2186c55cc7c54ca45b97c6f28cfd267d1c61b5f205f3c83533704cd991bdfdec", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.4.17 or ~> 1.5.2", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14", [hex: :phoenix_html, repo: "hexpm", optional: false]}], "hexpm", "c6309a7da2e779cb9cdf2fb603d75f38f49ef324bedc7a81825998bd1744ff8a"},
|
||||
"phoenix_pubsub": {:hex, :phoenix_pubsub, "2.0.0", "a1ae76717bb168cdeb10ec9d92d1480fec99e3080f011402c0a2d68d47395ffb", [:mix], [], "hexpm", "c52d948c4f261577b9c6fa804be91884b381a7f8f18450c5045975435350f771"},
|
||||
"plug": {:hex, :plug, "1.10.3", "c9cebe917637d8db0e759039cc106adca069874e1a9034fd6e3fdd427fd3c283", [: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", "01f9037a2a1de1d633b5a881101e6a444bcabb1d386ca1e00bb273a1f1d9d939"},
|
||||
"plug_crypto": {:hex, :plug_crypto, "1.1.2", "bdd187572cc26dbd95b87136290425f2b580a116d3fb1f564216918c9730d227", [:mix], [], "hexpm", "6b8b608f895b6ffcfad49c37c7883e8df98ae19c6a28113b02aa1e9c5b22d6b5"},
|
||||
|
|
|
@ -0,0 +1,124 @@
|
|||
defmodule Temple.ComponentTest do
|
||||
use ExUnit.Case, async: true
|
||||
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
|
||||
div class: "font-bold" do
|
||||
"Hello, world"
|
||||
end
|
||||
|
||||
c Temple.Components.Component do
|
||||
aside class: "foobar" do
|
||||
"I'm a component!"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
assert result ==
|
||||
~s{<div class="font-bold">Hello, world</div><%= Phoenix.View.render_layout Temple.Components.Component, :self, [] 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
|
||||
|
||||
test "function components can accept local assigns" do
|
||||
result =
|
||||
temple do
|
||||
div class: "font-bold" do
|
||||
"Hello, world"
|
||||
end
|
||||
|
||||
c Temple.Components.Component2, class: "bg-red" do
|
||||
"I'm a component!"
|
||||
end
|
||||
end
|
||||
|
||||
assert result ==
|
||||
~s{<div class="font-bold">Hello, world</div><%= Phoenix.View.render_layout Temple.Components.Component2, :self, [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, [class: class] do %>I'm a component!<% end %>}
|
||||
end
|
||||
|
||||
test "function components can use other components" do
|
||||
result =
|
||||
temple do
|
||||
c Temple.Components.Outer do
|
||||
"outer!"
|
||||
end
|
||||
|
||||
c Temple.Components.Inner, outer_id: "set by root inner" do
|
||||
"inner!"
|
||||
end
|
||||
end
|
||||
|
||||
assert result ==
|
||||
~s{<%= Phoenix.View.render_layout Temple.Components.Outer, :self, [] do %>outer!\n<% end %><%= Phoenix.View.render_layout Temple.Components.Inner, :self, [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
|
||||
c Temple.Components.WithFuncs, foo: :bar do
|
||||
"doo doo"
|
||||
end
|
||||
end
|
||||
|
||||
assert result ==
|
||||
~s{<%= Phoenix.View.render_layout Temple.Components.WithFuncs, :self, [foo: :bar] do %>doo doo<% end %>}
|
||||
|
||||
assert evaluate_template(result) == ~s{<div class="barbarbar">doo doo</div>}
|
||||
end
|
||||
|
||||
test "components can be void elements" do
|
||||
result =
|
||||
temple do
|
||||
c Temple.Components.VoidComponent, foo: :bar
|
||||
end
|
||||
|
||||
assert result ==
|
||||
~s{<%= Phoenix.View.render Temple.Components.VoidComponent, :self, [foo: :bar] %>}
|
||||
|
||||
assert evaluate_template(result) == ~s{<div class="void!!">bar</div>}
|
||||
end
|
||||
end
|
|
@ -0,0 +1,51 @@
|
|||
defmodule Temple.Parser.ComponentsTest do
|
||||
use ExUnit.Case, async: false
|
||||
alias Temple.Parser.Components
|
||||
use Temple.Support.Utils
|
||||
|
||||
describe "applicable?/1" do
|
||||
test "runs when using the `c` ast with a block" do
|
||||
ast =
|
||||
quote do
|
||||
c SomeModule, foo: :bar do
|
||||
div do
|
||||
"hello"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
assert Components.applicable?(ast)
|
||||
end
|
||||
|
||||
test "runs when using the `c` ast without a block" do
|
||||
ast =
|
||||
quote do
|
||||
c(SomeModule, foo: :bar)
|
||||
end
|
||||
|
||||
assert Components.applicable?(ast)
|
||||
end
|
||||
end
|
||||
|
||||
describe "run/2" do
|
||||
test "is correct" do
|
||||
buf = start_supervised!(Temple.Buffer)
|
||||
|
||||
ast =
|
||||
quote do
|
||||
c SomeModule, foo: :bar do
|
||||
aside class: "foobar" do
|
||||
"I'm a component!"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Temple.Parser.Components.run(ast, buf)
|
||||
|
||||
result = Temple.Buffer.get(buf)
|
||||
|
||||
assert result ==
|
||||
~s{<%= Phoenix.View.render_layout SomeModule, :self, [foo: :bar] do %><aside class="foobar">I'm a component!</aside><% end %>}
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,16 +0,0 @@
|
|||
defmodule PartialTest do
|
||||
use ExUnit.Case, async: true
|
||||
use Temple
|
||||
use Temple.Support.Utils
|
||||
|
||||
test "can correctly redefine elements" do
|
||||
result =
|
||||
temple do
|
||||
section do
|
||||
"Howdy!"
|
||||
end
|
||||
end
|
||||
|
||||
assert result == ~s{<section class="foo!">Howdy!</section>}
|
||||
end
|
||||
end
|
|
@ -1,12 +1,9 @@
|
|||
defmodule Temple.Components.Component do
|
||||
@behaviour Temple.Component
|
||||
use Temple.Component
|
||||
|
||||
@impl Temple.Component
|
||||
def render do
|
||||
quote do
|
||||
div class: @assign do
|
||||
@children
|
||||
end
|
||||
render do
|
||||
div do
|
||||
@inner_content
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,12 +1,9 @@
|
|||
defmodule Temple.Components.Component2 do
|
||||
@behaviour Temple.Component
|
||||
use Temple.Component
|
||||
|
||||
@impl Temple.Component
|
||||
def render do
|
||||
quote do
|
||||
div class: @class do
|
||||
@children
|
||||
end
|
||||
render do
|
||||
div class: @class do
|
||||
@inner_content
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,12 +1,9 @@
|
|||
defmodule Temple.Components.HasTemple do
|
||||
@behaviour Temple.Component
|
||||
use Temple.Component
|
||||
|
||||
@impl Temple.Component
|
||||
def render do
|
||||
quote do
|
||||
div class: @temple[:class] do
|
||||
@children
|
||||
end
|
||||
render do
|
||||
div class: @temple[:class] do
|
||||
@inner_content
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,12 +0,0 @@
|
|||
defmodule Temple.Components.HasTempleFunctionAssign do
|
||||
@behaviour Temple.Component
|
||||
|
||||
@impl Temple.Component
|
||||
def render do
|
||||
quote do
|
||||
div Keyword.put(@temple, :class, "flex #{@temple[:class]}") do
|
||||
@children
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,12 +1,9 @@
|
|||
defmodule Temple.Components.Inner do
|
||||
@behaviour Temple.Component
|
||||
use Temple.Component
|
||||
|
||||
@impl Temple.Component
|
||||
def render do
|
||||
quote do
|
||||
div id: "inner", outer_id: @outer_id do
|
||||
@children
|
||||
end
|
||||
render do
|
||||
div id: "inner", outer_id: @outer_id do
|
||||
@inner_content
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,12 +1,9 @@
|
|||
defmodule Temple.Components.Outer do
|
||||
@behaviour Temple.Component
|
||||
use Temple.Component
|
||||
|
||||
@impl Temple.Component
|
||||
def render do
|
||||
quote do
|
||||
inner outer_id: "from-outer" do
|
||||
@children
|
||||
end
|
||||
render do
|
||||
c Temple.Components.Inner, outer_id: "from-outer" do
|
||||
@inner_content
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,12 +1,9 @@
|
|||
defmodule Temple.Components.Section do
|
||||
@behaviour Temple.Component
|
||||
use Temple.Component
|
||||
|
||||
@impl Temple.Component
|
||||
def render do
|
||||
quote do
|
||||
section class: "foo!" do
|
||||
@children
|
||||
end
|
||||
render do
|
||||
section class: "foo!" do
|
||||
@inner_content
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
defmodule Temple.Components.VoidComponent do
|
||||
use Temple.Component
|
||||
|
||||
render do
|
||||
div class: "void!!" do
|
||||
"bar"
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,17 @@
|
|||
defmodule Temple.Components.WithFuncs do
|
||||
use Temple.Component
|
||||
|
||||
def get_class(:bar) do
|
||||
"barbarbar"
|
||||
end
|
||||
|
||||
def get_class(_) do
|
||||
"foofoofoo"
|
||||
end
|
||||
|
||||
render do
|
||||
div class: get_class(@foo) do
|
||||
@inner_content
|
||||
end
|
||||
end
|
||||
end
|
|
@ -19,4 +19,12 @@ defmodule Temple.Support.Utils do
|
|||
|
||||
Kernel.=~(a, b)
|
||||
end
|
||||
|
||||
def evaluate_template(template) do
|
||||
template
|
||||
|> EEx.compile_string(engine: Phoenix.HTML.Engine)
|
||||
|> Code.eval_quoted([])
|
||||
|> elem(0)
|
||||
|> Phoenix.HTML.safe_to_string()
|
||||
end
|
||||
end
|
||||
|
|
|
@ -269,7 +269,7 @@ defmodule TempleTest do
|
|||
~s{<div class="font-bold">Hello, world</div><div class="font-bold">Hello, world</div><div>Hello, world</div>}
|
||||
end
|
||||
|
||||
test "passing 'compact: true' will not insert new lines" do
|
||||
test "`do` passed as keyword will compile compacted markup" do
|
||||
import Temple.Support.Utils, only: []
|
||||
import Kernel
|
||||
|
||||
|
@ -287,95 +287,6 @@ defmodule TempleTest do
|
|||
assert result == ~s{<p>Bob</p>\n<p><%= foo %></p>}
|
||||
end
|
||||
|
||||
test "inlines function components" do
|
||||
result =
|
||||
temple do
|
||||
div class: "font-bold" do
|
||||
"Hello, world"
|
||||
end
|
||||
|
||||
component do
|
||||
"I'm a component!"
|
||||
end
|
||||
end
|
||||
|
||||
assert result ==
|
||||
~s{<div class="font-bold">Hello, world</div><div class="<%= @assign %>">I'm a component!</div>}
|
||||
end
|
||||
|
||||
test "function components can accept local assigns" do
|
||||
result =
|
||||
temple do
|
||||
div class: "font-bold" do
|
||||
"Hello, world"
|
||||
end
|
||||
|
||||
component2 class: "bg-red" do
|
||||
"I'm a component!"
|
||||
end
|
||||
end
|
||||
|
||||
assert 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"
|
||||
|
||||
component2 class: class do
|
||||
"I'm a component!"
|
||||
end
|
||||
end
|
||||
|
||||
assert result ==
|
||||
~s{<div class="font-bold">Hello, world</div><% class = "bg-red" %><div class="<%= class %>">I'm a component!</div>}
|
||||
end
|
||||
|
||||
test "function components can use other components" do
|
||||
result =
|
||||
temple do
|
||||
outer do
|
||||
"outer!"
|
||||
end
|
||||
|
||||
inner do
|
||||
"inner!"
|
||||
end
|
||||
end
|
||||
|
||||
assert result ==
|
||||
~s{<div id="inner" outer-id="from-outer">outer!</div><div id="inner" outer-id="<%= @outer_id %>">inner!</div>}
|
||||
end
|
||||
|
||||
test "@temple should be available in any component" do
|
||||
result =
|
||||
temple do
|
||||
has_temple class: "boom" do
|
||||
"yay!"
|
||||
end
|
||||
end
|
||||
|
||||
assert result == ~s{<div class="<%= [class: "boom"][:class] %>">yay!</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 "for with 2 generators" do
|
||||
result =
|
||||
temple do
|
||||
|
@ -424,33 +335,4 @@ defmodule TempleTest do
|
|||
assert result ==
|
||||
~s{<fieldset<%= Temple.Parser.Private.runtime_attrs(Foo.foo_bar()) %>><input type="text"></fieldset>}
|
||||
end
|
||||
|
||||
test "can pass a function as assigns that has @temple" do
|
||||
result =
|
||||
temple do
|
||||
has_temple_function_assign class: "justify-end", style: "color: pink" do
|
||||
input type: "text"
|
||||
end
|
||||
end
|
||||
|
||||
expected =
|
||||
~S"""
|
||||
<div<%= Temple.Parser.Private.runtime_attrs(Keyword.put([class: "justify-end", style: "color: pink"], :class, "flex #{[class: "justify-end", style: "color: pink"][:class]}")) %>>
|
||||
<input type="text">
|
||||
</div>
|
||||
"""
|
||||
|> String.trim()
|
||||
|
||||
assert result == expected
|
||||
|
||||
assert evaluate_template(result) == evaluate_template(expected)
|
||||
end
|
||||
|
||||
defp evaluate_template(template) do
|
||||
template
|
||||
|> EEx.compile_string(engine: Phoenix.HTML.Engine)
|
||||
|> Code.eval_quoted()
|
||||
|> elem(0)
|
||||
|> Phoenix.HTML.safe_to_string()
|
||||
end
|
||||
end
|
||||
|
|
Reference in New Issue