Rails 3 Select List Items with Custom Attributes
This post is to assist with adding custom attributes (like the new html 5 data attributes) to items within a select control using Rails 3.
Scenario
I have a select control with Appointment types for a hairdresser. The items in the list are:
- Men's Haircut
- Women's Haircut
- Kid's Haircut
Each of these items have a name (to be displayed) and an ID (to be saved to the database). I also however have a duration for each of these appointment types, and I want to do some client side (jquery) changes to my screen based on the appointment duration.
Options
Ordinarily with the items in the select control, I can only have one value stored, and I need this to be the ID. There are two ways to get around this:
- Store a combined key in the value like a pipe separated string "12|60" where the id is 12, and the duration is 60. You would then need to parse this back at your controller when dealing with this field.
- Add a custom attribute to the items in the select control (ie. data-duration). We have always been able to add custom attributes to HTML controls, however the HTML5 specification now makes this legit, as long as they are named with "data-" as the prefix.
Solution
I have opted for option 2, as I want to take advantage of rails automation of form controls when saving and loading the form.
Ordinarily I would have used a form.collection_select control for my select, but this does not allow for these extra attributes, so I will create my own custom helper that is based on "options_from_collection_for_select" and use it with a normal select tag.
First, I create the helper in my application_helper.rb file:
def options_from_collection_for_select_with_attributes(collection, value_method, text_method, attr_name, attr_field, selected = nil)
options = collection.map do |element|
[element.send(text_method), element.send(value_method), attr_name => element.send(attr_field)]
end
selected, disabled = extract_selected_and_disabled(selected)
select_deselect = {}
select_deselect[:selected] = extract_values_from_collection(collection, value_method, selected)
select_deselect[:disabled] = extract_values_from_collection(collection, value_method, disabled)
options_for_select(options, select_deselect)
end
In the above helper, I have added two extra parameters:
- attr_name This is the name of the attribute to add (eg. data-something)
- attr_field This is the field in your model to get the value from (eg. duration)
Now in my view, I can use the normal select tag, and use my new helper within:
<%= f.select(:appointment_type_id, options_from_collection_for_select_with_attributes(Appointment_type.all, 'id', 'name', 'data-duration', 'duration', @appointment.appointment_type_id)) %>
This will render my items like this:
<select id="appointment_appointment_type_id" name="appointment[appointment_type_id]">
<option value="1" selected="selected" data-duration="30">Men's Haircut</option>
<option value="2" data-duration="120">Women's Haircut</option>
<option value="3" data-duration="30">Kid's Haircut</option>
</select>