Drab - Access the User Interface from the Server Side

Living Pages in Phoenix

Drab is the extention 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. You can find a source code of the Drab - here 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, silverlights, etc. It works in any modern broser capable to run html5 + javascript.

Teaser: imagine having a Phoenix template like:

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

© Tomek "Grych" Gryszkiewicz 2016 - 2017

v0.5.0 - living assigns 2017-07-17

WARNING! Drab API has been completely changed in 0.5.0! Drab.Query (the jQuery based module) is not longer the default one. If you still want to use it, you must declare it in the commander with use Drab.Commander, modules: [Drab.Query, Drab.Modal]. Drab.Query based tutorial is still available here.

v0.4.0 improves reliability 2017-05-16

This version introduces redesigned API for the most essential part of Drab API: Drab.Core. execjs and broadcastjs has been depreciated in favor of exec_js and broadcast_js. Exciting!

New example - The Chat 2017-05-04

What is the websocket-based application without a chat? Now, you can learn how to create the simplest one with Drab, based on this example.

Drab v0.3.2 - tests, tests and tests 2017-04-14

The new release introductes automated integration tests, finally. In addition, Drab package on github contains its own Phoenix Server, makes it easier to play with it.

See all Release Notes to learn about the previous releases


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 a basic HTML with the input and the button:

  <input name="text_to_uppercase" value="<%= @text %>">
  <button drab-event="click" drab-handler="uppercase">Upcase</button>
  <button drab-event="click" drab-handler="lowercase">Downcase</button>

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. The pair of drab-event and drab-handler attributes in the <button> determines which event should be handled by which handler. 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

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

Every handler 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 imprortant 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
  use Drab.Controller

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

Nothing special yet, except use Drab.Controller, which tells the controller that is has a corresponding commander. But, 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 a shorthand for drab-event and drab-handler combination. There is a number of such shorthands for most popular events, see documentation for more.

The event handler function simply replaces the users list with the new one:

defmodule DrabPoc.LiveCommander do
  use Drab.Commander

  def replace_list(socket, _sender) do
    Drab.Live.poke socket, users: ["Mścisław", "Bożydar", "Mściwój", "Bogumił", "Mirmił"]
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:

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

Last, but not least, is a 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:

def add_to_list(socket, _sender) do
  users = Drab.Live.peek(socket, :users)
  Drab.Live.poke socket, users: users ++ ["Hegemon"]

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}%" %>
<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:

def 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)

  poke socket, progress_bar_class: "progress-bar-success", 
    long_process_button_text: "Click me to restart"
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.

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 behaviour in `config.exs` with:

config :drab, disable_controls_while_processing: false

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


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.

HTML side contains button and 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 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 run_async_tasks/2 function:

def 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")

  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"
The code above runs asynchronously 54 Tasks, which just wait some time (up to 4 seconds) and then communicate to the user by changing the boostrap 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, except of Drab natural beauty, you can't do it with AJAX. You browser limits the number of simultaneus 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 resouces!

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
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

  file_change_loop(socket, Application.get_env(:drab_poc, :watch_file))
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))
  file_change_loop(socket, file_path)

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>
def changed_input(socket, sender) do
  broadcast_prop socket, sender["dataset"]["update"], innerText: String.upcase(sender["value"])

Did you notice the new broadcast_prop/3 function? This one works exactly like set_prop/3, but it broadcasts to all the 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.
By default, broadcasts are sent to the displayers of the current page, but you may change this behaviour 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" 
<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):

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.

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

def show_counter(socket, _sender) do
  poke socket, counter: get_store(socket, :counter)

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 wan to access in access_session/1 macro (you may give it a list of atoms or a single atom). Whitelisting is due to security: it is kept in Token, on the client side, so it is signed but not encrypted.
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"
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)
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 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:

def 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.

def update_chat(socket, sender) do
  nick = get_store(socket, :nickname, "Anonymous")
  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"
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)})

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

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

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. Again, please treat it only as an example of Drab features - there are better ways to tracking the presence in Phoenix.

def connected(socket) do
  nickname = get_store(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)

  DrabPoc.Presence.add_user(Node.self(), Drab.pid(socket), nickname)
  put_store(socket, :my_drab_pid, Drab.pid(socket))
The code above is executed every time your browser connects to the server. First, we broadcasts the information to all to show them that some has joined the chat. Second, we store the information about connected user in the special store, DrabPoc.Presence. It is just a simple Agent, which stores key/value pairs. It is out of the scope of this tutorial, but if you are interested how it works, read the helpful source.

We are going to use tuple {node, drab_pid} as a key, and the nickname as a value. When you connect to the page which uses Drab, it creates its own GenServer - drab_pid is the PID of this process. Node name is used in the key, as this webpage runs on multiple Phoenix servers.

Last, we save drab_pid to the Drab Store. We are going to need it while handling disconnects.

Let's move to the disconnect handler. As you probably guess, it is executed when the browser disconnects from the server. It may be because of navigate 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
  DrabPoc.Presence.remove_user(Node.self(), store[:my_drab_pid])
  removed_user = store[:nickname] || anon_with_country_code(session[:country_code])
  html = ~E"<%= removed_user %> has left." |> safe_to_string()
  add_chat_message!(same_controller(DrabPoc.PageController), html)
We are going to use the Drab Store to retrieve the user's nickname, and the Drab process PID (remember, it was set previously in onconnect handler). First, we need to remove the connection information from the presence list. We could finish here, but it would be good to inform all other users that this guy 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 a 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.

As a bonus, we've got the list of connected users. Let's use it and do a live list of all the people currently connected to this page. First, create a function to update the panel on the right:

defp update_presence_list!(socket_or_subject) do
  users = DrabPoc.Presence.get_users() 
    |> Map.values() 
    |> 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
Second, run this function on connect, disconnect or nickname change. Voila!

Connected users

Drab Waiter

Sometimes it would be good to have a 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 wheter 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.

def 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) ->
    on_timeout 5500, fn ->
      "six times nine"

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

Handling Errors and Exceptions

Drab intercepts all exceptions from event handler function and let it 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.

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

There is much 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 other, than the exception, reason - 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.

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


There is an easy way to check and debug Drab functions directly in the IEx. The only 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:

    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"))

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, it will change its PID.

©2016 - 2017 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