View Source LiveNavigator behaviour (live_navigator v0.1.6)

This library improves Phoenix LiveView navigation and adds application state to your application. Often when user refreshes browser or presses back button you loose current live view state. Also when user navigates through live views you can't get this navigation path (for example from which page user reached current page). This library solves both of these problems.

WARNGING! The 0.1.x version is not production ready yet. The nearest production ready version would be 0.2.x

usage

Usage

Add library to dependencies

{:live_navigator, "~> 0.1"}

Add LiveNavigator to your application supervisor

children = [
  ...
  LiveNavigator,
  ...
]

Add LiveNavigator.Plug to your browser pipeline:

defmodule YourWebApp.Router do
  pipeline :browser do
    ...
    LiveNavigator.Plug
  end
end

To detect different browser tabs LiveNavigator needs to run some code on the client side. So add the following to your assets/package.json:

"live_navigator": "file:../../../deps/live_navigator",

And to your assets/app.js:

import { initNavigator } from './live_navigator';

const liveSocket = new LiveSocket('/live', Socket, initNavigator({
  // your phoenix LiveSocket params here
}));

Then use LiveNavigator in your live views and LiveNavigator.Component in your live components (LiveNavigator.Component is necessary only in those components that are doing any kind of redirection or navigator/page assignments)

defmodule YourWebApp.ExampleLive do
  use YourWebApp, :live_view
  use LiveNavigator
  ...
end

defmodule YourWebApp.Components.NavBar do
  use YourWebApp, :live_component
  use LiveNavigator.Component
  ...
end

In case you are going to use LiveNavigator for entire application it's better to place usage into your app definitions:

defmodule YourWebApp do
  def liver_view do
    quote do
      use Phoenix.LiveView
      use LiveNavigator
    end
  end

  def live_component do
    quote do
      use Phoenix.LiveComponent
      use LiveNavigator.Component
    end
  end
end

Now you have few additional assign functions in your live views: &assign_page/3, &assign_nav/3, &assign_page_new/3, &assign_nav_new/3, &clear_page/2 and &clear_nav/3. All of them works with assigns. LiveNavigator introduces two application states: navigator state and page state. Navigator state is a global application state that is unique for live views opened from deffierent browser tab and different HTTP session (that is set up via cookies usualy). Page state is a state that unique for different live view module and live view action and with above conditions for navigator state. In other words when user opens in browser one of your pages, lets say page A the new navigator and page states created. That he navigates to different page B new page state created (but page state A is not deleted). If he then backs to page A it's state will be loaded into your assigns. While navigator state stay same for all above operations. If user then opens any page in new browser tab then all states including navigator state will be created from sсratch.

To control user navigation you now has several additional callbacks in your live views: &handle_page_refresh/2, &handle_page_leave/4 and &handle_page_enter/4. &handle_page_refresh/2 will be called if user refreshes browser and stay on the same page. &handle_page_enter/4 called when user enters the page from other location. And &handle_page_leave/4 called when user going to another location. Note that while &handle_page_refresh/2 and &handle_page_enter/4 are called before standard handle_params callback and executed in live view process, but &handle_page_leave/4 called in process of live view where user went and the only save functions here is the assign-related functions provided by LiveNavigator.

Link to this section Summary

Callbacks

Called when user moves to this page from another. The first argument is the action that leaded the event (see action_spec for details), the second argument is the view specification of the page the user comes from, the third argument is the view specification of the page the user comes to (which is current view in this case) and the fourth is the socket. This function must return {:noreply, Socket.t} tuple.

Called when user moves from another page to this one. This function unlike &handle_page_refresh/2 and &handle_page_enter/4 called from another process and have no access to socket. The only safe functions here is the assets-related functions provided by LiveNavigator. Arguments are the same as in &handle_page_enter/4 except that the last argument is the LiveNavigator object instead of Socket. This function must return {:noreply, LiveNavigator.t} tuple

Called when user refreshes the page in browser. The first argument is the page view specification and the second is the socket. This function must return {:noreply, Socket.t} tuple.

Link to this section Types

@type action() :: atom()
@type action_method() :: :navigate | :patch | :redirect | :browse
@type action_spec() :: %{
  :method => action_method(),
  :action => history_action(),
  optional(:as) => LiveNavigator.History.name(),
  optional(:to) => non_neg_integer() | atom()
}
@type assign_new_callback() :: (() -> any()) | (map() -> any())
@type assigns() :: map()
@type changes() :: [atom()]
@type field() ::
  :session_id
  | :tab
  | :url
  | :view
  | :action
  | :awaiting
  | :history
  | :assigns
  | :fallback_url
@type history_action() ::
  :put
  | :stack
  | :stack_add
  | {:replace, LiveNavigator.History.index(), boolean()}
@type session_id() :: binary()
@type t() :: %LiveNavigator{
  __changed__: changes(),
  action: action() | nil,
  assigns: assigns(),
  awaiting: tuple() | nil,
  fallback_url: url() | nil,
  history: LiveNavigator.History.t(),
  session_id: session_id(),
  tab: tab(),
  url: url() | nil,
  view: view() | nil
}
@type tab() :: integer()
@type url() :: binary()
@type view() :: module()

Link to this section Callbacks

Link to this callback

handle_page_enter(action_spec, arg2, spec, t)

View Source

Called when user moves to this page from another. The first argument is the action that leaded the event (see action_spec for details), the second argument is the view specification of the page the user comes from, the third argument is the view specification of the page the user comes to (which is current view in this case) and the fourth is the socket. This function must return {:noreply, Socket.t} tuple.

Link to this callback

handle_page_leave(action_spec, spec, spec, t)

View Source
@callback handle_page_leave(
  action_spec(),
  LiveNavigator.History.spec(),
  LiveNavigator.History.spec(),
  t()
) ::
  {:noreply, t()}

Called when user moves from another page to this one. This function unlike &handle_page_refresh/2 and &handle_page_enter/4 called from another process and have no access to socket. The only safe functions here is the assets-related functions provided by LiveNavigator. Arguments are the same as in &handle_page_enter/4 except that the last argument is the LiveNavigator object instead of Socket. This function must return {:noreply, LiveNavigator.t} tuple

Link to this callback

handle_page_refresh(spec, t)

View Source
@callback handle_page_refresh(LiveNavigator.History.spec(), Phoenix.LiveView.Socket.t()) ::
  {:noreply, Phoenix.LiveView.Socket.t()}

Called when user refreshes the page in browser. The first argument is the page view specification and the second is the socket. This function must return {:noreply, Socket.t} tuple.

Link to this section Functions

Link to this function

assign_nav(socket, assigns)

View Source
@spec assign_nav(Phoenix.LiveView.Socket.t(), keyword() | map()) ::
  Phoenix.LiveView.Socket.t()
@spec assign_nav(t(), keyword() | map()) :: t()
Link to this function

assign_nav(socket, key, value)

View Source
@spec assign_nav(Phoenix.LiveView.Socket.t(), atom(), any()) ::
  Phoenix.LiveView.Socket.t()
@spec assign_nav(t(), atom(), any()) :: t()
Link to this function

assign_nav_new(socket, key, setter)

View Source
Link to this function

assign_page(socket, assigns)

View Source
@spec assign_page(Phoenix.LiveView.Socket.t(), keyword() | map()) ::
  Phoenix.LiveView.Socket.t()
@spec assign_page(t(), keyword() | map()) :: t()
Link to this function

assign_page(socket, key, value)

View Source
@spec assign_page(Phoenix.LiveView.Socket.t(), atom(), any()) ::
  Phoenix.LiveView.Socket.t()
@spec assign_page(t(), atom(), any()) :: t()
Link to this function

assign_page_new(socket, key, setter)

View Source

Returns a specification to start this module under a supervisor.

See Supervisor.

@spec clear_nav(Phoenix.LiveView.Socket.t(), atom() | [atom()]) ::
  Phoenix.LiveView.Socket.t()
@spec clear_nav(t(), atom() | [atom()]) :: t()
Link to this function

clear_page(socket, keys)

View Source
@spec clear_page(Phoenix.LiveView.Socket.t(), atom() | [atom()]) ::
  Phoenix.LiveView.Socket.t()
@spec clear_page(t(), atom() | [atom()]) :: t()
@spec current_url(Phoenix.LiveView.Socket.t() | t()) :: binary() | nil
Link to this macro

fallback_url(url)

View Source (macro)
@spec fallback_url(nil | atom() | binary()) :: Macro.t()
Link to this function

handle_event(arg1, params, socket)

View Source
Link to this function

handle_info(arg1, socket)

View Source
Link to this function

handle_params(params, url, socket)

View Source
@spec handle_params(map(), binary(), Phoenix.LiveView.Socket.t()) ::
  {:cont | :halt, Phoenix.LiveView.Socket.t()}
Link to this function

on_mount(args, params, arg3, socket)

View Source
@spec on_mount(any(), map(), map(), Phoenix.LiveView.Socket.t()) ::
  {:cont, Phoenix.LiveView.Socket.t()}
Link to this function

push_navigate(socket, opts)

View Source
Link to this function

push_patch(socket, opts)

View Source
Link to this function

push_redirect(socket, opts)

View Source
This function is deprecated. Use push_navigate/2 instead.
Link to this function

set_fallback_url(socket, url)

View Source
@spec set_fallback_url(Phoenix.LiveView.Socket.t(), url() | nil) ::
  Phoenix.LiveView.Socket.t()
@spec set_fallback_url(t(), url() | nil) :: t()
@spec start_link(keyword()) :: Supervisor.on_start()
Link to this function

update(navigator, fields)

View Source
@spec update(t(), map() | keyword()) :: t()
Link to this function

update(navigator, field, value)

View Source
@spec update(t(), field(), any()) :: t()