replaceTransport to LiveSocketphx-blur eventsphx-update valuephx-change on individual inputsMouseEvent on client:bubbles option to JS.dispatch to control event bubblingliveSocket instance on hookscsrf_token for forms without action, reducing the payload during phx-change/phx-submit eventslive_redirect links are clicked when not connected in certain casesJS.set_attribute and JS.remove_attribute
sticky: true option to live_render to maintain a nested child on across live redirectsphx:show-start, phx:show-end, phx:hide-start and phx:hide-end on JS.show|hide|toggle
get_connect_info/2 that also works on disconnected renderLiveSocket constructor options for configuration failsafe behavior via new maxReloads, reloadJitterMin, reloadJitterMax, failsafeJitter optionsphx-disable-with failing to disable elements outside of formsphx-click-away not respecting phx-target
JS.push with :target failing to send to correct component in certain casesPhoenix.LiveView.get_connect_info/1 in favor of get_connect_info/2
Phoenix.LiveViewTest.put_connect_info/2 in favor of calling the relevant functions in Plug.Conn
Phoenix.LiveView.consume_uploaded_entry/3 and Phoenix.LiveView.consume_uploaded_entries/3. The callback must return either {:ok, value} or {:postpone, value}. Returning any other value will emit a warning.phx-click-away if element is not visiblephx-remove failing to tear down nested live childrento target for JS.show|hide|dispatch
phx binding interactionson_mount changesThe hook API introduced in LiveView 0.16 has been improved based on feedback. LiveView 0.17 removes the custom module-function callbacks for the Phoenix.LiveView.on_mount/1 macro and the :on_mount option for Phoenix.LiveView.Router.live_session/3 in favor of supporting a custom argument. For clarity, the module function to be invoked during the mount lifecycle stage will always be named on_mount/4.
For example, if you had invoked on_mount/1 like so:
on_mount MyAppWeb.MyHook
on_mount {MyAppWeb.MyHook, :assign_current_user}
and defined your callbacks as:
# my_hook.ex def mount(_params, _session, _socket) do end def assign_current_user(_params, _session, _socket) do end
Change the callback to:
# my_hook.ex def on_mount(:default, _params, _session, _socket) do end def on_mount(:assign_current_user, _params, _session, _socket) do end
When given only a module name, the first argument to on_mount/4 will be the atom :default.
Stateful LiveComponents (where an :id is given) must now return HEEx templates (~H sigil or .heex extension). LEEx templates (~L sigil or .leex extension) are no longer supported. This addresses bugs and allows stateful components to be rendered more efficiently client-side.
phx-disconnected class has been replaced with phx-loading
Due to a bug in the newly released Safari 15, the previously used .phx-disconnected class has been replaced by a new .phx-loading class. The reason for the change is phx.new included a .phx-disconnected rule in the generated app.css which triggers the Safari bug. Renaming the class avoids applying the erroneous rule for existing applications. Folks can upgrade by simply renaming their .phx-disconnected rules to .phx-loading.
phx-capture-click has been deprecated in favor of phx-click-away
The new phx-click-away binding replaces phx-capture-click and is much more versatile because it can detect "click focus" being lost on containers.
Some functionality that was previously deprecated has been removed:
live_component do-blocks is no longer supported@socket to live_component will now raise if possible<:slot_name> and can be rendered with <%= render_slot @slot_name %>
JS command for executing JavaScript utility operations on the client with an extended push API<div class={"foo bar #{@baz}"}>, only the interpolation part is marked as dynamicPhoenix.LiveComponent. Instead of <%= live_component FormComponent, id: "form" %>, you must now do: <.live_component module={FormComponent} id="form" />
push_redirect from mount<img> tags with srcset and video with autoplay to fail to renderlive_patch/live_redirect. This mirrors the behaviour of the JavaScript clientlive_redirects<%= live_component MyModule, id: @user.id, user: @user %> is deprecated in favor of <.live_component module={MyModule} id={@user.id} user={@user} />. Notice the new API requires using HEEx templates. This change allows us to further improve LiveComponent and bring new features such as slots to them.render_block/2 in deprecated in favor of render_slot/2
handle_params to be defined for lifecycle hooksjoin_ref on messageson_mount hooks calling view mount before redirecting when the hook issues a halt redirect.@inner_block is missingphx-change form recovery event being sent to wrong component on reconnect when component order changesphoenix_html dependency requirementPhoenix.LiveViewTest.render_component/3
on_mount declarationsLiveView v0.16 optimizes live redirects by supporting navigation purely over the existing WebSocket connection. This is accomplished by the new live_session/3 feature of Phoenix.LiveView.Router. The security guide has always stressed the following:
... As we have seen, LiveView begins its life-cycle as a regular HTTP request. Then a stateful connection is established. Both the HTTP request and the stateful connection receives the client data via parameters and session. This means that any session validation must happen both in the HTTP request (plug pipeline) and the stateful connection (LiveView mount) ...
These guidelines continue to be valid, but it is now essential that the stateful connection enforces authentication and session validation within the LiveView mount lifecycle because a live_redirect from the client will not go through the plug pipeline as a hard-refresh or initial HTTP render would. This means authentication, authorization, etc that may be done in the Plug.Conn pipeline must also be performed within the LiveView mount lifecycle.
Live sessions allow you to support a shared security model by allowing live_redirects to only be issued between routes defined under the same live session name. If a client attempts to live redirect to a different live session, it will be refused and a graceful client-side redirect will trigger a regular HTTP request to the attempted URL.
See the Phoenix.LiveView.Router.live_session/3 docs for more information and example usage.
LiveView v0.16 introduces HEEx (HTML + EEx) templates and the concept of function components via Phoenix.Component. The new HEEx templates validate the markup in the template while also providing smarter change tracking as well as syntax conveniences to make it easier to build composable components.
A function component is any function that receives a map of assigns and returns a ~H template:
defmodule MyComponent do
use Phoenix.Component
def btn(assigns) do
~H"""
<button class="btn"><%= @text %></button>
"""
end
end
This component can now be used as in your HEEx templates as:
<MyComponent.btn text="Save">
The introduction of HEEx and function components brings a series of deprecation warnings, some introduced in this release and others which will be added in the future. Note HEEx templates require Elixir v1.12+.
The main deprecation in this release is that the ~L sigil and the .leex extension are now soft-deprecated. The docs have been updated to discourage them and using them will emit warnings in future releases. We recommend using the ~H sigil and the .heex extension for all future templates in your application. You should also plan to migrate the old templates accordingly using the recommendations below.
Migrating from LEEx to HEEx is relatively straightforward. There are two main differences. First of all, HEEx does not allow interpolation inside tags. So instead of:
<div id="<%= @id %>"> ... </div>
One should use the HEEx syntax:
<div id={@id}>
...
</div>
The other difference is in regards to form_for. Some templates may do the following:
~L""" <%= f = form_for @changeset, "#" %> <%= input f, :foo %> </form> """
However, when converted to ~H, it is not valid HTML: there is a </form> tag but its opening is hidden inside the Elixir code. On LiveView v0.16, there is a function component named form:
~H"""
<.form let={f} for={@changeset}>
<%= input f, :foo %>
</.form>
"""
We understand migrating all templates from ~L to ~H can be a daunting task. Therefore we plan to support ~L in LiveViews for a long time. However, we can't do the same for stateful LiveComponents, as some important client-side features and optimizations will depend on the ~H sigil. Therefore our recommendation is to replace ~L by ~H first in live components, particularly stateful live components.
Furthermore, stateless live_component (i.e. live components without an :id) will be deprecated in favor of the new function components. Our plan is to support them for a reasonable period of time, but you should avoid creating new ones in your application.
LiveView 0.16 removes the :layout and :container options from Phoenix.LiveView.Routing.live/4 in favor of the :root_layout and :container options on Phoenix.Router.live_session/3.
For instance, if you have the following in LiveView 0.15 and prior:
live "/path", MyAppWeb.PageLive, layout: {MyAppWeb.LayoutView, "custom_layout.html"}
Change it to:
live_session :session_name, root_layout: {MyAppWeb.LayoutView, "custom_layout.html"} do
live "/path", MyAppWeb.PageLive
end
On the client, the phoenix_live_view package no longer provides a default export for LiveSocket.
If you have the following in your JavaScript entrypoint (typically located at assets/js/app.js):
import LiveSocket from "phoenix_live_view"
Change it to:
import { LiveSocket } from "phoenix_live_view"
Additionally on the client, the root LiveView element no longer exposes the LiveView module name, therefore the phx-view attribute is never set. Similarly, the viewName property of client hooks has been removed.
Codebases calling a custom function component/3 should rename it or specify its module to avoid a conflict, as LiveView introduces a macro with that name and it is special cased by the underlying engine.
Phoenix.Component
Phoenix.Router.live_session/3 for optimized live redirectson_mount and attach_hook hooks which provide a mechanism to tap into key stages of the LiveView lifecyclelive_img_preview renderingform function component which wraps Phoenix.HTML.form_for
with_target for scoping components directlyrefute_redirected
phx-target values to mirror JS clientfollow_trigger_action
sessionStorage option LiveSocket constructor to support client storage overridesrender_component to ensure all relevant callbacks are invokedPhoenix.LiveViewTest.page_title returning nil in some caseshandle_params within the same callback lifecyclephx-no-feedback class not applied when page is live-patchedDOMException, querySelector, not a valid selector when performing DOM lookups on non-standard IDslive_file_input in one formshowError causing null querySelector
Phoenix.LiveView.Diff.many_to_iodata
do-end block to live_component is deprecated~L sigil and the .leex extension are now soft-deprecated in favor of ~H and .heex
live_component call without an :id) are deprecated in favor of the new function component featurehandle_params
LiveViewTest.render_upload/3 when using channel uploads and progress callbackKeyError on LiveView reconnect when an active upload was previously in progresscomponent/3
@socket/socket argument on live_component/3 callupload_errors/1 for returning top-level upload errorsconsume_uploaded_entry/3 with external uploads causing inconsistent entries statepush_event losing events when a single diff produces multiple events from different componentslive_render's causing remound of child LiveView even when ID does not changeauto_upload: true to fail to submit formmax_entries is 1 instead of accumulating multiple file selectionsstatic_path in open_browser failing to load stylesheetspush_redirect back causing timeout on the clientbeforeDestroy from phx-hook callbacksreconnected callback being fired with no prior disconnectconsume_uploaded_entries in LiveView caller processphx-capture-click triggering on mouseup during text selectionpush_event's not clearing up in components<textarea> being patched by LiveView while focusedPhoenix.LiveViewTest.open_browser/2 that opens up a browser with the LiveView page@inner_content in components and introduce render_block for rendering component @inner_block
@live_module in socket templates in favor of @socket.view
push_redirect from mount hanging on the client and causing a fallback to full page reload when following a clicked live_redirect on the clientredirect(socket, external: ...) when returned from an eventPhoenix.LiveViewTest when phx-update has non-HTML nodes as childrenphx_trigger_action submitting the form before the DOM updates are completephx-trigger-action causing reconnects before server form submitPhoenix.LiveView.send_update_after/3
live_redirect'sPhoenix.LiveViewTest
render_layout with LiveEEx"_target" input name contains array@inner_content causing no function clause matching in Keyword.put/3 errorLiveViewTest failing to patch children properly for append/prepend based phx-update's:as option to a live routephx-disable-with content to not be updated<textarea>'s failing to be disabled on form submitphx-update append/prepend:router to be given to render_component
~L
phx-update append/prepend based DOM updateslive_render's failing to be torn down when removed from the DOM in certain casesdocument.activeElement creates a null referenceliveSocket.disconnect() followed by liveSocket.connect()
error_tag failing to be displayed for non-text based inputs such as selects and checkboxes as the phx-no-feedback class was always appliedphx-error class being applied on live_redirect
__MODULE__
phx-static not being sent from the client for child viewsbeforeDestroy is called on hooks in children of a removed elementmount, handle_params, and handle_event
push_event for pushing events and data from the server to the clienthandleEvent hook method for receiving events pushed from the serverpushEvent from the server via {:reply, map, socket}
assigns.myself a struct to catch mistakessend_update, raise if module is unavailablelive_redirected route results in a redirect to a non-live route on the serverdom option to LiveSocket with onBeforeElUpdated callback for external client library support of broad DOM operationspush_redirect from a live_redirect
LiveSocket level. To maintain backwards compatibility with pre-0.13 behaviour, you can provide the following metadata option: let liveSocket = new LiveSocket("/live", Socket, {
params: {_csrf_token: csrfToken},
metadata: {
click: (e, el) => {
return {
altKey: e.altKey,
shiftKey: e.shiftKey,
ctrlKey: e.ctrlKey,
metaKey: e.metaKey,
x: e.x || e.clientX,
y: e.y || e.clientY,
pageX: e.pageX,
pageY: e.pageY,
screenX: e.screenX,
screenY: e.screenY,
offsetX: e.offsetX,
offsetY: e.offsetY,
detail: e.detail || 1,
}
},
keydown: (e, el) => {
return {
altGraphKey: e.altGraphKey,
altKey: e.altKey,
code: e.code,
ctrlKey: e.ctrlKey,
key: e.key,
keyIdentifier: e.keyIdentifier,
keyLocation: e.keyLocation,
location: e.location,
metaKey: e.metaKey,
repeat: e.repeat,
shiftKey: e.shiftKey
}
}
}
})
key
Phoenix.LiveView.get_connect_info/1
Phoenix.LiveViewTest.put_connect_info/2 and Phoenix.LiveViewTest.put_connect_params/2
@myself target to a hook's pushEventTo targetmetadata LiveSocket option"_mounts" key in connect params which specifies the number of times a LiveView has mountedinnerHTML being discarded when a sibling DOM element appears above it, in cases where the component lacks a DOM IDphx-disable-with and other pending attributes failing to be restored when an empty patch is returned by serverThis version of LiveView comes with an overhaul of the testing module, more closely integrating your LiveView template with your LiveView events. For example, in previous versions, you could write this test:
render_click(live_view, "increment_by", %{by: 1})
However, there is no guarantee that there is any element on the page with a phx-click="increment" attribute and phx-value-by set to 1. With LiveView 0.12.0, you can now write:
live_view
|> element("#term .buttons a", "Increment")
|> render_click()
The new implementation will check there is a button at #term .buttons a, with "Increment" as text, validate that it has a phx-click attribute and automatically submit to it with all relevant phx-value entries. This brings us closer to integration/acceptance test frameworks without any of the overhead and complexities of running a headless browser.
assert_patch/3 and assert_patched/2 for asserting on patchesfollow_redirect/3 to automatically follow redirects from render_* eventsphx-trigger-action form annotation to trigger an HTTP form submit on next DOM patchphx-target @myself targeting a sibling LiveView component with the same component IDphx:page-loading-stop firing before the DOM patch has been performedphx-update="prepend" failing to properly patch the DOM when the same ID is updated back to backphx-error-for has been removed in favor of phx-feedback-for. phx-feedback-for will set a phx-no-feedback class whenever feedback has to be hidden
Phoenix.LiveViewTest.children/1 has been renamed to Phoenix.LiveViewTest.live_children/1
Phoenix.LiveViewTest.find_child/2 has been renamed to Phoenix.LiveViewTest.find_live_child/2
Phoenix.LiveViewTest.assert_redirect/3 no longer matches on the flash, instead it returns the flash
Phoenix.LiveViewTest.assert_redirect/3 no longer matches on the patch redirects, use assert_patch/3 instead
Phoenix.LiveViewTest.assert_remove/3 has been removed. If the LiveView crashes, it will cause the test to crash too
Passing a path with DOM IDs to render_* test functions is deprecated. Furthermore, they now require a phx-target="<%= @id %>" on the given DOM ID:
<div id="component-id" phx-target="component-id"> ... </div>
html = render_submit([view, "#component-id"], event, value)
In any case, this API is deprecated and you should migrate to the new element based API.
unless in LiveEEx enginerender_event/render_click and friends now expect a DOM ID selector to be given when working with components. For example, instead of render_click([live, "user-13"]), you should write render_click([live, "#user-13"]), mirroring the phx-target API.@socket.assigns[...] in a template will now raise the exception Phoenix.LiveView.Socket.AssignsNotInSocket. The socket assigns are available directly inside the template as LiveEEx assigns, such as @foo and @bar. Any assign access should be done using the assigns in the template where proper change tracking takes place.defaults option on LiveSocket constructor to configure default phx-debounce and phx-throttle values, allowing <input ... phx-debounce>
detail key to click event metadata for detecting double/triple clicksRemove socket.assigns during render to avoid change tracking bugs. If you were previously relying on passing @socket to functions then referencing socket assigns, pass the explicit assign instead to your functions from the template.
Removed assets/css/live_view.css. If you want to show a progress bar then in app.css, replace
- @import "../../../../deps/phoenix_live_view/assets/css/live_view.css"; + @import "../node_modules/nprogress/nprogress.css";
and add nprogress to assets/package.json. Full details in the Progress animation guide
phx:page-loading-stop
@foo.bar only executes and diffs when bar changes@myself assign, to allow components to target themselves instead of relying on a DOM ID, for example: phx-target="<%= @myself %>"
enableProfiling() and disableProfiling() to LiveSocket for client performance profiling to aid the development process@live_view_module to @live_module
@live_view_action to @live_action
put_live_layout is no longer supported. Instead, use put_root_layout. Note, however, that the layout given to put_root_layout must use @inner_content instead of <%= render(@view_module, @view_template, assigns) %> and that the root layout will also be used by regular views. Check the Live Layouts section of the docs.phx-update="ignore" hook if data attributes have changed.html.leex template of the same basename of the LiveView will be automatically used for render/1
live_title_tag/2 helper for automatic prefix/suffix on @page_title updates:use on child LiveViewsfalse in the router and on mountfetch_flash before):flash is given as an assignsocket.router
MFArgs as the :session option in the live router macrophx-disable-with on live redirect and live patch linksredirect on the serverundefined to string when issuing pushStatePhoenix.LiveView.Flash in favor of :fetch_live_flash imported by Phoenix.LiveView.Router
@inner_content instead of invoking the LiveView directly:stop tuples from LiveView mount or handle_[params|call|cast|info|event] is no longer supported. LiveViews are stopped when issuing a redirect or push_redirect
put_live_layout plug to put the root layout used for live routesredirect and push_redirect from mountphx:page-loading-start and phx:page-loading-stop on window for live navigation, initial page loads, and form submits, for user controlled page loading integrationphx-page-loading to dispatch loading events above when the event is pushedenableLatencySim(milliseconds) and disableLatencySim()
enableDebug() and disableDebug() to LiveSocket for ondemand browser debugging from the web consoletransport_pid/1 to return the websocket transport pid when the socket is connectedlive_redirect would reload the current URL instead of the attempted new URLphx-change event to the server in certain caseslive_redirect was removed in favor of push_patch (for updating the URL and params of the current LiveView) and push_redirect (for updating the URL to another LiveView)live_link was removed in favor of live_patch (for updating the URL and params of the current LiveView) and live_redirect (for updating the URL to another LiveView)Phoenix.LiveViewTest.assert_redirect no longer accepts an anonymous function in favor of executing the code prior to asserting the redirects, just like assert_receive.@live_view_action in LiveViews to simplify tracking of URL statephx-auto-recover form binding for specialized recoverylive_patch and live_redirect
phx-capture-click to use event capturing to bind a click event as it propagates inwards from the targetphx-key binding to scope phx-window-keydown and phx-window-keyup eventsphx-value-* on key eventsupdated hook callbacks on phx-update="ignore" container when the container's attributes have changedphx-update="append" raising ArgumentError in LiveViewTestmount/2 has been deprecated in favor of mount/3. The params are now passed as the first argument to mount/3, followed by the session and socket.handle_params
LiveView now makes the connection session automatically available in LiveViews. However, to do so, you need to configure your endpoint accordingly, otherwise LiveView will fail to connect.
The steps are:
Find plug Plug.Session, ... in your endpoint.ex and move the options ... to a module attribute:
@session_options [ ... ]
Change the plug Plug.Session to use said attribute:
plug Plug.Session, @session_options
Also pass the @session_options to your LiveView socket:
socket "/live", Phoenix.LiveView.Socket, websocket: [connect_info: [session: @session_options]]
You should define the CSRF meta tag inside <head> in your layout, before app.js is included:
<%= csrf_meta_tag() %> <script type="text/javascript" src="<%= Routes.static_path(@conn, "/js/app.js") %>"></script>
Then in your app.js:
let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content");
let liveSocket = new LiveSocket("/live", Socket, {params: {_csrf_token: csrfToken}});
Also note that the session from now on will have string keys. LiveView will warn if atom keys are used.
live_link
beforeUpdate and beforeDestroy JS hooks:layout optionhibernate_after on LiveView processeslive_redirect and redirect
offsetX and offsetY to click event metadatalive_link and live_redirect to exist anywhere in the page and it will always target the main LiveView (the one defined at the router)phx-target="window" has been removed in favor of phx-window-keydown, phx-window-focus, etc, and the phx-target binding has been repurposed for targeting LiveView and LiveComponent events from the clientPhoenix.LiveView no longer defined live_render and live_link. These functions have been moved to Phoenix.LiveView.Helpers which can now be fully imported in your views. In other words, replace import Phoenix.LiveView, only: [live_render: ..., live_link: ...] by import Phoenix.LiveView.Helpers
Phoenix.LiveComponent to compartmentalize state, markup, and events in LiveViewphx-hook destroyed callback failing to be called in certain caseslive_isolated in tests no longer requires a router and a pipeline (it now expects only 3 arguments)handle_params is used on a non-router LiveViewphx-debounce and phx-throttle bindings to rate limit eventsmdn-polyfills/CustomEvent and mdn-polyfills/String.prototype.startsWith
getAttributeNames lookupphx-update append/prepend containers not building proper DOM contentphx-update append/prepend containers not updating existing child containers with matching IDs:container option to use Phoenix.LiveView
live_isolated test helper for testing LiveViews which are not routableconfigure_temporary_assigns/2 with 3-tuple mount return, supporting a :temporary_assigns keyredirect/live_redirect on mount nor in child live viewsphx-update containers now require a unique IDLiveSocket JavaScript constructor now requires explicit dependency injection of Phoenix Socket constructor. For example:import {Socket} from "phoenix"
import LiveSocket from "phoenix_live_view"
let liveSocket = new LiveSocket("/live", Socket, {...})
phx-update=append/prepend failing to join new nested live views or wire up new phx-hooks"focus"
phx-value has no effect, use phx-value-* instead:path_params key in session has no effect (use handle_params in LiveView instead)insertAdjacentHTML for faster append/prepend and proper css animation handlinghandle_in/3 callback now receives a map of metadata about the client eventphx-change events, handle_in/3 now receives a "_target" param representing the keyspace of the form input name which triggered the changephx-value- prefix, such as phx-value-myval1, phx-value-myval2, etcphx-update, which can be set to "replace", "append", "prepend" or "ignore"
phx-ignore was renamed to phx-update="ignore"
© 2018 Chris McCord
Licensed under the MIT License.
https://hexdocs.pm/phoenix_live_view/changelog.html