# encoding: UTF-8 module DryCrud module Form # Internal class to handle the rendering of a single form control, # consisting of a label, input field, addon, help text or # required mark. class Control attr_reader :builder, :attr, :args, :options, :span, :addon, :help delegate :content_tag, :object, to: :builder # Html displayed to mark an input as required. REQUIRED_MARK = '*'.freeze # Number of default input field span columns depending # on the #field_method. INPUT_SPANS = Hash.new(8) INPUT_SPANS[:number_field] = INPUT_SPANS[:integer_field] = INPUT_SPANS[:float_field] = INPUT_SPANS[:decimal_field] = 2 INPUT_SPANS[:date_field] = INPUT_SPANS[:time_field] = 3 # Create a new control instance. # Takes the form builder, the attribute to build the control for # as well as any additional arguments for the field method. # This includes an options hash as the last argument, that # may contain the following special options: # # * :addon - Addon content displayd just after the input field. # * :help - A help text displayd below the input field. # * :span - Number of columns the input field should span. # * :caption - Different caption for the label. # * :field_method - Different method to create the input field. # * :required - Sets the field as required # (The value for this option usually is 'required'). # # All the other options will go to the field_method. def initialize(builder, attr, *args) @builder = builder @attr = attr @options = args.extract_options! @args = args @addon = options.delete(:addon) @help = options.delete(:help) @span = options.delete(:span) @caption = options.delete(:caption) @field_method = options.delete(:field_method) @required = options[:required] end # Renders only the content of the control. # I.e. no label and span divs. def render_content content end # Renders the complete control with label and everything. # Render the content given or the default one. def render_labeled(content = nil) @content = content if content labeled end private # Create the HTML markup for any labeled content. def labeled errors = errors? ? ' has-error' : '' content_tag(:div, class: "form-group#{errors}") do builder.label(attr, caption, class: 'col-md-2 control-label') + content_tag(:div, content, class: "col-md-#{span}") end end # Return the currently set content or create it # based on the various options given. # # Optionally renders addon, required mark and/or a help block # additionally to the input field. def content @content ||= begin content = input if addon content = builder.with_addon(content, addon) elsif required content = builder.with_addon(content, REQUIRED_MARK) end content << builder.help_block(help) if help.present? content end end # Return the currently set input field or create it # depending on the attribute. def input @input ||= begin options[:required] = 'required' if required builder.send(field_method, attr, *(args << options)) end end # The field method used to create the input. # If none is set, detect it from the attribute type. def field_method @field_method ||= detect_field_method end # True if the attr is required, false otherwise. def required @required = @required.nil? ? builder.required?(attr) : @required end # Number of grid columns the input field should span. def span @span ||= INPUT_SPANS[field_method] end # The caption of the label. # If none is set, uses the I18n value of the attribute. def caption @caption ||= builder.captionize(attr, object.class) end # Returns true if any errors are found on the passed attribute or its # association. def errors? attr_plain, attr_id = builder.assoc_and_id_attr(attr) object.errors.key?(attr_plain.to_sym) || object.errors.key?(attr_id.to_sym) end # Defines the field method to use based on the attribute # type, association or name. # rubocop:disable PerceivedComplexity def detect_field_method if type == :text :text_area elsif association_kind?(:belongs_to) :belongs_to_field elsif association_kind?(:has_and_belongs_to_many, :has_many) :has_many_field elsif attr.to_s.include?('password') :password_field elsif attr.to_s.include?('email') :email_field elsif builder.respond_to?(:"#{type}_field") :"#{type}_field" else :text_field end end # rubocop:enable PerceivedComplexity # The column type of the attribute. def type @type ||= builder.column_type(object, attr) end # Returns true if attr is a non-polymorphic association. # If one or more macros are given, the association must be of this kind. def association_kind?(*macros) if type == :integer || type.nil? assoc = builder.association(object, attr, *macros) assoc.present? && assoc.options[:polymorphic].nil? else false end end end end end