Last Updated: February 25, 2016
·
3.106K
· joefiorini

User Interface Thinking in Rails: An Example (Part 1)

In my previous tip I outlined the shift in thinking that has to happen when you move from developing apps on the server-side to building them with a fairly rich user interface. In this tip I also mentioned that I’ve found it easier with some apps to build them in Rails first, without Javascript, and then enhance the behavior with Javascript afterwards. In my experience, with any of the standard Javascript MVC frameworks, this typically requires some heavy refactoring of my Rails controllers & views. Now I want to show some examples of an approach I’ve been using that I find much easier.

In this post we'll build a simple Rails app and then start adding Javascript on top of it. We'll analyze how the view is constructed and break that down into UI components. In a following tip, I'll finally show how to respond to these events on the server, in a lightweight and isolated way, to get just the UI components we care about.

A Rails App

Let’s build an app based on the TodoMVC examples and implement adding/removing a todo. We’ll need just one model:

class Todo < ActiveRecord::Base
end

create_table "todos", :force => true do |t|
  t.string   "description"
  t.boolean "complete"
  t.datetime "created_at", :null => false
  t.datetime "updated_at", :null => false
end

For now we'll just use an extremely basic Rails controller with actions for index, create and destroy:

class TodosController < ApplicationController
  def index
    @todos = Todo.all
    @new_todo = Todo.new # for the form on our index view
  end

  def create
    @todo = Todo.create params[:todo]
     respond_with @todo
   end

   def destroy
     @todo = Todo.find params[:id]
     @todo.destroy
   end

end

And then we'll display our todo list on the index view with a form to add new todos:

<section id="todoapp">
  <header id="header">
    <h1>todos</h1>
    <%= form_for @new_todo do |f| %>
      <%= f.text_field :description, placeholder: "What needs to be done?", autofocus: true %>
    <% end %>
  </header>
  <section id="main">
    <ul id="todo-list">
      <% @todos.each do |todo| %>
        <li class="todo">
          <div class="view">
            <%= form_for todo, method: delete do |f| %>
              <%= f.check_box :completed, class: "toggle" %>
              <%= f.label :completed, todo.description %>
              <button class="destroy"></button>
            <% end %>
          </div>
        </li>
      <% end %>
    </ul>
  </section>
</section>

If this looks a little weird to you, that's probably because we're combining all the steps of a CRUD application into one view. At the top we have a form to create a new todo, and within the list we have the ability to delete posts. There are also accomodations for updating (completing) a todo and editing, which we are not worried about in this example.

This should take care of our Rails app. Now I can create new todos and delete them. But every time I perform an action, the page refreshes, obviously, since this is still a server side application. Let's introduce some "user interface thinking" and refactor this to something we could make a little more client-side friendly.

Components

We need the ability to add and delete todos. We'll be using ajax requests in our Javascript to perform these actions and get back rendered content. Then we'll use a little Javascript to replace the existing content. For this Javascript code we'll be using Flight, a (very) small web framework from Twitter (I'll be using the standalone version for this example). Let's break this down one action at a time and think through how our widgets should behave.

Adding a Todo

When the user adds a new todo, we need to tell the server we're creating a todo, give it the content the user entered and take the response rendered by the server and add it to the list, maybe with a little animation. Now we're seeing a UI concept take shape: a todo list that can have items added to it (the level to which you abstract this concept depends heavily upon your app). In Flight we represent this UI concept as a "component". A components is an isolated object attached to a DOM node that indirectly communicates with other components via events.

Let's start by defining our component:

var TodoList = flight.component(function(){
  this.addTodo = function(todoListItem){
    this.$node.append(todoListItem);
  }
});

TodoList.attachTo("#todo-list");

Because we called attachTo with the #todo-list selector, the this.$node property in our component refers to the jQuery-wrapped instance of <ul id="todo-list">. We can call this.addTodo with the already-rendered markup containing a list item and jQuery will append it to our list.

But, how do we actually post the new todo to the server in order to get the rendered markup? We'll let another component handle that:

var TodoForm = flight.component(function(){
  function createTodo(e){
    e.preventDefault();
    var url = $('#new_todo').attr('action');
    $.post(url, { todo: { description: $("#todo_description").val() } }).then(function(result){
      // What to do here?
    });
  }
  this.after('initialize', function(){
    this.on('submit', createTodo);
  });
});

TodoForm.attachTo('#new_todo');

We have a lot going on here. Let's start with the call to this.after('initialize', ...). This is what Flight calls advice. It is called after the component is initialized by the framework. This is where you can setup your event bindings. Here we're going to wait for the user to submit the Rails-generated new todo form, then we'll call the private createTodo function. Create todo uses the URL from the form (that way we can update our routes without breaking Javascript code) and does an ajax post to that url, sending the description back as a form parameter. Upon successful completion of the post (the following call to .then) we can take the result and... what? The way the controller is currently written, the server will render the entire view, not just the new todo item. We need some way to render just the list item.

For that, we'll have to wait until the next tip. Until then, take some take and reflect on what we did here. Look at your own apps and think about how you might be able to break them down into UI components like this. In the next tip we'll look at how we're going to render the todo on the server and then send it back to the todo list.