October 23, 2016
I’ve been completely immersed in Elixir and Phoenix for the past 4 months, and have been enjoying the mixture of familiar (Phoenix owes some debts to Rails) and the new (sequential Erlang’s purely functional programming, and Erlang’s philosophy of building programs on small lightweight processes instead of objects).
There was a magical moment during Chris McCord’s keynote at ElixirConf 2016 where he was demonstrating the Presence feature of the Phoenix Framework through a collaborative drawing app he hosted on his laptop. As he spoke, the audience connected their laptops to the same page and quickly disrupted the presentation, adding their own little touches to the drawing he’d made.
I’ve seen this type of demo before, when Bonjour was new and developers were experimenting with collaborative apps, but my brain has been totally wired to expect Web apps to be slow and laggy when doing something highly interactive. I credit that responsiveness to Erlang, doing the heavy lifting in the background.
How hard is it to write the basic version of this app?
Phoenix offers so much through it’s channels feature, that I was able to bring up a (very) bare-bones version of this in a couple hours.
You can check out the app at: https://shrouded-cliffs-40854.herokuapp.com. It’s more fun if you open up two browser windows or point two separate devices to the same address.
<img src="/images/2016-10-23-phoenix_draw/demo.png" width=“85% height=“85%">
Here’s how to do it:
Step 1 – Create a Phoenix template app
In my first commit, I started with the standard Phoenix template. To keep it simple, I didn’t add any Ecto code.
Step 2 – Add a canvas
In my second commit, I added a canvas element which tracks input events over its surface, and draws a line by calling
drawLine(from, to). It’s important to think of this as the code in the page sending a message to itself, through a function call.
Step 3 – Send draw messages to the server
In my third commit, I added a channel on the server side, to accept a “drawLine” message and in accepting it, the message is re-broadcast to every listening socket for that room.
Now on the client side, I changed the
drawLine() function so that instead of drawing a line, it sends the coordinates to the server (remember that the server will re-broadcast the message to every connected page). Then I installed a handler on the channel to listen to the message and draw a line. So, in a sense, the page is still sending a message to itself, just like it did before, but this time the server is in the middle.
Step 4 – Actually, that’s it
There is no more coding. Now that all pages are listening for drawing events, when you draw on one page, the draw message is sent to all pages (including the one that originally sent the message).
There are some obvious shortcomings to this barebones implementation. First and foremost, due to the networking round-trip, there is a delay between receiving an input event and something actually being drawn on the canvas. Additionally, if the network goes away, the message will never be received, meaning the canvas will remain blank, no matter what the user does.
I have a proposed solution to this problem in this commit, but for this post, I wanted to focus on the simplicity of getting this up and running.
Summary – Phoenix is Fun!
All in all, I was surprised and delighted how fast it was to put something (admittedly simple) like this together. With WebSockets and a fast, responsive backend, the browser is starting to feel closer to an HTML-flavored display server to the erlang-powered application.
I can’t wait to start seeing sites take advantage of this.