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



Remember the :active styles

Posted by Pat Nakajima on May 05, 2008 in Web Applications.

The hyperlinks of today look way better than they did five years ago. Designers and developers have become better and better at styling links to not only be usable, but beautiful as well. We know how to indicate the basic behavior of a link using only color (for example, red usually indicates a destructive action). And we know how to style links to give them a stronger affordance of click-ability.

Most web applications make clever use of the :hover pseudo-class, which allows us to tweak link backgrounds to add an even stronger affordance of click-ability, and draws the eye to the link, which focuses the user on task at hand. All of 37signals’ apps provide great examples of tasteful rollover link styles.

When we begin to think about rollover styles, we’re no longer thinking about how the app looks. We’re thinking about how it feels. Different styles for link rollovers make a page feel much more responsive. Everybody these days knows this, and takes advantage of it.

Unfortunately, few people take advantage of the :active pseudo-class, which allows you to style the link differently while it’s being clicked. This is just as important, if not more important than the rollover state. Changing a link’s :active style provides immediate visual feedback that the link is being clicked.

Try each of the following links. See which feels better.

This link does not have an active state.

This link does have an active state.

The chances of a person successfully completing a task increase with the level of feedback that that he/she receives before, while, and after performing that task. (if you’ve read The Design of Everyday Things, you know what I’m talking about). With links, the “before” feedback can be achieved using :hover styles, and the “after” feedback is usually just the browser doing something, be it going to a different page or just updating something dynamically. The “during” feedback can be achieved by simply using a good :active style.

Now, just as much thought must be given to a link’s :active style as to its :hover style, if not more. Usually, a click is less than a second, so changing the style too much can be distracting, and actually do more harm than good. I’ve found that the best :active styles tend to just make the background a bit darker. I first saw this on flickr (which was where I first noticed the :active state in use and fell in love). You might also think about adding an underline during the click. Experiment and see what works best for you.

One thing’s for sure though. Once you feel the difference in your web apps between a good :active style and a non-existent one, you’ll wish every site made use of them.

Add a comment

Bells is Back

Posted by Pat Nakajima on March 24, 2008 in Ruby or Rails.

Remember Capistrano Bells? The Rails plugin I created so long ago, then neglected for so long? Well it’s back. And it has a bunch of new recipes for your deployment pleasure.

Nginx Recipes

You know about Nginx right? That hip new web server all the cool kids are clamoring about? Well now Bells has built-in recipes to make configuring new Nginx virtual hosts a snap:

cap deploy:nginx:configure
How does Bells know where you want your Nginx vhosts stored? Why with the new options added to the Capfile template of course!
set :nginx_sites_available, "/usr/local/nginx/sites-available" 
set :nginx_sites_enabled, "/usr/local/nginx/sites-enabled" 

Those are just the defaults. You can modify them however you wish.

Thin Recipes

Almost as trendy as Nginx is Thin, a new Ruby web server that takes Mongrel’s parser and mashes it up with EventMachine with Rack. It’s quick and simple, especially with Bells’ new Thin recipes.

cap thin:configure
cap thin:start
cap thin:stop
cap thin:restart
I’ve been working on making Bells a bit more app server agnostic, which allows you to use Mongrel or Thin almost interchangeably. Just choose which you want to use like so:
set :app_server, :mongrel # or :thin if you please
Then specify the app server settings:
set :app_servers, 1
set :app_server_port, 7007
set :app_environment, 'production'
set :app_server_address, '127.0.0.1'
set :app_server_conf, "#{shared_path}/config/mongrel_conf.yml" 
I’ve done away with Mongrel specific settings, since Thin and Mongrel’s settings are pretty much the same.

So that’s about it. The project is on GitHub now. You can check out its page, or clone a copy yourself like so:

git clone git://github.com/nakajima/capistrano-bells.git

If you’re running edge Rails, you can even install the plugin directly from GitHub:

script/plugin install git clone git://github.com/nakajima/capistrano-bells.git

If you have any questions/suggestions, hit the wiki.

1 Comment

Basic event delegation in Prototype

Posted by Pat Nakajima on February 13, 2008 in Javascript.
Event delegation is a technique that I wish I picked up a long time ago. If you’re unfamiliar with it, check out this post from the YUI blog for a terrific explanation. Then check out Dan Webb’s implementation for jQuery. Then come on back here to see how we can do the same with Prototype.

Event delegation is made possible by the fact that certain DOM events “bubble” up through their ancestors. So if we listen for onclick events in the body, we’ll should hear every one that occurs throughout the page. And if we want to assign certain behaviors to certain elements’ onclick event, we just have to check to see if the target element of the event matches the group of elements to which we want to assign a particular behavior. Make sense? Not so much? Here’s some code:
Object.extend(Event, (function(){
  return {
    // Delegates a single behavior to elements that
    // match the targetSelector
    delegate: function(element, eventName, targetSelector, handler) {
      var element = $(element);
      function createDelegation(_delegatedEvent) {
        var origin = _delegatedEvent.element();
        if ( origin.match(targetSelector) ){ return handler(_delegatedEvent); }
      };
      element.observe(eventName, createDelegation);
      return element;
    },

    // Delegates multiple behaviors for a single event name,
    // LowPro style.
    delegators: function(element, eventName, rules) {
      var element = $(element);
      function delegateRule(rule) {
        element.delegate(eventName, rule.key, rule.value)
      }
      $H(rules).each(delegateRule)
      return element;
    }
  }
})())

Element.addMethods({
  delegate: Event.delegate,
  delegators: Event.delegators
})

Object.extend(document, {
  delegate: Event.delegate,
  delegators: Event.delegators
})
With that snippet, we have two new methods available to our page elements: delegate and delegators (this could definitely be refactored into a cleaner implementation, but for the sake of illustration, it’s pretty good). So now if we had a div with some links in it, we could delegate behaviors within that div like so:
ElementBehaviors = {
  // I create an alert message out of my target element's innerHTML
  alertify: function(event) {
    var element = event.element();
    alert(element.innerHTML);
    event.stop();
  },

  // I remove my target element
  removify: function(event) {
    var element = event.element();
    element.remove();
    event.stop();
  }
}

$('div_id').delegators('click', {
  '.alert': ElementBehaviors.alertify,
  '.remove': ElementBehaviors.removify
})

Now, links within $('div_id') with the class name .alert will have their innerHTML alerted when clicked, and elements with the class name remove will just be removed when clicked, even if they are added to the page dynamically. No reloading or reassignmening of handlers is necessary.

So that’s great. But what if you want to event delegation for events that don’t bubble, such as form submissions?

Simulating event bubbling with Prototype’s custom events.

To simulate event bubbling, we have to resort to listening for bubbling events that can cause non-bubbling events. By checking the circumstances surrounding these “trigger” events, we can determine whether or not to fire a custom event. Here’s some code:
var Bubbler = {
  // Checks to see whether or not this element will submit  
  // a form if the Enter key is pressed within it.
  submittableInput: function(element) {
    var element = $(element);
    return ( element.match('input[type=text]') || element.match('input[type=password]') )
  },

  // Checks to see whether or not this element will submit a
  // form if clicked.
  submitButton: function(element) {
    var element = $(element);
    return ( element.match('input[type=submit]') || element.match('input[type=image]') )
  },

  Behaviors: {
    // Fires the 'form:submitted' custom event if the Enter key was 
    // pressed while the cursor was within a input that would submit
    // a form.
    keypress: function(event) {
      if ( event.keyCode == 13 ) {
        var element = event.element();
        if ( Bubbler.submittableInput(element) ){
          element.form.fire('form:submitted', { 'originalEvent': event });
        }
      }
    },

    // Fires the 'form:submitted' custom event if an element that 
    // would submit the form was clicked.
    click: function(event) {
      var element = event.element();      
      if ( Bubbler.submitButton(element) ) {
        element.form.fire('form:submitted', { 'originalEvent': event });
      }
    }
  }
}

// Always remaining vigilant.
Event.observe(document, 'keypress', Bubbler.Behaviors.keypress)    
Event.observe(document, 'click', Bubbler.Behaviors.click)

The above code continually listens to all keypress and onclick events in the entire document. When one of them matches the conditions required to submit a form, it’s smart enough to find that particular form element, and fires the ‘form:submitted’ custom event from it. Custom events bubble up through the DOM.

Prototype’s custom events also have a “memo” hash which can be used to store additional information about the event. This code makes use of it by stashing the original trigger element with the key ‘originalElement’ where it can be accessed by whatever handler (or delegator) takes the custom event. Let’s take a look at a delegator here:
var FormBehaviors = {
  // Takes a custom event, submit's the event's target (a form) 
  // via AJAX and stops the trigger event if it exists.
  remotify: function(event) {
    var element = event.element();
    element.request();
    if ( event.memo['originalEvent'] != null )
      event.memo['originalEvent'].stop()
    event.stop();
  }
}

document.delegate('form:submitted', '.remotify', FormBehaviors.remotify)

So now, any form with the class name “remotify” will be submitted via AJAX, again including those added dynamically. And again, no handler refreshes or reassignments were necessary.

If you haven’t played with event delegation yet, give it a try. And if you’re way better than me at Javascript, and can point out some better ways for what I’ve described above, please do share in the comments.

4 Comments

Parsing Parameters with Javascript

Posted by Pat Nakajima on November 19, 2007 in Programming.
Update: While this post is nice, a much better way to parse parameters is to use Prototype’s (if you’re using Prototype that is) built-in String#toQueryParams() method. To quote the official documentation, this method “Parses a URI-like query string and returns an object composed of parameter/value pairs.” Live and learn.

Wish you had a params object to use in your Javascript, just like the one you get in your Rails controllers? Well wish no longer. Just add the following script to your application code:
params = { };
document.URL.split('?')[1].split('&').each(function(par) {
  params[par.split('=')[0]] = par.split('=')[1];
})

Then when you’re on a page with a URL like “http://your-site.com/users?name=pat”, you can access the value of the name parameter like so: params['name'].

The biggest limitation of this script is that it’s limited to GET params, in other words, the ones found in the URL. Still, it’s a clean and easy way to get a Rails-like syntax in your Javascript.

Add a comment

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.

Add a comment