Adding a custom jQuery-UI control to Formtastic in Ruby on Rails

Posted on February 10, 2011

5


A jQuery-UI slider rendered with FormtasticWhile working on OnCompare, over the last month, one of the controls we wanted to support was a slider, that let you choose between a range of values, as you can see in the example at right. jQuery-UI provides a slider control and Ruby on Rails already has jQuery built in (we’re using Rails 3). The challenge was that all of our controls are rendered from Formtastic.

Fortunately, Formtastic allows you to add new control types by extending Formtastic::SemanticFormBuilder. To do that I changed config/initializers/formtastic.rb to specify our own builder:

Formtastic::SemanticFormHelper.builder = Formtastic::OnCompareFormBuilder

Then I just needed to add a method that responds to :slider type.

class OnCompareFormBuilder < Formtastic::SemanticFormBuilder

  def slider_input(method, options = {})
    collection   = find_collection_for_column(method, options)
    html_options = strip_formtastic_options(options).merge(options.delete(:input_html) || {})

    input_id = generate_html_id(method,'')
    slider_id = "#{input_id}_slider"
    label_id = "#{input_id}_label"
    value_items = []
    label_items = []

    collection.each do |c|
      label_items << (c.is_a?(Array) ? c.first : c)
      value = c.is_a?(Array) ? c.last  : c
      value_items << value
    end

    label_options = options_for_label(options).merge(:input_name => input_id)
    label_options[:for] ||= html_options[:id]

    script_content = "$(function() {
        var option_values = [#{value_items.map {|v|"'#{v.to_s.gsub(/[']/, '\\\\\'')}'"}.join(',')}]
        var option_labels = [#{label_items.map {|l|"'#{l.to_s.gsub(/[']/, '\\\\\'')}'"}.join(',')}]
        $( '##{slider_id}' ).slider({
            value:100,
            min: 0,
            max: #{collection.count - 1},
            step: 1,
            slide: function( event, ui ) {
                $( '##{input_id}' ).val(
                        option_values[ui.value] );
                $( '##{label_id}' ).text(
                        option_labels[ui.value] );
            }
        });
        for(var i = 0; i < option_values.length; i++) {
          if(option_values[i] == $( '##{input_id}' ).val()) $( '##{slider_id}' ).slider( 'value', i )
        }
        $( '##{label_id}' ).text( option_labels[$( '##{slider_id}' ).slider( 'value' )] );
    });"

    label(method, label_options) <<
    template.content_tag(:script, Formtastic::Util.html_safe(script_content), :type => 'text/javascript') <<
    template.content_tag(:div,
        template.content_tag(:div, label_items[label_items.count - 1], :class => 'right-label') <<
        template.content_tag(:div, label_items[0], :class => 'left-label') <<
        template.content_tag(:div, nil, html_options.merge(:id => slider_id)), :class => 'slider-holder') <<
      hidden_input(method, options.delete(:as)
    ) <<
    template.content_tag(:span, nil, :id => label_id)
  end

end

Finally, I generate a ruby call in the ERB that adds a slider using something like this:

<%= f.input :value, :as => :slider, :label => question.text, :hint => question.description %>
About these ads
Posted in: Coding