loading...
Cover image for Reactive views from server and 0 JavaScript?

Reactive views from server and 0 JavaScript?

gamezcua1 profile image Gerald Amezcua ・5 min read

Elixir

In 2011, a new programming language appeared with a simple focus in mind: Making a programming language concurrent, elegant, easy, blasting fast, and powered by one of the monsters ever built, Erlang.

Elixir is a functional language built on top of Erlang, providing a delightful syntax pretty similar to Ruby, it is designed to scale tons of thousands of threads concurrently and enabling communication between them via messages. Elixir and erlang also promote a really nice expression things will go wrong and this is because it is designed to fail, offering a lot of mechanisms such as supervisors restarting particular parts or threads of the system instead of a complete explosion.

Phoenix

Phoenix is to Elixir what Rails is to Ruby: an amazingly fast web framework for building web applications without compromising scalability or maintainability.

By taking advantage of Phoenix speed, channels, and HTTP 2.0 implementation, Phoenix's builders came out with a cool library for Phoenix called LiveView which provides a really cool way to make reactive views with 0 Javascript and just Elixir.

Let's stop talking and let's write some code!

Having in mind that you already installed NodeJS in your computer, we first want to have Elixir installed

brew install elixir

Mix is a build tool that ships with Elixir that provides tasks for creating, compiling, testing your application, managing its dependencies and much more.

Now that we have it installed, let's install Phoenix

mix archive.install hex phx_new 1.4.0

Now we are all set to create our new Phoenix app

mix phx.new reactivity

A lot of things are going to start happening so, when mix asks us to Fetch and install dependencies?, just type y.

Now, let's set up our database and start our server

cd reactivity
mix ecto.create
mix phx.server

And know in our browser, we can go to localhost:4000

phoenix-running

Now we are ready to set up LiveView

First, let's open our mix.exs file and add LiveView as a dependency and then run mix deps.get

def deps do
  [
    ...
    {:phoenix_live_view, "~> 0.10.0"},
    {:floki, ">= 0.0.0", only: :test}
  ]
end

After getting all the dependencies, let's restart our server by pressing ctr+c twice and then mix phx.server

Now, let's open config/confix.exs and add out endpoint configuration. You can generate your own salt by running mix phx.gen.secret 32

config :reactivity, ReactivityWeb.Endpoint,
   ...
   live_view: [
     signing_salt: "YOUR_SALT_GOES_HERE"
   ]

Now we need to configure our browser pipeline:

# lib/my_app_web/router.ex
import Phoenix.LiveView.Router

pipeline :browser do
  ...
  plug :fetch_session
- plug :fetch_flash
+ plug :fetch_live_flash
end

Then, add the following imports to our web file in lib/reactivity_web.ex:

# lib/reactivity_web.ex

def controller do
  quote do
    ...
    import Phoenix.LiveView.Controller
  end
end

def view do
  quote do
    ...
    import Phoenix.LiveView.Helpers
  end
end

def router do
  quote do
    ...
    import Phoenix.LiveView.Router
  end
end

Then, we will configure our endpoint:

# lib/reactivity_web/endpoint.ex

defmodule ReactivityWeb.Endpoint do
  use Phoenix.Endpoint

  # ...

  socket "/live", Phoenix.LiveView.Socket,
    websocket: [connect_info: [session: @session_options]]

  # ...
end

Now, let's add LiveView JavaScript dependency:

{
  "dependencies": {
    "phoenix": "file:../deps/phoenix",
    "phoenix_html": "file:../deps/phoenix_html",
    "phoenix_live_view": "file:../deps/phoenix_live_view"
  }
}

And let's install it:

npm install --prefix assets

Now, let's enable the socket communication:

// assets/js/app.js
import {Socket} from "phoenix"
import LiveSocket from "phoenix_live_view"

let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content");
let liveSocket = new LiveSocket("/live", Socket, {params: {_csrf_token: csrfToken}});
liveSocket.connect()

And finally, and optionally, you can add the default CSS:

/* assets/css/app.css */
@import "../../deps/phoenix_live_view/assets/css/live_view.css";

Perfect! We can now restart our server and check if everything is OK!

phoenix-running

Controller

Now that we have everything set up, we can start by writing our first Controller, which will have the specific action for our route and will render our LiveView!

Let's go ahead and create a new file under /lib/reactivity_web/controllers/ called to_do_controller.ex and add the following:

defmodule ReactivityWeb.ToDoController do
  use ReactivityWeb, :controller

  def index(conn, _params) do
    live_render(conn, ReactivityWeb.ToDoView)
  end
end

Then, add the route to lib/reactivity_web/router.ex

  scope "/", ReactivityWeb do
    pipe_through :browser

    get "/", PageController, :index

    get "/todo", ToDoController, :index
  end

LiveView

Add a new folder index /lib/reactivity_web/ called live and a file inside it called to_do_view.ex with the next content:

defmodule ReactivityWeb.ToDoView do
  use Phoenix.LiveView

  # Method that returns the HTML code that will be rendered
  def render(assigns) do
    ~L"""
      <div>
        <span> <%= format_date(@time) %></span>
      </div>
    """
  end

  # This is the first function that gets called once the live view is loaded
  def mount(_session, _, socket) do
    # Check if the socket is correctly connect we send a tick in the server and falls in handle_info(:tick, process)
    if connected?(socket), do: Process.send_after(self(), :tick, 1000)

    # Here we assign all the variables needed for our view
    {:ok, assign(socket, time: :calendar.local_time())}
  end

  # This function receives the tick event and the socket that triggered the event
  def handle_info(:tick, socket) do
    # So we send another tick after 1 second
    Process.send_after(self(), :tick, 1000)
    # And reply to our view with the local time, which ends up in rendering the view again
    {:noreply, assign(socket, time: :calendar.local_time())}    
  end


  # Helper to format the date into a readable Hour
  def format_date(date) do
    {_, {h, m, s}} = date
    "#{h}:#{m}:#{s}"
  end

end

And now, if we go to localhost:4000/todo, we'll see
phoenix-running

As you can see, the view is being rendered without making any kind of event from the browser: the server knows that every second a :tick event is being triggered and the handle_info(:tick, socket) function handles this event and updates the state or assigns making the view render again.

Now, let's add some more code in order to build a simple To-Do list.

First, we'll add a new variable called todos:

  def mount(_session, _, socket) do
    if connected?(socket), do: Process.send_after(self(), :tick, 1000)
    {:ok, assign(socket, time: :calendar.local_time(), todos: [])}
  end

Add a form and a loop for rendering the tasks in our To-Do list. Here's the form will an option called phx_submit and a value of :save: this means that, whenever the form is submitted, LiveView will trigger an event called :save with the data in the form.

  def render(assigns) do
    ~L"""
      <div>
        <span> <%= format_date(@time) %></span>

        <%= f = form_for :todo, "#", [phx_submit: :save] %>
          <%= label f, :task %>
          <%= text_input f, :task %>

          <%= submit "Save" %>
        </form>

        <ul>
          <%= for task <- @todos do %>
            <li><%= task %></li>
          <% end %>
        </ul>
      </div>
    """
  end

Now to handle that event, let's add a new handle_event function:

  #This function will handle the save event on submit form 
  def handle_event("save", %{"todo" => %{"task" => task}}, socket) do
    # And we will add the new task to out tasks array
    {:noreply, update(socket, :todos, &([task |&1]))}
  end

And now in the browser, you will see:

phoenix-running

And that's it!

No JavaScript needed and a whole world of opportunities around Elixir, Phoenix and this amazing library LiveView. There's a ton of things you can build with this and you don't need a lot of languages and stuff trying to interact with each other. Elixir has been growing along with its community, so I think it's the best time to start learning it and build amazing stuff with it.

And here is the full code

Posted on by:

gamezcua1 profile

Gerald Amezcua

@gamezcua1

Elixir enthusiast, Developer & Coffee lover.

Discussion

markdown guide