PhxInPlace

Inline editing package for the Phoenix Framework based on the rails gem best_in_place.

Based on the Rails gem best_in_place, phx_in_place enables unobtrusive, inline editing via Phoenix channels. The package consists of a view helper, javascript event listeners and a server side channel helper method that when set up will automatically update your application database and views whenever a user changes a field value.

Basic Example:

<%= phx_in_place @product, :name %>
//<input class="pip-input" hash="<<hashed value here>>" name="name" value="251.00" style="background: initial;">

Optional parameters provide support for styling and formatting:

<%= phx_in_place @product, :name, class="input-lg", display_as: :number_to_currency, display_options: [precision: 2, unit: "$"], type: "textarea" %>
//<textarea class="pip-input input_lg" display_type="number_to_currency" hash="<<hashed value here>>" name="name" value="$ 251.00" style="background: initial;">

Full documentation for the package is available here.

A demo of the solution in action is available here

Installation

Adding phx_in_place to an existing application with one or more channels already set up is easy and straight forward. If you are unsure how to set up a Phoenix Channel, please see the Phoenix Channels guide for more information.

Include phx_in_place as a dependency in the mix.exs file:

def deps do
  [
    {:phx_in_place, git: "https://github.com/cjwadair/phx_in_place",  tag: "0.1.0"}
  ]
end

and run mix deps.get:

  mix deps.get

Server-side set up

import PhxInPlace into your appName_web.ex file to make it available in all of your templates:

defmodule YourAppNameWeb do
  ...
  def view do
    quote do
      #other import, use and alias statements
      import PhxInPlace
    end
  end
  ...
end

and use PhxInPlace.ChannelManager in your channels:

  # in my_channel.ex
  defmodule AppName.MyChannel do
    use Phoenix.Channel
    use PhxInPlace.ChannelManager
  end

then add the following to your config.exs file:

  config :phx_in_place,
    repo: AppName.RepoName,
    endpoint: AppNameWeb.Endpoint

Client-side set up

import phx_in_place into your app.js after your import for your socket.js file:

  import "phoenix_html"
  import socket from &#39;./socket&#39;
  import * as pip from "phx_in_place"

and add the phx_in_place event listeners to your pages:

  channel.join()
    .receive("ok", message => {
      pip.addListeners(channel);
    })
    .receive("error", resp => {
      console.warn("Unable to join", resp)
    });

Usage

phx_in_place (phx_in_place struct(), :field, options)

View helper for generating input fields for inline editing:

 <%= phx_in_place @product, :name %>
 #<input class="pip-input" hash="SFMyNTY.g3QAAAACZAAEZGF0YWwAAAABaAJkACpFbGl4aXIuU2l0ZWxpbmVQaG9lbml4LlN1cHBsaWVycy5CY2xpc3RpbmdiAAAHTmpkAAZzaWduZWRuBgAvG0InYwE.aJPlnBRX1nuKx8Bdyo8P_UTpRYIyO24aQaYknQJ2Q50" name="name" value="251.00" style="background: initial;">

Hash value contains the name of the struct and the id of the record in question. Values are signed using Phoenix.Token to prevent tampering but are not encrypted. See the Phoenix.Token module for details.

Params:

Options

phx_in_place_if (phx_in_place_if condition, struct, field, OPTIONS)

Similar to the phx_in_place helper but with a condition as the first parameter.

  <%= phx_in_place_if @user.type==&#39;admin&#39;, @product, :supplier_id %>

if @user.type=='admin' is false, a non-editable <span></span> tag is generated. Otherwise, the output is the same as the phx_in_place method above. This is useful for enforcing authorization rules.

Post Update Callbacks

The event handlers that phx_in_place adds to your code will handle database updates and change the value of the input field automatically. For additional post-update event handling, you can listen to the pip:update:success and pip:update_failure events as follows:

  document.addEventListener(&#39;pip:update:success&#39;, function (e) {
      //add your success event callbacks here
  }, false);

  document.addEventListener(&#39;pip:update:failure&#39;, function (e) {
      //add your failure event callbacks here
  }, false);

These callbacks can be used to trigger additional client side javascript updates or channel events for server side processing.

Examples

Coming Soon...