Drab - Access the User Interface from the Server Side

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.7.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 + 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.7.0: Drab core rearranged 2018-01-22

v0.7.0 looks small, but it brings important changes in the core.

v0.6.0 new, better living assigns 2017-10-31

This version introduces a completely rewritten Drab.Live module: it is not putting its <span> everywhere into your template. Also introduces Shared Commanders.

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 no 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 a redesigned API for the most essential part of Drab API: Drab.Core. execjs and broadcastjs has been deprecated in favor of exec_js and broadcast_js. Exciting!

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

  <input name="text_to_uppercase" value="<%= @text %>">
  <button drab="click:uppercase">Upcase</button>
  <button drab="click:downcase">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. 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

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

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

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

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 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
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 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"
<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 want 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 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:

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)

  ref = make_ref()
  DrabPoc.Presence.add_user(ref, nickname)
  put_store(socket, :my_drab_ref, ref)
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 a reference as a key, which will be stored on the client side with the Drab Store. We are going to need it while handling disconnects.

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

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

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 really think it is #{answer}?"

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.

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

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

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