Reactive pages from server and 0 JavaScript?

March 27th, 2020 / 3 minute(s) reading time
elixir

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 for Elixir what Rails for Ruby. An amazingly fast web framework for building web applications without compromising scalability or maintainability.

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

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,
kj...
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

Not 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 now can restart our server a 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 at lib/reactivity_web/router.ex

scope "/", ReactivityWeb do
pipe_through :browser
get "/", PageController, :index
get "/todo", ToDoController, :index
end

Now add a new folder undex /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 conntect 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

And 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

Now add a form and a loop for rendering the tasks in our To-Do list. Here 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 is 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 each other. Elixir has been growing along with its community, so I think it's the best time to start learning it a building amazing stuff with it.

The silly code

Self taught 📖 Software engineer 💻 Coffee lover ☕️ Coding and sharing cool stuff and knowledge