views:

245

answers:

2

I'd like to be able to generate the following markup:

<label for="field">Something <span class="hint">Field hint</span></label>

from the following code:

form_for ... do |f|
    f.label :field, :hint => "Field hint"
end

So far I've created an initializer to store the custom functionality which re-opens ActionView::Helpers::FormBuilder and changes the label method, however I'm not sure what the best way to actually get the span into the text for the label. If I try to put the text in directly then rails, rightly so, escapes the content.

I'd quite like to use the existing label infrastructure as it has all the validation error support. This rules out using content_tag and generating it all myself (which would work, but doesn't seem... right).

A: 

Instead of changing the default builder, you should create a custom builder and pass it to the form with the :builder parameter.

class HintFormBuilder < ActionView::Helpers::FormBuilder
end

form_for @resource, :builder => HintFormBuilder do |f|
# ...
end

The Hint builder inherits all FormBuilder features, including validation, error messages and so on. Now, you should change what you need to change in order to customize the behavior. This is a really raw draft.

class HintFormBuilder < ActionView::Helpers::FormBuilder

  (%w(label)).each do |selector|
    src = <<-end_src
      def #{selector}(method, options = {})
        hint = options.delete(:hint)
        returning(super) do |element|
          # replace here the value of element with hint 
          # if hint != nil
          # remember to use gsub! and not gsub
        end
      end
    end_src
    class_eval src, __FILE__, __LINE__
  end

end

EDIT based on the first comment:

It's always a good idea to not hack the Rails internals because you might need to use, now or in the future, plugins or features that rely on the original behavior. If you don't want to manually append the builder in your forms, you can create an helper.

def search_form_for(record_or_name_or_array, *args, &proc) options = { :builder => HintFormBuilder }

form_for(record_or_name_or_array, 
         *(args << options),
         &proc)

end

If you want to reopen the original class instead, I would suggest to create a new method. This solution also applies to the custom helper and has the benefit you can customize it without the need to gsub! the response. Yes, gsub! is the common way to do so because when extending the original methods you only have access to the method/options and the result, no the value (that is injected by the @object variable).

class ActionView::Helpers::FormBuilder

  def label_with_hint(method, text = nil, options = {})
    hint = options.delete(:hint)
    # do your own customizations...
    @template.label(@object_name, method, text, objectify_options(options))
  end

end

EDIT: I was mistaken, you can pass a custom text as a parameter so you don't need to gsub! the returned string. I got confused by the text_field tag. At this point, you can use either the first (subclassing with/without custom method), second (hacking internals) or third option (hacking internals with custom method) and intercept the text value before it is sent to @template.label.

Also note that text can be nil. If nil, the value is automatically generated from method. You should be aware of this.

Simone Carletti
I really don't want to have specify the form builder I want to use on every form. Also, I'm sure there must be a better way than gsub-ing in the value...
jonnii
I don't know why someone voted this post down.
jonnii
+1  A: 

Here's what I would have done.

# config/initializers/[anything].rb
ActionView::Base.default_form_builder = CustomFormBuilder

# lib/custom_form_builder.rb
class CustomFormBuilder < ActionView::Helpers::FormBuilder
  def label(field, text, options = {})
    if options[:hint]
      hint = @template.content_tag(:span, options[:hint], :class => "hint")
      super(field, "#{field.to_s.humanize} #{hint}", options)
    else
      super
    end
  end
end
August Lilleaas
I didn't know you could set the default form builder, that's a really useful feature. Have you tried this approach? I think it suffers from the same problem I had which is that the super method html encodes the text.
jonnii