diff --git a/assets/js/phoenix_live_view/constants.js b/assets/js/phoenix_live_view/constants.js index 34b9a31e5c..f17c837cd0 100644 --- a/assets/js/phoenix_live_view/constants.js +++ b/assets/js/phoenix_live_view/constants.js @@ -37,6 +37,7 @@ export const PHX_ERROR_CLASS = "phx-error"; export const PHX_CLIENT_ERROR_CLASS = "phx-client-error"; export const PHX_SERVER_ERROR_CLASS = "phx-server-error"; export const PHX_PARENT_ID = "data-phx-parent-id"; +export const PHX_AUTO_CONNECT = "data-phx-auto-connect"; export const PHX_MAIN = "data-phx-main"; export const PHX_ROOT_ID = "data-phx-root-id"; export const PHX_VIEWPORT_TOP = "viewport-top"; diff --git a/assets/js/phoenix_live_view/live_socket.js b/assets/js/phoenix_live_view/live_socket.js index 9482c97131..13dc95c98d 100644 --- a/assets/js/phoenix_live_view/live_socket.js +++ b/assets/js/phoenix_live_view/live_socket.js @@ -20,6 +20,7 @@ import { PHX_PARENT_ID, PHX_VIEW_SELECTOR, PHX_ROOT_ID, + PHX_AUTO_CONNECT, PHX_THROTTLE, PHX_TRACK_UPLOADS, PHX_SESSION, @@ -417,7 +418,7 @@ export default class LiveSocket { let rootsFound = false; DOM.all( document, - `${PHX_VIEW_SELECTOR}:not([${PHX_PARENT_ID}])`, + `${PHX_VIEW_SELECTOR}:not([${PHX_PARENT_ID}]):not([${PHX_AUTO_CONNECT}="false"])`, (rootEl) => { if (!this.getRootById(rootEl.id)) { const view = this.newRootView(rootEl); diff --git a/lib/phoenix_live_view.ex b/lib/phoenix_live_view.ex index a64a15b6ff..74a5c7e147 100644 --- a/lib/phoenix_live_view.ex +++ b/lib/phoenix_live_view.ex @@ -217,6 +217,15 @@ defmodule Phoenix.LiveView do this option will override any layout previously set via `Phoenix.LiveView.Router.live_session/2` or on `use Phoenix.LiveView` + * `:auto_connect` - if false, instructs the LiveView JavaScript client + to not automatically connect to the server on disconnected render. + This is useful when you have a static page that does not require + any connected functionality, but should render over the existing + connection when navigating from an already connected LiveView. + Defaults to `true`. When navigating from a page that has `auto_connect: false` + and is not connected, all navigations perform a regular `window.location` + update, triggering a full page reload. + """ @callback mount( params :: unsigned_params() | :not_mounted_at_router, diff --git a/lib/phoenix_live_view/static.ex b/lib/phoenix_live_view/static.ex index 4ec586e71e..e283002ce6 100644 --- a/lib/phoenix_live_view/static.ex +++ b/lib/phoenix_live_view/static.ex @@ -161,6 +161,12 @@ defmodule Phoenix.LiveView.Static do data_attrs = if(router, do: [phx_main: true], else: []) ++ data_attrs + data_attrs = + if(not Map.get(socket.private, :auto_connect, true), + do: [phx_auto_connect: "false"], + else: [] + ) ++ data_attrs + attrs = [ {:id, socket.id}, {:data, data_attrs} diff --git a/lib/phoenix_live_view/utils.ex b/lib/phoenix_live_view/utils.ex index 245a89428b..2afc38c084 100644 --- a/lib/phoenix_live_view/utils.ex +++ b/lib/phoenix_live_view/utils.ex @@ -6,7 +6,7 @@ defmodule Phoenix.LiveView.Utils do alias Phoenix.LiveView.{Socket, Lifecycle} # All available mount options - @mount_opts [:temporary_assigns, :layout] + @mount_opts [:temporary_assigns, :layout, :auto_connect] @max_flash_age :timer.seconds(60) @@ -437,6 +437,14 @@ defmodule Phoenix.LiveView.Utils do } end + defp handle_mount_option(%Socket{} = socket, :auto_connect, value) do + if not is_boolean(value) do + raise "the :auto_connect mount option must be a boolean, got: #{inspect(value)}" + end + + put_in(socket.private[:auto_connect], value) + end + @doc """ Calls the `handle_params/3` callback, and returns the result. diff --git a/test/e2e/support/lifecycle.ex b/test/e2e/support/lifecycle.ex new file mode 100644 index 0000000000..d4fa09d0cd --- /dev/null +++ b/test/e2e/support/lifecycle.ex @@ -0,0 +1,23 @@ +defmodule Phoenix.LiveViewTest.E2E.LifecycleLive do + use Phoenix.LiveView + + @impl Phoenix.LiveView + def mount(params, _session, socket) do + auto_connect = + case params do + %{"auto_connect" => "false"} -> false + _ -> true + end + + {:ok, socket, auto_connect: auto_connect} + end + + @impl Phoenix.LiveView + def render(assigns) do + ~H""" +