LiveAttribute (LiveAttribute v1.1.4) View Source

LiveAttribute makes binding updateable values easier. To use it add it to your LiveView using use LiveAttribute and then use the function assign_attribute(socket, subscribe_callback, property_callbacks) to register attributes.

The attributes will listen to all incoming events and update their assigns of your LiveView automatically, saving you the hassle of implementing independent handle_info() and update_...() calls.

Example using LiveAttribute

defmodule UserLive do
  use Phoenix.LiveView
  use LiveAttribute

  def mount(_params, _session, socket) do
    {:ok, assign_attribute(socket, &User.subscribe/0, users: &User.list_users/0)}
  end

  def handle_event("delete_user", %{"id" => user_id}, socket) do
    User.get_user!(user_id)
    |> User.delete_user()

    {:noreply, socket}
  end
end

Same Example without LiveAttribute

defmodule UserLive do
  use Phoenix.LiveView

  def mount(_params, _session, socket) do
    if connected?(socket), do: User.subscribe()
    {:ok, update_users(socket)}
  end

  defp update_users(socket) do
    users = User.list_users()
    assign(socket, users: users)
  end

  def handle_event("delete_user", %{"id" => user_id}, socket) do
    User.get_user!(user_id)
    |> User.delete_user()

    {:noreply, socket}
  end

  def handle_info({User, [:user, _], _}, socket) do
    {:noreply, update_users(socket)}
  end
end

assign_attribute(socket, subscribe, filter \ :_, refresher)

  • socket the LiveView socket where the assigns should be executed on
  • subscribe the subscribe callback to start the subscription e.g. &Users.subscribe/0
  • filter an optional filter if you don't want to update on each event. The filter can either be an expression using :_ as wildcard parameter such as {User, [:user, :_], :_}. Alternatively filter can be a function with one parameter Note LiveAttribute is issuing each subscribe call in an isolated helper process, so you only need to add filters to reduce the scope of a single subscription.
  • refresher the function callback to load the new values after a subscription event has fired.

Link to this section Summary

Types

The filter allows doing optimzation by a) ignoring certain events of the subscription source and b) pass event values directly to assign values, instead of using refresher functions to re-load them.

The refresher list is passed to assign_attribute() to know which assigns to update when the subscription source issues an event.

Functions

Shortcut version of assign_attribute to capture an attribute configuration in a tuple and re-use in multiple LiveViews. This accepts two-element and three- element tuples with: {subscribe, refresher} or {subscribe, filter, refresher} correspondingly

socket = assign_attribute(socket, &User.subscribe/0, users: &User.list/0)

assign_attribute updates the specified assign keys each time there is a new event sent from the subscription source.

Returns a specification to start this module under a supervisor.

    socket = update_attribute(socket, :users)

Helper method to issue a update callback manually on a live attribute, when there is a known update but no subscription event.

Link to this section Types

Specs

filter() :: atom() | tuple() | list() | (() -> false | %{})

The filter allows doing optimzation by a) ignoring certain events of the subscription source and b) pass event values directly to assign values, instead of using refresher functions to re-load them.

It can be either a match object defining which events should be matched, or a function returning false when the event should be ignored or a map when it should be processed. Keys that are present in the map will be assigned to the socket. (if there are matching keys in the refresher list)

Filter function

The filter function receives the event and should return either false or a map of the new values:

fn event ->
  case event do
    {User, :users_updated, users} -> %{users: users}
    _ -> false
  end
end)

Filter object

Match objects are defined by example of a matching list or tuple. These can be customized using two special terms:

  • :_ the wildcard which matches any value, but ignores it
  • {:"$", some_key} - which matches any value, and uses it as update value in the socket assigns

Examples

# Let's assumg the `User` module is generating the following event each time
# the user list is updated: `{User, :users_updated, all_users}`
# then the following match object will extract the users

{User, :users_updated, {:"$", :users}}

# Full function call with match object
assign_attribute(socket, &User.subscribe/0, users: &User.list/0, {User, :users_updated, {:"$", :users}})


# Now the same we could get with this function callback instead:
fn event ->
  case event do
    {User, :users_updated, users} -> %{users: users}
    _ -> false
  end
end)

# Full function call with callback
assign_attribute(socket, &User.subscribe/0, users: &User.list/0,
  fn event ->
    case event do
      {User, :users_updated, users} -> %{users: users}
      _ -> false
    end
  end)

Specs

refresher() ::
  [{atom(), (() -> any()) | (socket() -> any())}] | (socket() -> socket())

The refresher list is passed to assign_attribute() to know which assigns to update when the subscription source issues an event.

It is a list of {key, callback} pairs specifying how to load the new attribute values. The callback thereby can have optionally one argument to read context from the socket.

refresher() examples

# 1. Zero argument callback to update the users list:
[users: &User.list_all/0]

# 2. Single argument callback to use the socket state in the update:
[users: fn socket ->
  User.list_all() -- socket.assigns.blacklist
end]

# 3. Special `socket` key to assign multiple values at once manually
[socket: fn socket ->
  assign(socket,
    users: User.list_all() -- socket.assigns.blacklist,
    last_update: System.os_time()
  )
end]

Usage Examples

iex> assign_attribute(socket, &User.subscribe(), users: &User.list_all/0)

iex> assign_attribute(socket, &User.subscribe(), fn socket ->
  assign(socket, users: User.list_all() -- socket.assigns.blacklist)
end)

Specs

socket() :: %Phoenix.LiveView.Socket{
  assigns: term(),
  endpoint: term(),
  fingerprints: term(),
  host_uri: term(),
  id: term(),
  parent_pid: term(),
  private: term(),
  redirected: term(),
  root_pid: term(),
  router: term(),
  transport_pid: term(),
  view: term()
}

Link to this section Functions

Link to this function

assign_attribute(socket, tuple)

View Source

Specs

assign_attribute(socket(), tuple()) :: socket()

Shortcut version of assign_attribute to capture an attribute configuration in a tuple and re-use in multiple LiveViews. This accepts two-element and three- element tuples with: {subscribe, refresher} or {subscribe, filter, refresher} correspondingly

Use with:

socket = assign_attribute(socket, User.live_attribute())

When there is an User method:

defmodule User do
  def live_attribute() do
    {&subscribe/0, users: &list_users/0}
  end
  ...
end
Link to this function

assign_attribute(socket, subscribe, filter \\ :_, refresher)

View Source

Specs

assign_attribute(socket(), (() -> any()), filter(), refresher()) :: socket()
socket = assign_attribute(socket, &User.subscribe/0, users: &User.list/0)

assign_attribute updates the specified assign keys each time there is a new event sent from the subscription source.

See refresher() and filter() for advanced usage of these parameters. Simple usage:

Returns a specification to start this module under a supervisor.

See Supervisor.

Link to this function

update_attribute(socket, name)

View Source
    socket = update_attribute(socket, :users)

Helper method to issue a update callback manually on a live attribute, when there is a known update but no subscription event.