Chronicling the trials and tribulations of developing for the modern web.



How to make an AJAX todo list checkbox

Posted by Pat Nakajima on November 07, 2007 in Development.

One of the most compelling apps when it comes to introducing users to Rails is Ta-da Lists. Almost every Rails developer has built his/her own todo list app (including me), and almost every one of these todo list apps contains the requisite AJAX checkboxes.

I don’t know about you, but when I first tried to make my checkboxes “all ajaxy,” I had a bit of trouble deciding the best way. There’s no “checkbox_to_remote” helper in Rails. After building several todo list apps, I’m convinced that the best way to create the AJAX checkbox behavior doesn’t live in Rails at all. It lives in Prototype and LowPro, my two favorite Javascript libraries.

You’ve probably heard of Prototype before. If not, you’re probably reading the wrong blog (or you’re my mom. Hi Mom.) Prototype makes the normally somewhat ugly language Javascript as beautiful as a Ruby. In other words, it takes care of a lot of the browser incompatibilities that usually plague Javascript development, as well as adds a bunch of syntactical sugar to make developing with Javascript a more enjoyable experience.

Dan Webb’s LowPro library allows you to do a few cool things with Prototype, but the one we’ll use is its Behaviors feature. Behaviors allow you to designate different behaviors for the elements on your page based on CSS selectors such as class name, ID, etc. I wrote about this in a bit more detail here.

Let’s say you’ve built your todo list app like so:

Routes

map.resources :lists do |list|
  list.resources :todos
end

Models

class List < ActiveRecord::Base
  has_many :todos, :dependent => :destroy
  has_many :incomplete_todos, :class_name => "Todo", :foreign_key => :list_id, :conditions => ['todos.complete = ?', false]
  has_many :complete_todos,   :class_name => "Todo", :foreign_key => :list_id, :conditions => ['todos.complete = ?', true]
end
class Todo < ActiveRecord::Base
  belongs_to :list
end

Pertinent Actions

class ListsController < ApplicationController
  def show
    @list  = List.find(params[:id], :include => [:incomplete_todos, :complete_todos])
    respond_to do |format|
      format.html # default
    end
  end
end
class TodosController < ApplicationController
  def update
    @list = List.find(params[:list_id])
    @todo = @list.todos.find(params[:id])
    respond_to do |format|
      if @todo.update_attributes(params[:todo])
        flash[:notice] = 'Todo was successfully updated.'
        format.html { redirect_to(@list) }
        format.js   { render :action => "updated" } # This is a .rjs template
      else
        format.html { render :action => "edit" }
      end
    end
  end
end

The lists/show view

<ul id="incomplete_list">
  <% @list.incomplete_todos.each do |todo| %>
  <li id="list_<%= todo.list_id %>_todo_<%= todo.id %>" class="todo">
    <%= checkbox :todo, :complete, :class => "checkbox" %>
    <%= todo.name %>
  </li>
  <% end %>
</ul>

Some Javascript

Event.addBehavior({
  // Defining the checkbox behavior and assigning to the appropriate elements
  'li.todo input.checkbox:click' : function(event) {
    Todo.update(this.parentNode.id);
  }
})
// A module containing the actual function to perform the AJAX request
var Todo = {
  update: function(id) {
    var element  = $(id);
    var checkbox = element.firstDescendent();
    var list_id  = id.gsub('list_','').split('_todo_')[0];
    var todo_id  = id.gsub('list_','').split('_todo_')[1];
    var url  = '/lists/' + list_id + '/todos/' + todo_id;
    var pars = $H({
      todo: { 'complete':$F(checkbox); }
    })
    new Ajax.Request(url, { parameters: pars.toQueryString(), method: 'put' })
  }
}

And that should do it. I could have made one or ten typos, since I was just typing this code off the top of my head and haven’t actually gone about testing it. If all goes to plan though, clicking a checkbox with the class name “checkbox” within an li with the class name “todo” should submit an AJAX request. The response is up to you and probably merits another post from me at some point. Not now though. Enjoy.

Taking Merb for a spin

Posted by Pat Nakajima on October 10, 2007 in Development.

A few nights ago, I had a hankering to write in a personal blog. My old one, based on WordPress, died unexpectedly, and rather than wrangle with PHP, I decided it’d be fun to build a new one from scratch. I decided to give Merb a whirl, since everybody says it’s so dandy, and my personal blog needs next to nothing when it comes to features. The ability to write posts, view those posts in a list on a front page, edit posts, and add comments are pretty much all I need. I figured Merb could handle it.

Was I right? Yes and no. Merb can handle the app that I described above. Can it do so elegantly? Not so much. Here’s why.

No nested routes

I’m a RESTful guy. I like resources. Merb lets you create resources, but you can’t nest them. What’s the difference? Nesting basically allows you to reveal your models’ resources through their routes. So while Rails gives me something like this:

map.resources :posts do |post|
  post.resources :comments
end
# Gives me routes like this: /posts/1/comments
# which imposes a helpful scope for comments based
# on their parent post
Merb left me with this at best:
map.resources :posts
map.resources :comments
# Gives me routes like this:
# - /posts/1
# - /comments/1
# which group all comments together. The only way
# to access comments for a particular post would
# be to include the post_id as a parameter.

More convention would be nice

This one is up for debate, in fact, one of the features that Merb’s creator, Ezra Zygmuntowicz, advertises is the fact that Merb is a hackers’ framework. That is to say that Merb doesn’t penalize you for leaving the “opinionated” path, as Rails is prone to doing. That makes it great for simple apps (Merb is great for building web service apps). It makes it a bit of a hassle to create a full web app with a front end and everything.

It’s not Rails.

I’ll be honest. I’m very familiar with Rails development. I’m not very familiar with Merb development. I’m always going to favor the devil I know over the devil I don’t.

Conclusion

Merb is lightning fast. Its speed is not to be underestimated. However, at this point, developing with Merb feels a bit too much like developing with a crippled Rails.

While it’s not ready for prime-time yet, Merb is definitely worth a look, and I eagerly await the day it reaches maturity.

Have you had a better experience with Merb? Am I totally wrong? A little bit wrong? Tell me about it in the comments. Give Merb a fighting chance.

A short list of great things

Posted by Pat Nakajima on September 18, 2007 in Development.

Unobtrusive Javascript with Dan Webb’s Low Pro

Unobtrusive Javascript means a lot of things to different people, so to explain Low Pro’s greatness, I’ll give you a few examples of what you can do. In the same way you use a CSS file to modify and HTML class’ style, you can now use Unobtrusive Javascript to modify an HTML class’ behavior. So instead of targeting different elements on your page to receive certain behaviors, as was the custom before Unobtrusive Javascript. So let’s say we have this little script to show a notification on your page:

// Shows notification message for 3 seconds.
function notify(message) {
  $('notification_label').update(message);
  new Effect.SlideDown('notification_bar');
  new Effect.SlideUp('notification_bar', {
    delay: 3,
    queue: 'end'
  });
  return false;
}

The really old way would look like this:

<!-- Inline "onclick" attributes are NOT a Good Thing -->
<a href="#" id="notifier_link" class="notifiers" onclick="notify('This is the message');">Click here to show the message.</a>

But everybody knows that inline “onclick” attributes are just as bad as inline style attributes. That’s why it was so great when prototype brought along Element observers. They let us do this:

// Observers are cool... sort of.
$('notifier_link').observe('click', function(event){
  notify('This is the message');
 });

The trouble was that we were still basically shooting HTML elements with Javascript behavior. If you wrote a Javascript and included it in your HTML document, you couldn’t just add a class name to your HTML elements to give them behavior the same way you give them style. No, you’d have to modify the Javascript to give behavior to specific page elements. And that’s where Low Pro comes in.

// Unobtrusive Javascript to the rescue.
Event.addBehavior({
  'a.notifiers:click':function(){
    notify('This is the message.');
  }
});

With that code, any page element with the class name “notifiers” will automatically pickup the behavior described in that script. Using CSS selectors to designate behaviors is technique I’m really starting to get into, and I’m surprised that more people haven’t picked it up

rcov: code coverage for Ruby

With Test Driven Development, your code usually has pretty good test coverage by default. Still, sometimes things slip through the cracks. If you’re wondering what your tests are missing, check out rcov. It runs through your test suite, then generates HTML docs that highlight any parts of your code that your tests aren’t running.

I use rcov with my Test::Unit tests, but it also has solid compatibility with the Behavior Driven Development tool RSpec.

Camping: A microframework

I used Camping to build patnakajima.com, as well as fun.patnakajima.net. It’s an incredibly simple MVC web framework implemented entirely in Ruby by Why the Lucky Stiff. Camping integrates with Mongrel, ActiveRecord, and all sorts of Ruby templating languages (Markaby, Haml). Give it a try. It’s fun stuff.

Introducing nakajimaTools

Posted by Pat Nakajima on August 26, 2007 in Development.

Update: You may have noticed that this domain is no longer active. Sorry about that. It was just sort of a pain to maintain the site, so I just dumped everything on my GitHub profile. See it here: http://github.com/nakajima.

It’s been a little while since I wrote a post for devthatweb. In the time since I last posted, I’ve been pretty busy with a few different projects. I also put together a quick new site to help showcase my various open source tools. It uses a small Camping server to grab the latest versions of code from the repository.

You can visit the site at fun.patnakajima.com. The Capistrano General Deployment Capfile that used to be located at recipe.devthatweb.com is there, as well as a few cool Javascripts I’ve created that designate element behavior by just adding a class name to the element (I’ll write more about those later).

So go ahead and check out the new site. It’s plain, but hopefully you’ll fine something interesting. If you have any thoughts, please leave them in the comments of this post. Thanks!

Deploy any project using Capistrano 2

Posted by Pat Nakajima on June 28, 2007 in Development.

Feel left out of the Capistrano party because you don’t use Rails? Take a trip to fun.patnakajima.com/recipe and follow the directions. What you’ll find there is an almost completely redone recipe for deploying pretty much almost any project you have.

Requirements

  • You use some sort of source code management (Subversion, CVS, Perforce, etc.)
  • You have SSH access to the remote server, and administrative privileges.
  • You want to start deploying your project like the cool kids.
Here’s a look at what the recipe looks like:

load 'deploy' if respond_to?(:namespace) # cap2 differentiator

require 'rubygems'
require 'bells/recipes/apache' # This one requires the Bells gem

set :application, "your project's name" 
set :domain, "your.project.domain" 
role :app, domain
role :web, domain
role :db,  domain, :primary => true

# Set the Unix user and group that will actually perform each task
# on the remote server. The defaults are set to 'deploy'
set :user, "deploy" 
set :group, "deploy" 

# Deployment Settings
set :repository,  "set your repository path here" 
set :deploy_to, "/var/www/sites/#{application}" # This is where your project will be deployed.

# Uncomment this if you want to deploy your project by copying
# the files from your local computer.
# set :deploy_via, :copy

# =============================================================
# Apache Settings
# =============================================================
set :apache_server_name, nil
set :apache_conf_path, "/usr/local/apache2/conf/sites" 
set :apache_conf_file, "#{application}.conf" 
set :apache_ctl, "/usr/local/apache2/bin/apachectl" 
set :apache_server_aliases, []
# set :apache_ssl_enabled, false
# set :apache_ssl_ip, nil
# set :apache_ssl_forward_all, false
# set :apache_ssl_chainfile, false

# In the event you have the urge to develop your own vhost erb template, you can modify
# this variable. I wouldn't do it unless you're strongly confident in what you're doing.
set :apache_conf_template, 'http://svn.nakadev.com/tools/recipe/templates/vhost.conf'

# ================================================================================================
# DON'T MODIFY ANYTHING BELOW THIS POINT UNLESS YOU KNOW WHAT YOU ARE DOING
# ================================================================================================

# Making sure things will go smoothly.
depend :remote, :directory, apache_conf_path
depend :remote, :directory, deploy_to
depend :local, :gem, 'bells'

namespace :deploy do
  namespace :apache do
    desc "Setup vhost conf on Apache web server." 
    task :setup do
      sudo "mkdir -p #{apache_conf_path}" 
      sudo "chown -R #{user}:#{group} #{apache_conf_path} && chmod -R 775 #{apache_conf_path}" 
      logger.info "generating .conf file" 
      conf = Net::HTTP.get URI.parse(apache_conf_template)
      require 'erb'
      result = ERB.new(conf).result(binding)
      logger.info "putting #{application}.conf on #{domain}" 
      put result, "#{application}.conf" 
      run "mv #{application}.conf #{apache_conf_path}/#{apache_conf_file}" 
    end
  end

  # Overwritten to provide flexibility for people who aren't using Rails.
  task :setup, :except => { :no_release => true } do
    dirs = [deploy_to, releases_path, shared_path]
    dirs += %w(system).map { |d| File.join(shared_path, d) }
    run "umask 02 && mkdir -p #{dirs.join(' ')}" 
  end

  # Also overwritten to remove Rails-specific code.
  task :finalize_update, :except => { :no_release => true } do
    run "chmod -R g+w #{release_path}" if fetch(:group_writable, true)
  end

  # Each of the following tasks are Rails specific. They're removed.
  task :migrate do
  end

  task :migrations do
  end

  task :cold do
  end

  task :start do
  end

  task :stop do
  end

  # Do nothing (To restart apache, run 'cap deploy:apache:restart')
  task :restart do
  end
end

Note: For the most recent version of this recipe, visit fun.patnakajima.com/recipe.

As always, if you have any comments about the recipe, leave them here on this post.