Skip to content

Commit 3b4f773

Browse files
Prep for new HEEx interpolation syntax (#5978)
* Prep for new HEEx interpolation syntax * bump deps for integration tests * bump LV to 1.0 --------- Co-authored-by: Steffen Deusch <[email protected]>
1 parent 4580d47 commit 3b4f773

File tree

32 files changed

+256
-228
lines changed

32 files changed

+256
-228
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
/installer/deps/
1313
/installer/doc/
1414
/installer/phx_new-*.ez
15+
/installer/tmp/
1516

1617
/integration_test/_build/
1718
/integration_test/deps/

guides/components.md

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ At the end of the Request life-cycle chapter, we created a template at `lib/hell
1616

1717
```heex
1818
<section>
19-
<h2>Hello World, from <%= @messenger %>!</h2>
19+
<h2>Hello World, from {@messenger}!</h2>
2020
</section>
2121
```
2222

@@ -34,7 +34,7 @@ That's simple enough. There's only two lines, `use HelloWeb, :html`. This line c
3434

3535
All of the imports and aliases we make in our module will also be available in our templates. That's because templates are effectively compiled into functions inside their respective module. For example, if you define a function in your module, you will be able to invoke it directly from the template. Let's see this in practice.
3636

37-
Imagine we want to refactor our `show.html.heex` to move the rendering of `<h2>Hello World, from <%= @messenger %>!</h2>` to its own function. We can move it to a function component inside `HelloHTML`, let's do so:
37+
Imagine we want to refactor our `show.html.heex` to move the rendering of `<h2>Hello World, from {@messenger}!</h2>` to its own function. We can move it to a function component inside `HelloHTML`, let's do so:
3838

3939
```elixir
4040
defmodule HelloWeb.HelloHTML do
@@ -46,7 +46,7 @@ defmodule HelloWeb.HelloHTML do
4646

4747
def greet(assigns) do
4848
~H"""
49-
<h2>Hello World, from <%= @messenger %>!</h2>
49+
<h2>Hello World, from {@messenger}!</h2>
5050
"""
5151
end
5252
end
@@ -89,19 +89,21 @@ Next, let's fully understand the expressive power behind the HEEx template langu
8989

9090
## HEEx
9191

92-
Function components and templates files are powered by [the HEEx template language](https://hexdocs.pm/phoenix_live_view/Phoenix.Component.html#sigil_H/2), which stands for "HTML+EEx". EEx is an Elixir library that uses `<%= expression %>` to execute Elixir expressions and interpolate their results into the template. This is frequently used to display assigns we have set by way of the `@` shortcut. In your controller, if you invoke:
92+
Function components and templates files are powered by [the HEEx template language](https://hexdocs.pm/phoenix_live_view/Phoenix.Component.html#sigil_H/2), which stands for "HTML+EEx". EEx is an Elixir library that uses `<%= expression %>` to execute Elixir expressions and interpolate their results into arbitrary text templates. HEEx extends EEx for writing HTML templates mixed with Elixir interpolation. We can write Elixir code inside `{...}` for HTML-aware interpolation inside tag attributes and the body. We can also interpolate arbitrary HEEx blocks using EEx interpolation (`<%= ... %>`). We use `@name` to access the key `name` defined inside `assigns`.
93+
94+
This is frequently used to display assigns we have set by way of the `@` shortcut. In your controller, if you invoke:
9395

9496
```elixir
9597
render(conn, :show, username: "joe")
9698
```
9799

98-
Then you can access said username in the templates as `<%= @username %>`. In addition to displaying assigns and functions, we can use pretty much any Elixir expression. For example, in order to have conditionals:
100+
Then you can access said username in the templates as `{@username}`. In addition to displaying assigns and functions, we can use pretty much any Elixir expression. For example, in order to have conditionals:
99101

100102
```heex
101103
<%= if some_condition? do %>
102-
<p>Some condition is true for user: <%= @username %></p>
104+
<p>Some condition is true for user: {@username}</p>
103105
<% else %>
104-
<p>Some condition is false for user: <%= @username %></p>
106+
<p>Some condition is false for user: {@username}</p>
105107
<% end %>
106108
```
107109

@@ -115,8 +117,8 @@ or even loops:
115117
</tr>
116118
<%= for number <- 1..10 do %>
117119
<tr>
118-
<td><%= number %></td>
119-
<td><%= number * number %></td>
120+
<td>{number}</td>
121+
<td>{number * number}</td>
120122
</tr>
121123
<% end %>
122124
</table>
@@ -131,20 +133,20 @@ HEEx also comes with handy HTML extensions we will learn next.
131133
Besides allowing interpolation of Elixir expressions via `<%= %>`, `.heex` templates come with HTML-aware extensions. For example, let's see what happens if you try to interpolate a value with "<" or ">" in it, which would lead to HTML injection:
132134

133135
```heex
134-
<%= "<b>Bold?</b>" %>
136+
{"<b>Bold?</b>"}
135137
```
136138

137139
Once you render the template, you will see the literal `<b>` on the page. This means users cannot inject HTML content on the page. If you want to allow them to do so, you can call `raw`, but do so with extreme care:
138140

139141
```heex
140-
<%= raw "<b>Bold?</b>" %>
142+
{raw("<b>Bold?</b>")}
141143
```
142144

143-
Another super power of HEEx templates is validation of HTML and lean interpolation syntax of attributes. You can write:
145+
Another super power of HEEx templates is validation of HTML and interpolation syntax of attributes. You can write:
144146

145147
```heex
146148
<div title="My div" class={@class}>
147-
<p>Hello <%= @username %></p>
149+
<p>Hello {@username}</p>
148150
</div>
149151
```
150152

@@ -154,7 +156,7 @@ To interpolate a dynamic number of attributes in a keyword list or map, do:
154156

155157
```heex
156158
<div title="My div" {@many_attributes}>
157-
<p>Hello <%= @username %></p>
159+
<p>Hello {@username}</p>
158160
</div>
159161
```
160162

@@ -178,7 +180,7 @@ Likewise, for comprehensions may be written as:
178180

179181
```heex
180182
<ul>
181-
<li :for={item <- @items}><%= item.name %></li>
183+
<li :for={item <- @items}>{item.name}</li>
182184
</ul>
183185
```
184186

@@ -189,7 +191,7 @@ Layouts are just function components. They are defined in a module, just like al
189191
You may be wondering how the string resulting from a rendered view ends up inside a layout. That's a great question! If we look at `lib/hello_web/components/layouts/root.html.heex`, just about at the end of the `<body>`, we will see this.
190192

191193
```heex
192-
<%= @inner_content %>
194+
{@inner_content}
193195
```
194196

195197
In other words, after rendering your page, the result is placed in the `@inner_content` assign.

guides/contexts.md

Lines changed: 22 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -525,10 +525,9 @@ We added a `category_select` above our save button. Now let's try it out. Next,
525525
<.list>
526526
...
527527
+ <:item title="Categories">
528-
+ <%= for cat <- @product.categories do %>
529-
+ <%= cat.title %>
530-
+ <br/>
531-
+ <% end %>
528+
+ <ul>
529+
+ <li :for={cat <- @product.categories}>{cat.title}</li>
530+
+ </ul>
532531
+ </:item>
533532
</.list>
534533
```
@@ -937,29 +936,23 @@ We created a view to render our `show.html` template and aliased our `ShoppingCa
937936
Next we can create the template at `lib/hello_web/controllers/cart_html/show.html.heex`:
938937

939938
```heex
940-
<%= if @cart.items == [] do %>
941-
<.header>
942-
My Cart
943-
<:subtitle>Your cart is empty</:subtitle>
944-
</.header>
945-
<% else %>
946-
<.header>
947-
My Cart
948-
</.header>
939+
<.header>
940+
My Cart
941+
<:subtitle :if={@cart.items == []}>Your cart is empty</:subtitle>
942+
</.header>
949943
944+
<div :if={@cart.items !== []}>
950945
<.simple_form :let={f} for={@changeset} action={~p"/cart"}>
951-
<.inputs_for :let={item_form} field={f[:items]}>
952-
<% item = item_form.data %>
946+
<.inputs_for :let={%{data: item} = item_form} field={f[:items]}>
953947
<.input field={item_form[:quantity]} type="number" label={item.product.title} />
954-
<%= currency_to_str(ShoppingCart.total_item_price(item)) %>
948+
{currency_to_str(ShoppingCart.total_item_price(item))}
955949
</.inputs_for>
956950
<:actions>
957951
<.button>Update cart</.button>
958952
</:actions>
959953
</.simple_form>
960-
961-
<b>Total</b>: <%= currency_to_str(ShoppingCart.total_cart_price(@cart)) %>
962-
<% end %>
954+
<b>Total</b>: {currency_to_str(ShoppingCart.total_cart_price(@cart))}
955+
</div>
963956
964957
<.back navigate={~p"/products"}>Back to products</.back>
965958
```
@@ -1299,20 +1292,20 @@ Next we can create the template at `lib/hello_web/controllers/order_html/show.ht
12991292
<.header>
13001293
Thank you for your order!
13011294
<:subtitle>
1302-
<strong>User uuid: </strong><%= @order.user_uuid %>
1295+
<strong>User uuid: </strong>{@order.user_uuid}
13031296
</:subtitle>
13041297
</.header>
13051298
13061299
<.table id="items" rows={@order.line_items}>
1307-
<:col :let={item} label="Title"><%= item.product.title %></:col>
1308-
<:col :let={item} label="Quantity"><%= item.quantity %></:col>
1300+
<:col :let={item} label="Title">{item.product.title}</:col>
1301+
<:col :let={item} label="Quantity">{item.quantity}</:col>
13091302
<:col :let={item} label="Price">
1310-
<%= HelloWeb.CartHTML.currency_to_str(item.price) %>
1303+
{HelloWeb.CartHTML.currency_to_str(item.price)}
13111304
</:col>
13121305
</.table>
13131306
13141307
<strong>Total price:</strong>
1315-
<%= HelloWeb.CartHTML.currency_to_str(@order.total_price) %>
1308+
{HelloWeb.CartHTML.currency_to_str(@order.total_price)}
13161309
13171310
<.back navigate={~p"/products"}>Back to products</.back>
13181311
```
@@ -1324,11 +1317,11 @@ Our last addition will be to add the "complete order" button to our cart page to
13241317
```diff
13251318
<.header>
13261319
My Cart
1327-
+ <:actions>
1328-
+ <.link href={~p"/orders"} method="post">
1329-
+ <.button>Complete order</.button>
1330-
+ </.link>
1331-
+ </:actions>
1320+
+ <:actions>
1321+
+ <.link href={~p"/orders"} method="post">
1322+
+ <.button>Complete order</.button>
1323+
+ </.link>
1324+
+ </:actions>
13321325
</.header>
13331326
```
13341327

guides/plug.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ In the [`init/1`] callback, we pass a default locale to use if none is present i
120120
To see the assign in action, go to the template in `lib/hello_web/controllers/page_html/home.html.heex` and add the following code after the closing of the `</h1>` tag:
121121

122122
```heex
123-
<p>Locale: <%= @locale %></p>
123+
<p>Locale: {@locale}</p>
124124
```
125125

126126
Go to [http://localhost:4000/](http://localhost:4000/) and you should see the locale exhibited. Visit [http://localhost:4000/?locale=fr](http://localhost:4000/?locale=fr) and you should see the assign changed to `"fr"`. Someone can use this information alongside [Gettext](https://hexdocs.pm/gettext/Gettext.html) to provide a fully internationalized web application.

guides/real_time/presence.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,7 @@ defmodule HelloWeb.OnlineLive do
217217
def render(assigns) do
218218
~H"""
219219
<ul id="online_users" phx-update="stream">
220-
<li :for={{dom_id, %{id: id, metas: metas}} <- @streams.presences} id={dom_id}><%= id %> (<%= length(metas) %>)</li>
220+
<li :for={{dom_id, %{id: id, metas: metas}} <- @streams.presences} id={dom_id}>{id} ({length(metas)})</li>
221221
</ul>
222222
"""
223223
end

guides/request_lifecycle.md

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ Now that we've got the route, controller, view, and template, we should be able
181181
There are a couple of interesting things to notice about what we just did. We didn't need to stop and restart the server while we made these changes. Yes, Phoenix has hot code reloading! Also, even though our `index.html.heex` file consists of only a single `section` tag, the page we get is a full HTML document. Our index template is actually rendered into layouts: first it renders `lib/hello_web/components/layouts/root.html.heex` which renders `lib/hello_web/components/layouts/app.html.heex` which finally includes our content. If you open those files, you'll see a line that looks like this at the bottom:
182182

183183
```heex
184-
<%= @inner_content %>
184+
{@inner_content}
185185
```
186186

187187
This line injects our template into the layout before the HTML is sent off to the browser. We will talk more about layouts in the Controllers guide.
@@ -273,15 +273,17 @@ It's good to remember that the keys of the `params` map will always be strings,
273273

274274
For the last piece of this puzzle, we'll need a new template. Since it is for the `show` action of `HelloController`, it will go into the `lib/hello_web/controllers/hello_html` directory and be called `show.html.heex`. It will look surprisingly like our `index.html.heex` template, except that we will need to display the name of our messenger.
275275

276-
To do that, we'll use the special HEEx tags for executing Elixir expressions: `<%= %>`. Notice that the initial tag has an equals sign like this: `<%=` . That means that any Elixir code that goes between those tags will be executed, and the resulting value will replace the tag in the HTML output. If the equals sign were missing, the code would still be executed, but the value would not appear on the page.
276+
To do that, we'll use the special HEEx tags for executing Elixir expressions: `{...}` and `<%= %>`. Notice that EEx tag has an equals sign like this: `<%=` . That means that any Elixir code that goes between those tags will be executed, and the resulting value will replace the tag in the HTML output. If the equals sign were missing, the code would still be executed, but the value would not appear on the page.
277277

278-
Remember our templates are written in HEEx (HTML+EEx). HEEx is a superset of EEx which is why it shares the `<%= %>` syntax.
278+
Remember our templates are written in HEEx (HTML+EEx). HEEx is a superset of EEx, and thereby supports the EEx `<%= %>` interpolation syntax for interpolating arbitrary blocks of code. In general, the HEEx `{...}` interpolation syntax is preferred anytime there is HTML-aware intepolation to be done – such as within attributes or inline values with a body.
279279

280-
And this is what the template should look like:
280+
The only times `EEx` `<%= %>` interpolation is necessary is for interpolationg arbitrary blocks of markup, such as branching logic that inects separate markup trees, or for interpolating values within `<script>` or `<style>` tags.
281+
282+
This is what the `hello_html/show.html.heex` template should look like:
281283

282284
```heex
283285
<section>
284-
<h2>Hello World, from <%= @messenger %>!</h2>
286+
<h2>Hello World, from {@messenger}!</h2>
285287
</section>
286288
```
287289

installer/lib/phx_new/generator.ex

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -465,13 +465,13 @@ defmodule Phx.New.Generator do
465465
# ## Examples
466466
#
467467
# iex> ~s|<tag>#{maybe_eex_gettext("Hello", true)}</tag>|
468-
# ~S|<tag><%= gettext("Hello") %></tag>|
468+
# ~S|<tag>{gettext("Hello")}</tag>|
469469
#
470470
# iex> ~s|<tag>#{maybe_eex_gettext("Hello", false)}</tag>|
471471
# ~S|<tag>Hello</tag>|
472472
def maybe_eex_gettext(message, gettext?) do
473473
if gettext? do
474-
~s|<%= gettext(#{inspect(message)}) %>|
474+
~s|{gettext(#{inspect(message)})}|
475475
else
476476
message
477477
end

installer/templates/phx_single/mix.exs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,7 @@ defmodule <%= @app_module %>.MixProject do
4343
{<%= inspect @adapter_app %>, ">= 0.0.0"},<% end %><%= if @html do %>
4444
{:phoenix_html, "~> 4.1"},
4545
{:phoenix_live_reload, "~> 1.2", only: :dev},
46-
# TODO bump on release to {:phoenix_live_view, "~> 1.0.0"},
47-
{:phoenix_live_view, "~> 1.0.0-rc.1", override: true},
46+
{:phoenix_live_view, "~> 1.0.0"},
4847
{:floki, ">= 0.30.0", only: :test},<% end %><%= if @dashboard do %>
4948
{:phoenix_live_dashboard, "~> 0.8.3"},<% end %><%= if @javascript do %>
5049
{:esbuild, "~> 0.8", runtime: Mix.env() == :dev},<% end %><%= if @css do %>

installer/templates/phx_umbrella/apps/app_name_web/mix.exs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,7 @@ defmodule <%= @web_namespace %>.MixProject do
4141
{:phoenix_ecto, "~> 4.5"},<% end %><%= if @html do %>
4242
{:phoenix_html, "~> 4.1"},
4343
{:phoenix_live_reload, "~> 1.2", only: :dev},
44-
# TODO bump on release to {:phoenix_live_view, "~> 1.0.0"},
45-
{:phoenix_live_view, "~> 1.0.0-rc.1", override: true},
44+
{:phoenix_live_view, "~> 1.0.0"},
4645
{:floki, ">= 0.30.0", only: :test},<% end %><%= if @dashboard do %>
4746
{:phoenix_live_dashboard, "~> 0.8.3"},<% end %><%= if @javascript do %>
4847
{:esbuild, "~> 0.8", runtime: Mix.env() == :dev},<% end %><%= if @css do %>

installer/templates/phx_umbrella/mix.exs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,7 @@ defmodule <%= @root_app_module %>.MixProject do
2727
[
2828
<%= if @dev or @phoenix_version.pre != [] do %><%= @phoenix_dep_umbrella_root %>,
2929
<% end %># Required to run "mix format" on ~H/.heex files from the umbrella root
30-
# TODO bump on release to {:phoenix_live_view, ">= 0.0.0"},
31-
{:phoenix_live_view, "~> 1.0.0-rc.1", override: true}
30+
{:phoenix_live_view, ">= 0.0.0"}
3231
]<% else %>
3332
[]<% end %>
3433
end

0 commit comments

Comments
 (0)