View Source Navigator behaviour (navigator v0.1.0)
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 be0.2.x
usage
Usage
Add library to dependencies
{:navigator, "~> 0.1", hex: :live_navigator}
Add Navigator
to your application supervisor
children = [
...
Navigator,
...
]
Add Navigator.Plug
to your browser pipeline:
defmodule YourWebApp.Router do
pipeline :browser do
...
Navigator.Plug
end
end
To detect different browser tabs Navigator needs to run some code on the
client side. So add the following to your assets/package.json
:
"navigator": "file:../../../deps/navigator",
And to your assets/app.js
:
import { initNavigator } from './navigator';
const liveSocket = new LiveSocket('/live', Socket, initNavigator({
// your phoenix LiveSocket params here
}));
Then use Navigator
in your live views and Navigator.Component
in your
live components (Navigator.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 Navigator
...
end
defmodule YourWebApp.Components.NavBar do
use YourWebApp, :live_component
use Navigator.Component
...
end
In case you are going to use Navigator 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 Navigator
end
end
def live_component do
quote do
use Phoenix.LiveComponent
use Navigator.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. Navigator
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
Navigator.
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 Navigator
. Arguments are the same as
in &handle_page_enter/4
except that the last argument is the Navigator
object instead of Socket
. This function must return
{:noreply, Navigator.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.
Functions
Returns a specification to start this module under a supervisor.
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) => Navigator.History.name(), optional(:to) => non_neg_integer() | atom() }
@type assigns() :: map()
@type back_index() :: Navigator.History.index() | Navigator.History.spec() | {Navigator.History.index() | Navigator.History.spec(), boolean()}
@type changes() :: [atom()]
@type field() ::
:session_id | :tab | :url | :view | :action | :awaiting | :history | :assigns
@type history_action() :: :put | :stack | :stack_add | {:replace, Navigator.History.index(), boolean()}
@type session_id() :: binary()
@type t() :: %Navigator{ __changed__: changes(), action: action() | nil, assigns: assigns(), awaiting: tuple() | nil, history: Navigator.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
@callback handle_page_enter( action_spec(), Navigator.History.spec() | nil, Navigator.History.spec(), Phoenix.LiveView.Socket.t() ) :: {:noreply, Phoenix.LiveView.Socket.t()}
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.
@callback handle_page_leave( action_spec(), Navigator.History.spec(), Navigator.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 Navigator
. Arguments are the same as
in &handle_page_enter/4
except that the last argument is the Navigator
object instead of Socket
. This function must return
{:noreply, Navigator.t}
tuple
@callback handle_page_refresh(Navigator.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
@spec assign_page(Phoenix.LiveView.Socket.t(), keyword() | map()) :: Phoenix.LiveView.Socket.t()
@spec assign_page(t(), keyword() | map()) :: t()
@spec assign_page(Phoenix.LiveView.Socket.t(), atom(), any()) :: Phoenix.LiveView.Socket.t()
@spec assign_page(t(), atom(), any()) :: t()
@spec assign_page_new(Phoenix.LiveView.Socket.t(), atom(), assign_new_callback()) :: Phoenix.LiveView.Socket.t()
Returns a specification to start this module under a supervisor.
See Supervisor
.
@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
@spec handle_params(map(), binary(), Phoenix.LiveView.Socket.t()) :: {:cont | :halt, Phoenix.LiveView.Socket.t()}
@spec history(Phoenix.LiveView.Socket.t() | t()) :: Navigator.History.t()
@spec on_mount(any(), map(), map(), Phoenix.LiveView.Socket.t()) :: {:cont, Phoenix.LiveView.Socket.t()}
@spec push_patch( Phoenix.LiveView.Socket.t(), keyword() ) :: Phoenix.LiveView.Socket.t()
@spec push_redirect( Phoenix.LiveView.Socket.t(), keyword() ) :: Phoenix.LiveView.Socket.t()
@spec redirect( Phoenix.LiveView.Socket.t(), keyword() ) :: Phoenix.LiveView.Socket.t()
@spec start_link(keyword()) :: Supervisor.on_start()