Drab - Remote Controlled Frontend Framework

Living Pages in Phoenix

Drab is the extension library to Phoenix Framework for providing an access to the browser's User Interface (DOM objects) from the server side. The main advantage is to eliminate necessity of writing two applications: one for the client-side, and one for the backend. All the UI control may be now done in the backend, eliminating JS and AJAX.

Additionally, because http is a stateless and an one-way protocol, it is not easy to communicate back from the server to the browser. Like, for example, during execution of the long running process, when there is a need to update status (eg. progress bar), or to ask the operator about something (like "would you like to continue?"). Of course there are workarounds for this, like polling the server every few seconds to get a status, or even auto-refreshing the page, but those are just a dirty workarounds. The solution would be to allow an access to the interface directly from the server side. And here comes the Drab.

Drab is made with Elixir on Phoenix. This page is a living demo - all examples are actually working on the Drab server (v0.10.0). You can find the source code of Drab on Github, and the source code of this demo page: here. For more detailed instructions, please refer to the documentation.

Drab does not use any browser plugins, flash players, Java applets, Silverlight, etc. It works in any modern browser capable of running HTML5.

Teaser: Imagine having a Phoenix template like:

<a href="https://<%= @url%>" class="btn btn-success" @style.width=<%= @width%>>
  Visit <%= @url %>
</a>
With Drab, you may remote control this html element, live:

© Tomek "Grych" Gryszkiewicz 2016 - 2018

v0.9.1: jQueryless Modal 2018-07-25

Everyone loves modals, but not everyone loves jQuery. This version removes jQuery dependency from Drab.Modal module!

v0.9.0 - The Final API 2018-06-29

BREAKING RELEASE! Drab API has been changed in 0.9.0! Good news are that this API will not change until 1.0. Anyway, please be aware that this release will break your existing code. Do You Want to Know More?

v0.8.3: Subscribe and Presence 2018-06-22

Subscribe and Presence release, brings the ability to subscribe to the topic in a runtime and utilyze the Phoenix.Presence in an easy way.

v0.8.0: Brand New Engine 2018-05-09

The new EEx engine, included in this release, introduces the new era in the Living Assigns world. It is better, faster and limitless. You may now delete the useless priv/drab.live.cache file.

v0.7.2: API Changes and Reusable Components 2018-04-01

Please read the Release Notes - API has been changed. Also, introduced the new feature: creating reusable components!

See all Release Notes to learn about the previous releases


Introduction

Here is the simplest example - the text input and the buttons. Clicking the buttons change the string case. Awesome!

On the client side there is basic HTML with the input and the button:

<form>
  <input name="text_to_uppercase" value="<%= @text %>">
  <button drab="click:uppercase">Upcase</button>
  <button drab="click:downcase">Downcase</button>
</form>

What is a difference between the standard behaviour and Drab? Drab does not GET or POST the form, it calls the event handler function via websockets instead, and updates the DOM nodes directly on the page. As you can see, clicking the button does not reload the page.

The idea is to reuse the existing Phoenix templates, but make them living. drab="event:handler" attribute defines on which Phoenix function (handler) is going to be run in case of the given event. This handler is a function in the commander module, analogically to action in the controller. A commander is like a controller for living Drab pages.

In this example, click event in the browser remotely runs DrabPoc.LiveCommander.uppercase/2 on the server.

defmodule DrabPoc.LiveCommander do
  use Drab.Commander

  defhandler uppercase(socket, sender) do
    text = sender.params["text_to_uppercase"]
    poke socket, text: String.upcase(text)
  end
end

The code above creates `uppercase/2` function and mark it as an event handler. Such function takes two parameters: first is a %Phoenix.Socket{}, and the second is a map with information about sender DOM object, as well as parameters from the surrounding form. sender.params contains values of all the input elements inside the form, where the keys are element's name or id.

After processing the inputs, we need to present it to back in the browser. This is where we use poke/2 function - it pushes the assign (in this case: <%= @text %>) back to the browser. Input value is updated, without reloading the page.

Notice that you can have many event launchers (buttons, inputs, links) in the form, each one calling the different event handler. The second function handler is very similar, just makes the text lowercase.

This is a live demo. Click the button to launch DrabPoc.LiveCommander.uppercase/2 on the server.



POKE and PEEK strike back

Function poke/2 is one of the most important in Drab, so we should go a little bit deeper. Let's assume you have your index.html.eex already prepared, displaying a list of some users. There are two assigns there, @title and @users:

<strong><%= @title %>:</strong><br>
<%= for user <- @users do %>
  Username: <%= user %><br>
<% end %>
The code is rendered as usual, in the simple Controller:
defmodule DrabPoc.LiveController do
  use DrabPoc.Web, :controller

  def index(conn, _params) do
    render conn, "index.html", users: ["Dżesika", "Brajanek", "Zdzichu"], title: "Users List"
  end
end

The goal of Drab is to update assigns live, without re-rendering the page. For this, Drab introduces its own EEx Engine. By default, it uses .drab extension, so you need to rename index.html.eex to index.html.drab.

Then, create some button to launch the Drab event in the commander:

<button drab-click="replace_list">Replace List</button>
Did you notice drab-click attribute? This is an alternative method for defining handler for an event. There is a number of such shorthands for most popular events, see documentation for more.

The event handler function should be placed inside the commander module. By default, Drab searches for the corresponding names, so pages generated with DrabPoc.LiveController are handled by DrabPoc.LiveCommander. Handler below simply replaces the users list with the new one:

defmodule DrabPoc.LiveCommander do
  use Drab.Commander

  defhandler replace_list(socket, _sender) do
    Drab.Live.poke socket, users: ["Mścisław", "Bożydar", "Mściwój", "Bogumił", "Mirmił"]
  end
end
What is going on here? We've just modified the assign @users with the new value. Drab.Live.poke pushes the new value and re-evaluate the corresponding part of the template. Notice that you don't have to poke all assigns, Drab remembers what was there previously, and replaces only the assigns you've poked (in this case, @users).

The second button runs replace_title event handler, which changes the value of the @title assign:

defhandler replace_title(socket, _sender) do
  Drab.Live.poke socket, title: "New, better Title"
end
Of course you can change the values of many assigns in one poke, Drab will find and update the corresponding parts of the page.

Last, but not least, is the possibility to get the current value of assigns. For this, we have a function called peek/2. In the example, the third button adds something to the existing list of users:

defhandler add_to_list(socket, _sender) do
  {:ok, users} = Drab.Live.peek(socket, :users)
  Drab.Live.poke socket, users: users ++ ["Hegemon"]
end

Notice that assign is updated only on the server side, with poke/2. This is why we didn't use peek/2 in the first example - having <input value="<%= @assign %>" does not update @assign when user changes the input.

Users List:
Username: Dżesika
Username: Brajanek
Username: Zdzichu








Long Running Process with Live Attributes and Properties

Let's move on to show how to update DOM node attributes and properties, live. At the same time, the next example is going to demonstrate what Drab is good in: controlling the user interface from the long-running server process.
Assume we have a process which is doing some stuff on the server side, and we want to report back to the user after each completed step. We are going to use the progress bar controlled from the server.

<div class="progress">
  <div class="progress-bar <%= @progress_bar_class %>" @style.width=<%= "#{@bar_width}%" %>>
    <%= "#{@bar_width}%" %>
  </div>
</div>
<button class="btn btn-primary" drab-click="perform_long_process"><%= @long_process_button_text %></button>
Notice that this time we used assigns in the DOM node attributes: class and @style.width. What will happen when we poke this assigns from the controller? In the first case, class attribute is updated as expected. In the second case, Drab sets style.width property of the corresponding node to whatever is evaluated of the given Elixir expression.

We are going to live update the assigns of the template above from perform_long_process/2 function in the commander:

defhandler perform_long_process(socket, sender) do
  poke socket, progress_bar_class: "progress-bar-danger",
    long_process_button_text: "Processing..."

  steps = :rand.uniform(100)
  for i <- 1..steps do
    Process.sleep(:rand.uniform(500)) #simulate real work
    poke socket, bar_width: Float.round(i * 100 / steps, 2)
  end

  case alert(socket, "Very Important Question", "Once again?", buttons: [ok: "Sure", cancel: "Nope"]) do
    {:ok, _} ->
      perform_long_process(socket, sender)
    {:cancel, _} ->
      poke socket, progress_bar_class: "progress-bar-success", long_process_button_text: "Restart"
  end
end
This function simulates some process, which updates the User Interface after each step done. For more fun, we randomly choose the number of steps and the step "processing time".

Before running loop of steps, we update the button text (to show the user that the work is in progress), and the progress bar class attribute. In the template, it was initially set to class="progress-bar <%= @progress_bar_class %>". After poking the @progress_bar_class assign, Drab updates the class attribute of the node, preserving the given pattern: it will become class="progress-bar progress-bar-danger".

Even more interesting happens when poking the @bar_width assign. Using the @property=<%= expression %> syntax, we can bound any node property with the Elixir expression! In this case, @style.width=<%= "#{@bar_width}%" %> connects style.width of the DOM object with "#{@bar_width}%" expression. Elixir expression is evaluated every time you poke a new value of the assign(s), and the value of the evaluated expression updates given node property.

Unlike attributes, properties may be bound to any JSON encodable value: a list, a map, etc. And because of this, properties syntax does have some limitations. The only valid sytax is @property=<%= expression %>, no quotes or apostrophes. You can only bind one expression to one property; you may not use string patterns, like you could with attributes.

At the very end, we want user to decide if we should run the process again. It can be done with the Drab.Modal.alert function - it shows the boostrap modal window and waits for the answer. In the meantime, the handler process is still alive. This can be very useful if you want to ask user if to commit or to rollback the changes!

One more thing: you probably noticed that when the task is "processing", you can click another button in the page. This is because Drab requests are asynchronous on the server side. And this is a reason why buttons are disabled by default while processing - it is blocking the user from running the same action for few times. You may override this behavior in `config.exs` with:

config :drab, disable_controls_while_processing: false

Click a button to simulate long-running process on the server.

0%


Tens of Tasks Running in Parallel on the Server and Communicating Back to the Browser

In the next example we will emulate long running server Process composed of a number of Tasks, which can be run asynchronously. We want to run it in parallel, change their status when they finish the job and - when all of them are done - change the status of the whole process.

The HTML side contains a button and a few spans to display the status of the process and tasks:

Async task status:
<span id="async_task_status" class="label label-primary">
  <%= @async_task_status %>
</span>
<span class="task label label-danger" task-id="1">Task 1</span>
  ...
<span class="task label label-danger" task-id="54">Task 54</span>
<button drab-click="run_async_tasks">Start async task</button>

All the processing is on the server side. Clicking the button runs the run_async_tasks/2 function:

defhandler run_async_tasks(socket, _sender) do
  poke socket, async_task_label: "danger", async_task_status: "running"
  set_attr(socket, ".task[task-id]", class: "task label label-danger")

  tasks = Enum.map(1..54, fn(i) -> Task.async(fn ->
      Process.sleep(:rand.uniform(4000)) # simulate real work
      set_prop(socket, ".task[task-id='#{i}']", className: "task label label-success")
    end)
  end)

  begin_at = :os.system_time(:millisecond)
  Enum.each(tasks, fn(task) -> Task.await(task) end)
  end_at = :os.system_time(:millisecond)

  poke socket, async_task_label: "success", async_task_status:
    "finished in #{(end_at - begin_at)/1000} seconds"
end
The code above runs 54 tasks asynchronously, which just wait some time (up to 4 seconds) and then communicate to the user by changing the bootstrap class from label-danger to label-success. After launching the background tasks, it waits (Task.await/1) for all of the tasks to finish and then informs the user by changing the overall status to "finished".

There are two new functions here: set_attr/3 and set_prop/3. Both are members of Drab.Element module, which is the one for query and manipulate HTML element attributes and properties. They take the CSS selector as an argument, and modifies all the elements found by this selector.

You may think: what is the difference between this approach and running 54 AJAX requests which launches action on the controller side? Well, in addition to Drab's natural beauty, you can't do it with AJAX. You browser limits the number of simultaneous connections.

Click the button below to simulate server-side long running process with asynchronous tasks.

Async task status: ready

Task #01
Task #02
Task #03
Task #04
Task #05
Task #06
Task #07
Task #08
Task #09
Task #10
Task #11
Task #12
Task #13
Task #14
Task #15
Task #16
Task #17
Task #18
Task #19
Task #20
Task #21
Task #22
Task #23
Task #24
Task #25
Task #26
Task #27
Task #28
Task #29
Task #30
Task #31
Task #32
Task #33
Task #34
Task #35
Task #36
Task #37
Task #38
Task #39
Task #40
Task #41
Task #42
Task #43
Task #44
Task #45
Task #46
Task #47
Task #48
Task #49
Task #50
Task #51
Task #52
Task #53
Task #54


Server-Side Events: Display the Growing File (tail -F)

Another great example of Drab usage is to display the files, which are changing over the time, like log files. In the normal, Ajax world, you need to send a request periodically to refresh the view, and you never know, if the file changed or not. What a waste of resources!

Let's create a space for the access log, and fill it up with some initial message:

<pre><code class="accesslog"><%= @access_log %></code></pre>

With Drab, you can utilize the events on the Server Side: in this case to use fsevent to trace the changes in the file. So you upgrade the UI view only in case when something really happened.

To enable the Server-Side event watcher, we are going to introduce the Drab Callbacks. Callbacks are events launched automatically, when page is loaded, when browser reconnects or disconnects from the Server. To enable it you need to specify them in the Commander:

use Drab.Commander
  onconnect :connected
  ...
end
onconnect launches every time client connects to the server, so after first load, network crashes, server crashes etc. There is also onload callback, which runs only once and ignores disconnects.

In the callback implementation we subscribe to Sentix file watcher (it is a wrapper around fswatch utility):

def connected(socket) do
  # Sentix is already started within application supervisor
  Sentix.subscribe(:access_log)

  file_change_loop(socket, Application.get_env(:drab_poc, :watch_file))
end
Watcher sends a message each time the file change. In this case we are going to update the @access_log assign to present the last few lines of the file.
defp file_change_loop(socket, file_path) do
  receive do
    {_pid, {:fswatch, :file_event}, {^file_path, _opts}} ->
      socket |> poke(access_log: last_n_lines(file_path, 8))
  end
  file_change_loop(socket, file_path)
end

There is also the disconnect callback, which runs every time browser disconnects from the server - close the browser, navigate away from the page, network issue, etc. Obviously there is no way to show it on the Demo page, as it operates when the page is not visible anymore.

This is a realtime view of few last lines of the access.log of this page

... this pane will update when access.log change ...


Is Drab Quick Enough? Plus Broadcast

You may concern about Drab performance - is it quick enough to handle events in the realtime? The best to find it out is to check with, lets say, the keyup event.

<input drab-keyup="changed_input" class="form-control" data-update="#display_placeholder">
<div id="display_placeholder"></div>
defhandler changed_input(socket, sender) do
  broadcast_prop socket, sender["dataset"]["update"], innerText: String.upcase(sender["value"])
end

Did you notice the new broadcast_prop/3 function? This one works exactly like set_prop/3, but it broadcasts to all browsers on which the current page is opened. Open another browser window or even another browser and observe what is going on when you type in the field.
By default, broadcasts are sent to the displayers of the current page, but you may change this behavior by setting up broadcasting option in the Commander. For example, setting broadcasting :same_controller will send this broadcast_prop to all browsers which have opened the pages generated in the current controller.

Demonstration of keyup event:

In many situation you don't need to react on every single key press, and you want to launch the event when user stop typing. The good example is autocomplete - you don't want to search the database after each keypress. This is the are where debounce() option becomes handy:

<input drab-keyup="changed_input"
       drab-options="debounce(500)"
       data-update="#display_placeholder2">
<div id="display_placeholder2"></div>
The code above is exactly the same as previous, except of debounce(500) option. The same event handler (changed_input/2) is launched, but not after every key up, but when you actually stop typing for 500 miliseconds.

The same event with debounce(500):


Optional Argument

To pass one additional argument to the event handler, just add it in the parenthesis after the handler function name:

<button class="btn btn-primary" drab-click="sleep(1)">Sleep 1 second</button><br>
<button class="btn btn-primary" drab-click="sleep(1+1)">Sleep 2 seconds</button><br>
<button class="btn btn-primary" drab-click="sleep(parseInt('3'))">Sleep 3 seconds</button>

In this case, event handler must have arity of 3. Notice that the argument is evaluated on the client side before sending to the browser, so it can be any valid JS piece of code. In this example it is Integer, and it is passed to the Elixir function correctly, as Integer.

defhandler sleep(_socket, _sender, interval) do
  Process.sleep(interval * 1000)
end
Observe that when one button is "processing", you can click another one. This is because Drab requests are asynchronous on the server side. And this is a reason why buttons are disabled by default while processing - it is blocking the user from running the same action for few times. You may override this behaviour, see documentation for Drab.Config.






Shared Commanders

So far, we did run events in the specific commander - the one which is related to the controller, which rendered the current page. This is done by the reason: we don't want to allow browser to launch any public function in our server! But it would be nice to run event handlers from another module, for staying DRY. Drab gives you an opportunity to do it with Shared Commander, just by specifying the module name in the drab attribute: <button drab-click="My.Module.function">.
We are going to create re-usable countdown commander, with two parameters: number of seconds to countdown from, and the selector of the node, on which the output will be displayed:

<button drab-click="DrabPoc.TimerCommander.countdown({seconds: 5, output: '#first_example'})">
  Count down from 5
</button>
<span id="first_example">SPAN #first_example</span>

For the security reasons, you must declare all shared commanders you want to use, in the controller:

defmodule DrabPoc.LiveController do
  use Drab.Controller, commanders: [DrabPoc.TimerCommander]
end

The whole TimerCommander is simply the commander, with countdown/3 event handler function:

defmodule DrabPoc.TimerCommander do
  use Drab.Commander

  defhandler countdown(socket, _sender, options) do
    for i <- 1..options["seconds"] do
      set_prop socket, options["output"], innerText: options["seconds"] - i
      Process.sleep(1000)
    end
  end
end
The handler function is getting a map, defined in the drab-click attribute in html, as a third argument.


SPAN #first_example


Creating Reusable Components with Shared Commanders

Now we are going to modify the example a bit. And this time, we are going to use drab-commander and drab-argument attributes. You may set them in any tag, and all the children of this tag will use given Shared Commander and the given argument:

<p drab-commander="DrabPoc.Timer2Commander" drab-argument="{seconds: 10}">
  <button class="btn btn-primary" drab-click="countdown">
    Count down from 10
  </button>
  <br><span class="output">SPAN .output</span>
</p>
<p drab-commander="DrabPoc.Timer2Commander" drab-argument="{seconds: 20}">
  <br>
  <button class="btn btn-primary" drab-click="countdown">
    Count down from 20
  </button>
  <br><span class="output">SPAN .output</span>
</p>

Observe the example. When you click one of the buttons, the only corresponding span.output is updated. But how does Drab know which one to update? Here comes function:

defmodule DrabPoc.Timer2Commander do
  use Drab.Commander

  defhandler countdown(socket, sender, options) do
    for i <- 1..options["seconds"] do
      set_prop socket, this_commander(sender) <> " .output", innerText: options["seconds"] - i
      Process.sleep(1000)
    end
  end
end
this_commander/1 function returns the unique selector of the tag with drab-commander. Using it, you may create reusable components.


SPAN .output



SPAN .output


Shared Commanders and Living Assigns

The previous example was nice, but what about living assigns? They are using the pure magic to update the page, not the selector, so we can't just use this_commander/1 as above. Consider almost exactly the same html, but with the assigns:

<p drab-commander="DrabPoc.Timer3Commander" drab-argument="{seconds: 10}">
  <button class="btn btn-primary" drab-click="countdown">
    Count down from 10
  </button>
  <br><span ><%= @countdown %></span>
</p>
<p drab-commander="DrabPoc.Timer3Commander" drab-argument="{seconds: 20}">
  <br>
  <button class="btn btn-primary" drab-click="countdown">
    Count down from 20
  </button>
  <br><span ><%= @countdown %></span>
</p>

What to do if we want to update the corresponding section of the page with poke? The answer is: nothing. All is done automagically. Only the assigns, which are in the same drab-commander section as the element, which generated the event, are updated.

defmodule DrabPoc.Timer3Commander do
  use Drab.Commander

  defhandler countdown(socket, _sender, options) do
    for i <- 1..options["seconds"] do
      poke socket, countdown: options["seconds"] - i
    end
  end
end
Notice in this handler we've removed Process.sleep/1 - this is to demonstrate the network latency between the browser and the server (which is located in France) and the performance overhead of using the living assigns. We will show how to reduce this overhead, in the next chapter.


here be countdown



here be countdown


Using Living Assigns with Partials

In Phoenix, assigns live withing the templates. So far, we did poke to the main template, the one rendered in the controller. But what if you want to use living assigns within the partials? Let's move the previous example to the counter.html.drab file and render it inside the main template with standard, Phoenix way:

<%= render "counter.html", countdown: "the countdown in partial" %>

On the Commander side, the only difference is the line with the poke. You need to tell poke in which partial you want to update the assign. Do it by adding the partial name as a second argument of the poke. Notice that you may also specify the View, but without it, Drab takes the default view for the main template.

poke socket, "counter.html", countdown: options["seconds"] - i
Did you spot the performance difference between those two operations? Yes, Drab.Live works best with the small templates. This is because each time you poke anything, it must get the assigns from page, re-render the template and parse the html. It might be quite a long operation on a big, bad designed pages, like this one!


the countdown in partial



the countdown in partial


Drab Store and access to Plug Session

Analogically to Plug, Drab can store the values in its own session. To avoid confusion with the session, we will call it a Drab Store. You can use two functions, put_store/3 and get_store/2 to read and write the values in the Store.

  • By default, Drab Store is kept in browser Local Storage. This means it is gone when you close the browser or the tab. You may set up where to keep the data with drab_store_storage config entry.
  • Drab Store is not the Plug Session! This is a different entity. Anyway, you have an access to the Plug Session (details below).
  • Drab Store is stored on the client side and it is signed, but - as the Plug Session cookie - not ciphered.

defhandler increase_counter(socket, _sender) do
  counter = get_store(socket, :counter) || 0
  put_store(socket, :counter, counter + 1)
end

defhandler show_counter(socket, _sender) do
  alert(socket, "Counter", "<code>get_store(socket, :counter) == #{get_store(socket, :counter)}</code>")
  poke socket, counter: get_store(socket, :counter)
end



Although Drab Store is a different entity than Plug Session (used in Controllers), there is a way to access the Session. First, you need to whitelist the keys you want to access in access_session/1 macro (you may give it a list of atoms or a single atom).
To show an example, we've put the key :drab_test to the Plug Session while rendering this page by the Controller:

defmodule DrabPoc.PageController do
  use DrabPoc.Web, :controller
  use Drab.Controller

  def index(conn, _params) do
    conn = put_session(conn, :drab_test, "test string in Plug Session, set in the Controller")
    render conn, "index.html"
  end
end
To use it in the Commander, you need to explicitly inherit this value:
defmodule DrabPoc.PageCommander do
  use Drab.Commander

  onload :page_loaded
  access_session :drab_test

  def page_loaded(socket) do
    set_prop socket, "#show_session_test", value: get_session(socket, :drab_test)
  end
end
Notice that there is not way to update session from Drab. Session is read-only.

Result of get_session(socket, :drab_test) in the onload handler:


Chat Example

Is there any websocket based library without the Chat Example? Here is the one based on Drab! Simplest Chat Ever (and, well, the most primitive one).
The client-side contains a chat div to display messages, and two input fields - one for your nick, and the second one for the chat message:

<div id="chat" class="panel-body chat-panel"></div>
<input drab-change="update_nick" class="form-control" placeholder="nickname">
<input drab-change="update_chat" class="form-control">

On the Elixir side, update_nick event handler saves your nickname for the future use:

defhandler update_nick(socket, sender), do: put_store(socket, :nickname, sender["value"])

The following code is the whole chat application! We retrieve the nickname from the Drab Store (or set it to Anonymous if not found), compose the message to be displayed, and we are ready to go. First, we insert the message to the chat panel (used the broadcast_insert method with :beforeend modifier, so it is added at the bottom of the chat div). Second, clean up the message input. Third, scroll down chat to the bottom. All the changes of are broadcasted to all browsers viewing this page.

defhandler update_chat(socket, sender) do
  nick = get_store(socket, :nickname, "Anonymous")
  message = sender["value"]
  html = ~E"<strong><%= nick %>:</strong> <%= message %><br>" |> safe_to_string()
  broadcast_insert socket, "#chat", :beforeend, html
  set_prop socket, this(sender), value: ""
  broadcast_js socket, "document.querySelector('#chat').scrollTop = document.querySelector('#chat').scrollHeight"
end
The only new thing is the broadcast_js/2 function. As you probably guess, it runs any JS partial on all connected clients.

So far, chat just displays the message when someone type it in. It would also nice to have some additional information, like the time when the message appeared. This is quite complicated feature - we can't just display the server time, as users may go from the different timezones. Drab has a build-in function Drab.Browser.now/1 function, which gives the client local time:

iex> Drab.Browser.now(socket)
~N[2017-04-02 20:06:07.600000]
but we can't use it here, as we are broadcasting messages to all connected browsers, and every one may be in a different timezone.

The solution is to use a bit of Javascript. Instead of adding the message to chat window with broadcast_insert socket, "#chat", :beforeend, html, we directly will run the JS on all connected browsers!

defp chat_message_js(message) do
  """
  var time = "[" + (new Date()).toTimeString().substring(0, 5) + "] "
  document.querySelector('#chat').insertAdjacentHTML('beforeend', time + #{Drab.Core.encode_js(message)})
  """
end

defp add_chat_message!(subject, message) do
  subject |> broadcast_js(chat_message_js(message))
  subject |> scroll_down!()
end

defhandler update_chat(socket, sender) do
  nick = get_store(socket, :nickname, "Anonymous")
  message = sender["value"]
  html = ~E"<strong><%= nick %>:</strong> <%= message %><br>" |> safe_to_string()
  set_prop socket, this(sender), value: ""
  add_chat_message! socket, html
end

The Chat


Tracking connected users with Drab callbacks

You probably realized that actual chat is a little bit more complicated then it was explained in the previous example. It shows users connects and disconnects, as well as the list of connected users (type in /who in the chat to see the list). To archive it, we are going to use Drab callbacks, defined with onconnect/1 and ondisconnect/1 macros, along with Drab.Presence.

def connected(socket) do
  nickname = get_store(socket, :nickname, "Anonymous") || get_session(socket, :nickname, "Anonymous")
  joined = ~E"""
    <span class='chat-system-message'>*** <b><%= nickname %></b> has joined the chat.</span><br>
    """ |> safe_to_string()
  socket |> add_chat_message!(joined)

  update_presence_list!(socket)
end
The code above is executed every time your browser connects to the server. First, we broadcasts the information to all to show them that someone has joined the chat. Second, we update the presence list - this will be explained later.
We store your nickname in the Drab Store or, if it not set, we take it from the session - we already put "Anonymouos" with the country code to the session, in the controller.

Let's move to the disconnect handler. As you can probably guess, it is executed when the browser disconnects from the server. It may be because of navigating away from the page, close the tab or because of the network issues. Disconnect handler is different than the others - it does not receive the socket as an argument. It is because the socket does not exist anymore! But we still have an access to Drab Store and Drab Session:

def disconnected(store, session) do
  removed_user = store[:nickname] || session[:nickname]
  html = ~E"<%= removed_user %> has left." |> safe_to_string()
  add_chat_message!(same_controller(DrabPoc.PageController), html)
  update_presence_list!(same_controller(DrabPoc.PageController))
end
We are going to use the Drab Store or session to retrieve the user's nickname. Then, it would it would be good to inform all other users that this person is gone. But how to broadcast the message, when the socket does not exist anymore?

Drab's broadcasting functions can live without socket. Socket is needed only to find out to which browser send a message (so event handlers in Commander know which browser pressed a button). Broadcasts are just pushed into the void and being received by all the browsers which currently listen to the given topic. Because we set up the commander with broadcasting :same_controller, we can send the broadcast to same_controller(Controller.Name) instead of the socket.

For tracking a presence, we are going to use Drab.Presence. First, we need to enable and configure it - we are going to use a :nickname stored in session or store as a tracking ID:

config :drab, :presence, id: [store: :nickname, session: :nickname]
Such config first checks the store, and if it is not set, gets the user id from the session. Now, it is easy to retrieve the list of connections from Drab.Presence.list/1:
defp update_presence_list!(socket_or_subject) do
  users = for {user, %{metas: metas}} <- Drab.Presence.list(socket_or_subject), _ <- metas, do: user
  users = users
    |> Enum.sort()
    |> Enum.map(&html_escape/1)
    |> Enum.map(&safe_to_string/1)
    |> Enum.join("<br>")
  broadcast_prop socket_or_subject, "#presence-list", innerHTML: users
end
Notice that we are counting all metas. This is because the user nickname is not unique in this sophisticated system. In the real world, you would use some unique identifier here.

Connected users


Drab Waiter

Sometimes it would be good to have the possibility to ask user about something, when you are in a middle of some process, and you don't want to finish your function. The good example is a database transaction, when you run some query and want to ask user whether commit or rollback changes.

So the solution for this issue is called Drab Waiter. It is a tiny little DSL which stops processing the event handler function and waits for some event from the client side. You may register multiple events as well as a timeout (by default it is waiting forever). The example below is not very sophisticated, but shows the idea.

defhandler waiter_example(socket, _sender) do
  buttons = render_to_string("waiter_example.html", [])

  set_prop socket, "#waiter_answer_div", innerHTML: nil
  insert_html socket, "#waiter_example_div", :beforeend, buttons

  answer = waiter(socket) do
    on "#waiter_example_div button", "click", fn(sender) ->
      sender["text"]
    end
    on_timeout 5500, fn ->
      "six times nine"
    end
  end

  set_prop socket, "#waiter_example_div", innerHTML: nil
  set_prop socket, "#waiter_answer_div", innerText: "Do you really think it is #{answer}?"
end




Handling Errors and Exceptions

Drab intercepts all exceptions from the event handler function and lets them die, but before it presents the error message in the logs, and, for development environment, on the page. For production, it shows just an alert with the generic error.
Please notice that an error in the specific handler function will not harm anything else: all the processing are going on normally.

defhandler raise_error(_socket, _dom_sender) do
  map = %{x: 1, y: 2}
  # the following line will cause KeyError
  map.z
end
By default it is just an alert(), but you can easly override it by creating the template in priv/templates/drab/drab.error_handler.js with your own javascript presenting the error.

There is a very descriptive message in the logs. The same message is shown within the alert on development environment.

[error] Drab Handler failed with the following exception:
** (KeyError) key :z not found in: %{x: 1, y: 2}
    (drab_poc) web/commanders/live_commander.ex:200: DrabPoc.LiveCommander.raise_error/2
    (drab) lib/drab.ex:251: anonymous fn/6 in Drab.handle_event/6

But what when the Handler Process is killed for some reason, other than the exception, - like being brutally murdered? The story is quite similar - we can show the error banner, but in this case there is no way to clean up. In practice the button will stay disabled.

defhandler self_kill(_socket, _dom_sender) do
  Process.exit(self(), :kill)
end


Debugging

There is an easy way to check and debug Drab functions directly in the IEx. The only thing you need to do is to run your Drab-enabled Phoenix server (you can get one with pre-installed Phoenix and Drab from https://github.com/grych/drab-example) with IEx: iex -S mix phoenix.server. Then, connect with the browser to http://localhost:4000 and observe the IEx console. When started, Drab will show you some important debugging information:

[debug]
    Started Drab for controller:Elixir.DrabPoc.LiveController, handling events in DrabPoc.LiveCommander
    You may debug Drab functions in IEx by copy/paste the following:
import Drab.{Core, Waiter, Element, Live}
socket = Drab.get_socket(pid("0.753.0"))

    Examples:
socket |> exec_js("alert('hello from IEx!')")
socket |> poke(count: 42)

As suggested, copy those two lines into IEx and now you can remote control your browser from IEx. Very handy for learning Drab and debugging!

iex(1)> import Drab.{Core, Waiter, Element, Live}
[Drab.Core, Drab.Waiter, Drab.Element, Drab.Live]
iex(2)> socket = Drab.get_socket(pid("0.879.0"))
%Phoenix.Socket{assigns: %{__action: :index,
  ...
transport_pid: #PID<0.876.0>}
iex(3)> query socket, "#uppercase_button", [:text, :className]
{:ok, %{"#uppercase_button" => %{"className" => "btn btn-primary"}}}
iex(4)> set_style socket, "p", backgroundColor: "red"
{:ok, 147}

Please be aware that Drab creates its own process for every single connection, so if you open a page in multiple tabs or browsers, you will have to handle with few different Drab PIDs. Also watch out for automatic page reload after code change - this will re-launch Drab as well, and it will change its PID.


©2016 - 2018 Tomek "Grych" Gryszkiewicz, grych@tg.pl


Warning: this is still a beta version. Any comments, criticism, thoughts are welcome - grych@tg.pl. Your feedback is highly appreciated!


Illustrations by www.Vecteezy.com