avatar

28

Add file-wide comments for rdoc's Files section, and add a comment to the FormHelper module explaining the form user experience by chrisk, 29 Jul, 2007 04:12 AM
Diff this changeset:
templated_attribute_helper.rb
      # This file contains the extensions to ActionView::Helpers::FormHelper:
# +text_area_with_templating+[link:classes/ActionView/Helpers/FormHelper.html]
# and +text_field_with_templating+[link:classes/ActionView/Helpers/FormHelper.html].

module ActionView         #:nodoc:
  module Helpers          #:nodoc:
    
    
    # Forms which have fields for templated attributes have the following behavior:
    # * When the user visits the form, the field is pre-filled with a
    #   template value (like <tt>http://</tt>) if the real value is empty or +nil+.
    # * If the template is left unchanged, it's convert it to +nil+ before validation.
    # * To imply that the initial value is a suggestion, the field's text color is
    #   gray until the user makes a change. If the user's only change is to empty
    #   the field, we should reset it to the template value and gray again on blur.
    # * In addition to this, <tt>label</tt>-style templates blank their fields
    #   (removing the template value) when they receive focus.
    module FormHelper
      
      
      # When this plugin is installed, calls to
      # <tt>ActionView::Helpers::FormHelper#text_area</tt> in your forms will
      # transparently call this method instead.
      #
      # When you call <tt>#text_area</tt> for an attribute which is declared as
      # templated using 
      # +templated_attribute+[link:classes/TemplatedAttribute/ActiveRecordExtensions/ClassMethods.html]
      # in the model, we enhance the normal <tt>#text_area</tt>
      # by inserting templated values insetad of empty field values, and
      # appending unobtrusive Javascript to enable smart templating behavior
      # and styling on <tt><textarea></tt> tags.
      #
      # When you call <tt>#text_area</tt> for attributes which are not
      # templated, we defer to Rails's original <tt>#text_area</tt> without
      # enhhancement.
      #
      # Javascript support will be disabled if <tt>:templated_javascript =>
      # false</tt> is passed in the options hash of +text_area+.
      #
      # <b>Note:</b> The Javascript uses Prototype to add event handlers to
      # your form. Make sure <tt>prototype.js</tt> is included in your layout.
      #
      # Usage:
      #   <% form_for @user do |form| %>
      #     <label for="user_website">About you:</label>
      #     <%= form.text_area :bio %>
      #   <% end %>
      def text_area_with_templating(class_name, method, options={})
        args = [class_name, method, options]  # use params above for docs' sake
        if templated?(*args)
          field_with_templating :text_area, *args
        else
          text_area_without_templating *args
        end
      end
      alias_method_chain :text_area, :templating
      
      
      # Same as <tt>#text_area_with_templating</tt>, but overrides
      # <tt>ActionView::Helpers::FormHelper#text_field</tt> so you can get
      # templated attribute behavior with <tt><input type="text"></tt> tags.
      # See notes for <tt>#text_area_with_templating</tt> above.
      #
      # Usage:
      #   <% form_for @user do |form| %>
      #     <label for="user_website">Website:</label>
      #     <%= form.text_field :website %>
      #
      #     <label for="user_phone">Phone:</label>
      #     <%= form.text_field :phone, :templated_javascript => false %>
      #   <% end %>
      def text_field_with_templating(class_name, method, options={})
        args = [class_name, method, options]  # use params above for docs' sake
        if templated?(*args)
          field_with_templating :text_field, *args
        else
          text_field_without_templating *args
        end
      end
      alias_method_chain :text_field, :templating
      




      private
      
      
      # Helper method to determine whether or not +templated_attribute+ has
      # been called for a given class and method.
      def templated?(class_name, method, options={})  # :nodoc:
        klass = class_name.classify.constantize
        klass.respond_to?(:templated_attributes_options) && klass.templated_attributes_options[method.to_sym]
      end


      # Behind the scenes, figure out the starting value for the field, then
      # render the field normally with that value (if applicable) and append a
      # touch of Javascript to add behavior.
      def field_with_templating(field_helper, class_name, method, options = {})   # :nodoc:
        options = { :templated_javascript => true }.merge(options)
        template_options = class_name.classify.constantize.templated_attributes_options[method.to_sym]

        # If calling method on object returns nil, use template value instead
        field_value = options[:object].__send__(method.to_sym)
        if field_value.nil? || field_value.strip.empty?
          field_value = template_options[:value]
        end
        
        # Only use javascript if not explicitly disabled in options
        if options.delete(:templated_javascript)
          javascript_string = javascript_tag \
            "Event.observe(window, 'load', function() { new TemplatedAttribute($('#{class_name}_#{method}'),
             '#{template_options[:type]}', '#{template_options[:value]}'); });"
        else
          javascript_string = ''
        end
        
        # output using the original method with our modified form field value,
        # plus the javascript
        send("#{field_helper}_without_templating".to_sym, class_name, method, options.merge({:value => field_value})) + javascript_string
      end


    end
  end  
end