Templated attributes in ActiveRecord

Vandal spraypainting a wall using an 'http://' template
On a recent project, we decided it would be nice to have “templated attributes” for certain fields in a form. A templated attribute has a helpful initial value—kind of like a default value—except that these aren’t valid data or saved in the database. They’re suggestions to the user about the expected formatting or content of a field.

So the game is:

  1. keep these values out of the database
  2. specify these values once in the model, super-duper-DRY
  3. create a user experience that clearly implies that these values are just templates for valid data

Case one: a website attribute

We wanted a website attribute to be prefilled with http:// as a suggestion to the user about proper URL formatting—most users will just enter “example.com” when prompted for a URL. This is to avoid XSS problems with javascript: URIs and the like, and to keep browsers from resolving short URLs to be internal—nothing quite like being sent to http://yoursite.com/www.theirsite.com.

(Aside: there are better ways to handle this specific situation: a :before_validation callback to fix invalid URLs and check for XSS attacks, or the fantastic white_list plugin. But for now I’m interested in the general case.)

This attribute needed certain behavior:

  • When the user hits the form, the field should be pre-filled with http:// if the real value is empty or nil.
  • If the field is left as http://, we should convert it to nil before validation.
  • Client-side: to imply that the initial value is a suggestion, we’ll make the text color gray until the user makes a change. If the user’s only change is to empty the field, we should reset it to http:// and gray again on blur.

Case two: label attributes

There’s another use case, which you’ve seen before: a text field’s initial value is used as a replacement for its label. When the user clicks in the field, the “label” disappears. My example of this is a phone attribute, where we’d like to suggest a standard US area code format. Something like (123) 555-1234.

We don’t want the user to have to delete our dummy numbers and put in their own; it’s too much work. Instead we think that the reminder will help coax the right format out of the user by itself—so this field gets blanked on focus, unlike the website attribute.

You also see this pattern used for content suggestions instead of formatting hints: for example, search fields and login forms which are space-constrained, like the built-in search in Firefox and Safari.

Get on with the plugin, already

OK, OK. So we have two kinds of templated attributes: those with starting values, which are potentially the start of valid data, and labels, which are just helpful, ephemeral reminders.

Check out the goods:

1
2
3
4
class User < ActiveRecord::Base
  templated_attribute :website, :starting_value => 'http://'
  templated_attribute :phone, :label => '(123) 555-1234'
end

Validations work as expected, since unchanged template values get removed in a :before_validation callback. So you can sprinkle on a little :validates_presence_of and :validates_format_of for a really good time.

There’s also some nice, unobtrusive Javascript you can generate to get the behavior I mentioned above. If you’re using form_for, it’s totally automatic. It gets installed when you install the plugin, or you can install and remove manually with these rake tasks:

1
2
rake templated_attribute:install
rake templated_attribute:remove

To turn off the Javascript for a given templated_attribute—say, because the generated stuff doesn’t jibe with your fancy-pantsy, AJAX-validating, Grey Poupon of a form—just throw :templated_javascript => false in the options hash for text_field or text_area. You’ll have to do any styling and event handling by yourself.

I’d like to make this work for fields other than text_field and text_area; the other contenders were file_field, which we can’t do because the Javascript security model doesn’t let us touch its value at runtime, and password_field, which I haven’t done because showing the template value would require dynamically switching the element to a text_field and back (to avoid all those asterisks). That one’s on the list, though.

Plugin resources