formtastic/formtastic

View on GitHub
lib/formtastic/helpers/form_helper.rb

Summary

Maintainability
A
1 hr
Test Coverage
# frozen_string_literal: true
module Formtastic
  module Helpers

    # FormHelper provides a handful of wrappers around Rails' built-in form helpers methods to set
    # the `:builder` option to `Formtastic::FormBuilder` and apply some class names to the `<form>`
    # tag.
    #
    # The following methods are wrapped:
    #
    # * `semantic_form_for` to `form_for`
    # * `semantic_fields_for` to `fields_for`
    # * `semantic_remote_form_for` and `semantic_form_remote_for` to `remote_form_for`
    #
    # The following two examples are effectively equivalent:
    #
    #     <%= form_for(@post, :builder => Formtastic::FormBuilder, :class => 'formtastic post') do |f| %>
    #       #...
    #     <% end %>
    #
    #     <%= semantic_form_for(@post) do |f| %>
    #       #...
    #     <% end %>
    #
    # This simple wrapping means that all arguments, options and variations supported by Rails' own
    # helpers are also supported by Formtastic.
    #
    # Since `Formtastic::FormBuilder` subclasses Rails' own `FormBuilder`, you have access to all
    # of Rails' built-in form helper methods such as `text_field`, `check_box`, `radio_button`,
    # etc **in addition to** all of Formtastic's additional helpers like {InputsHelper#inputs inputs},
    # {InputsHelper#input input}, {ButtonsHelper#buttons buttons}, etc:
    #
    #     <%= semantic_form_for(@post) do |f| %>
    #
    #       <!-- Formtastic -->
    #       <%= f.input :title %>
    #
    #       <!-- Rails -->
    #       <li class='something-custom'>
    #         <%= f.label :title %>
    #         <%= f.text_field :title %>
    #         <p class='hints'>...</p>
    #       </li>
    #     <% end %>
    #
    # Formtastic is a superset of Rails' FormBuilder. It deliberately avoids overriding or modifying
    # the behavior of Rails' own form helpers so that you can use Formtastic helpers when suited,
    # and fall back to regular Rails helpers, ERB and HTML when needed. In other words, you're never
    # fully committed to The Formtastic Way.
    module FormHelper

      # Allows the `:builder` option on `form_for` etc to be changed to your own which subclasses
      # `Formtastic::FormBuilder`. Change this from `config/initializers/formtastic.rb`.
      @@builder = Formtastic::FormBuilder
      mattr_accessor :builder

      # Allows the default class we add to all `<form>` tags to be changed from `formtastic` to
      # `whatever`. Change this from `config/initializers/formtastic.rb`.
      @@default_form_class = 'formtastic'
      mattr_accessor :default_form_class

      # Allows to set a custom proc to handle the class infered from the model's name. By default it
      # will infer the name from the class name (eg. Post will be "post").
      @@default_form_model_class_proc = proc { |model_class_name| model_class_name }
      mattr_accessor :default_form_model_class_proc

      # Allows to set a custom field_error_proc wrapper. By default this wrapper
      # is disabled since `formtastic` already adds an error class to the LI tag
      # containing the input. Change this from `config/initializers/formtastic.rb`.
      @@formtastic_field_error_proc = proc { |html_tag, instance_tag| html_tag }
      mattr_accessor :formtastic_field_error_proc

      # Wrapper around Rails' own `form_for` helper to set the `:builder` option to
      # `Formtastic::FormBuilder` and to set some class names on the `<form>` tag such as
      # `formtastic` and the downcased and underscored model name (eg `post`).
      #
      # See Rails' `form_for` for full documentation of all supported arguments and options.
      #
      # Since `Formtastic::FormBuilder` subclasses Rails' own FormBuilder, you have access to all
      # of Rails' built-in form helper methods such as `text_field`, `check_box`, `radio_button`,
      # etc **in addition to** all of Formtastic's additional helpers like {InputsHelper#inputs inputs},
      # {InputsHelper#input input}, {ButtonsHelper#buttons buttons}, etc.
      #
      # Most of the examples below have been adapted from the examples found in the Rails `form_for`
      # documentation.
      #
      # @see http://api.rubyonrails.org/classes/ActionView/Helpers/FormHelper.html Rails' FormHelper documentation (`form_for`, etc)
      # @see http://api.rubyonrails.org/classes/ActionView/Helpers/FormBuilder.html Rails' FormBuilder documentaion (`text_field`, etc)
      # @see FormHelper The overview of the FormBuilder module
      #
      # @example Resource-oriented form generation
      #   <%= semantic_form_for @user do |f| %>
      #     <%= f.input :name %>
      #     <%= f.input :email %>
      #     <%= f.input :password %>
      #   <% end %>
      #
      # @example Generic form generation
      #   <%= semantic_form_for :user do |f| %>
      #     <%= f.input :name %>
      #     <%= f.input :email %>
      #     <%= f.input :password %>
      #   <% end %>
      #
      # @example Resource-oriented with custom URL
      #   <%= semantic_form_for(@post, :url => super_post_path(@post)) do |f| %>
      #     ...
      #   <% end %>
      #
      # @example Resource-oriented with namespaced routes
      #   <%= semantic_form_for([:admin, @post]) do |f| %>
      #     ...
      #   <% end %>
      #
      # @example Resource-oriented with nested routes
      #   <%= semantic_form_for([@user, @post]) do |f| %>
      #     ...
      #   <% end %>
      #
      # @example Rename the resource
      #   <%= semantic_form_for(@post, :as => :article) do |f| %>
      #     ...
      #   <% end %>
      #
      # @example Remote forms (unobtrusive JavaScript)
      #   <%= semantic_form_for(@post, :remote => true) do |f| %>
      #     ...
      #   <% end %>
      #
      # @example Namespaced forms all multiple Formtastic forms to exist on the one page without DOM id clashes and invalid HTML documents.
      #   <%= semantic_form_for(@post, :namespace => 'first') do |f| %>
      #     ...
      #   <% end %>
      #
      # @example Accessing a mixture of Formtastic helpers and Rails FormBuilder helpers.
      #   <%= semantic_form_for(@post) do |f| %>
      #     <%= f.input :title %>
      #     <%= f.input :body %>
      #     <li class="something-custom">
      #       <label><%= f.check_box :published %></label>
      #     </li>
      #   <% end %>
      #
      # @param record_or_name_or_array
      #   Same behavior as Rails' `form_for`
      #
      # @option *args [Hash] :html
      #   Pass HTML attributes into the `<form>` tag. Same behavior as Rails' `form_for`, except we add in some of our own classes.
      #
      # @option *args [String, Hash] :url
      #   A hash of URL components just like you pass into `link_to` or `url_for`, or a named route (eg `posts_path`). Same behavior as Rails' `form_for`.
      #
      # @option *args [String] :namespace
      def semantic_form_for(record_or_name_or_array, *args, &proc)
        options = args.extract_options!
        options[:builder] ||= @@builder
        options[:html] ||= {}
        options[:html][:novalidate] = !@@builder.perform_browser_validations unless options[:html].key?(:novalidate)
        options[:custom_namespace] = options.delete(:namespace)

        singularizer = defined?(ActiveModel::Naming.singular) ? ActiveModel::Naming.method(:singular) : ActionController::RecordIdentifier.method(:singular_class_name)

        class_names = options[:html][:class] ? options[:html][:class].split(" ") : []
        class_names << @@default_form_class
        model_class_name = case record_or_name_or_array
          when String, Symbol then record_or_name_or_array.to_s                                  # :post => "post"
          when Array then options[:as] || singularizer.call(record_or_name_or_array[-1].class)  # [@post, @comment] # => "comment"
          else options[:as] || singularizer.call(record_or_name_or_array.class)                  # @post => "post"
        end
        class_names << @@default_form_model_class_proc.call(model_class_name)

        options[:html][:class] = class_names.compact.join(" ")

        with_custom_field_error_proc do
          self.form_for(record_or_name_or_array, *(args << options), &proc)
        end
      end

      # Wrapper around Rails' own `fields_for` helper to set the `:builder` option to
      # `Formtastic::FormBuilder`.
      #
      # @see #semantic_form_for
      def semantic_fields_for(record_name, record_object = nil, options = {}, &block)
        options, record_object = record_object, nil if record_object.is_a?(Hash) && record_object.extractable_options?
        options[:builder] ||= @@builder
        options[:custom_namespace] = options.delete(:namespace)

        with_custom_field_error_proc do
          self.fields_for(record_name, record_object, options, &block)
        end
      end

      protected

      def with_custom_field_error_proc(&block)
        default_field_error_proc = ::ActionView::Base.field_error_proc
        ::ActionView::Base.field_error_proc = @@formtastic_field_error_proc
        yield
      ensure
        ::ActionView::Base.field_error_proc = default_field_error_proc
      end
    end
  end
end