require 'cgi/util' require 'action_view' module Incline::Extensions ## # Adds additional helper methods to form builders. module FormBuilder ## # Creates a date picker selection field using a bootstrap input group. # # *Valid options:* # # input_group_size:: # Valid optional sizes are 'small' or 'large'. # readonly:: # Set to true to make the input field read only. # pre_calendar:: # Set to true to put a calendar icon before the input field. # post_calendar:: # Set to true to put a calendar icon after the input field. This is the default setting if no other pre/post # is selected. # pre_label:: # Set to a text value to put a label before the input field. Replaces +pre_calendar+ if specified. # post_label:: # Set to a text value to put a label after the input field. Replaces +post_calendar+ if specified. # # f.date_picker :end_date, :pre_label => 'End' # def date_picker(method, options = {}) options = { class: 'form-control', read_only: false, pre_calendar: false, pre_label: nil, post_calendar: false, post_label: false, attrib_val: { }, style: { }, input_group_size: '' }.merge(options) style = '' options[:style].each { |k,v| style += "#{k}: #{v};" } attrib = options[:attrib_val] attrib[:class] = options[:class] attrib[:style] = style attrib[:readonly] = 'readonly' if options[:read_only] if %w(sm small input-group-sm).include?(options[:input_group_size]) options[:input_group_size] = 'input-group-sm' elsif %w(lg large input-group-lg).include?(options[:input_group_size]) options[:input_group_size] = 'input-group-lg' else options[:input_group_size] = '' end attrib[:value] = object.send(method).strftime('%m/%d/%Y') if object.send(method) fld = text_field(method, attrib) # must have at least one attachment, default to post-calendar. options[:post_calendar] = true unless options[:pre_calendar] || options[:pre_label] || options[:post_label] # labels override calendars. options[:pre_calendar] = false if options[:pre_label] options[:post_calendar] = false if options[:post_label] # construct the prefix if options[:pre_calendar] pre = '' elsif options[:pre_label] pre = "#{CGI::escape_html options[:pre_label]}" else pre = '' end # construct the postfix if options[:post_calendar] post = '' elsif options[:post_label] post = "#{CGI::escape_html options[:post_label]}" else post = '' end # and then the return value. "
#{pre}#{fld}#{post}
".html_safe end ## # Creates a multiple input field control for the provided form. # # The +methods+ parameter can be either an array of method names, or a hash with method names as the # keys and labels as the values. # # For instance: # [ :alpha, :bravo, :charlie ] # { :alpha => 'The first item', :bravo => 'The second item', :charlie => 'The third item' } # # *Valid options:* # # class:: # The CSS class to apply. Defaults to 'form-control'. # # read_only:: # Should the control be read-only? Defaults to false. # # style:: # A hash containing CSS styling attributes to apply to the input fields. # Width is generated automatically or specified individually using the "field_n" option. # # input_group_size:: # You can specific *small* or *large* to change the control's overall size. # # attrib:: # Any additional attributes you want to apply to the input fields. # # field_n:: # Sets specific attributes for field "n". These values will override the "attrib" and "style" options. # # f.multi_input [ :city, :state, :zip ], # :field_1 => { :maxlength => 30, :style => { :width => '65%' } }, # :field_2 => { :maxlength => 2 }, # :field_3 => { :maxlength => 10, :style => { :width => '25%' } } # def multi_input(methods, options = {}) raise ArgumentError.new('methods must be either a Hash or an Array') unless methods.is_a?(::Hash) || methods.is_a?(::Array) options = options.dup # add some defaults. options = { class: 'form-control', read_only: false, attrib: { }, style: { }, input_group_size: '' }.merge(options) # build the style attribute. options[:attrib][:style] ||= '' options[:style].each do |k,v| if k.to_s == 'width' options[:input_group_width] = "width: #{v};" else options[:attrib][:style] += "#{k}: #{v};" end end # Standardize the "methods" list to be an array of arrays. if methods.is_a?(::Hash) methods = methods.to_a elsif methods.is_a?(::Array) methods = methods.map{|v| v.is_a?(::Array) ? v : [ v, v.to_s.humanize ] } end # Extract field attributes. fields = { } methods.each_with_index do |(meth,label), index| index += 1 fields[meth] = options[:attrib].merge(options.delete(:"field_#{index}") || {}) fields[meth][:readonly] = 'readonly' if options[:read_only] fields[meth][:class] ||= options[:class] if fields[meth][:style].is_a?(::Hash) fields[meth][:style] = fields[meth][:style].to_a.map{|v| v.map(&:to_s).join(':') + ';'}.join(' ') end fields[meth][:placeholder] ||= label end if %w(sm small input-group-sm).include?(options[:input_group_size]) options[:input_group_size] = 'input-group-sm' elsif %w(lg large input-group-lg).include?(options[:input_group_size]) options[:input_group_size] = 'input-group-lg' else options[:input_group_size] = '' end # We want each field to have a width specified. remaining_width = 100.0 remaining_fields = fields.count width_match = /(?:^|;)\s*width:\s*([^;]+);/ # pass 1, compute remaining width. fields.each do |meth, attr| if attr[:style] =~ width_match width = $1 if width[-1] == '%' width = width[0...-1].strip.to_f if width > remaining_width Incline::Log::warn "Field width adds up to more than 100% in multi_input affecting field \"#{meth}\"." width = remaining_width attr[:style] = attr[:style].gsub(width_match_1, '').gsub(width_match_2, '') + "width: #{width}%;" end remaining_width -= width remaining_width = 0 if remaining_width < 0 remaining_fields -= 1 else # we do not support pixel, em, etc, so dump the unsupported width. Incline::Log::warn "Unsupported width style in multi_input affecting field \"#{meth}\": #{width}" attr[:style] = attr[:style].gsub(width_match_1, '').gsub(width_match_2, '') end end end # pass 2, fill in missing widths. fields.each do |meth, attr| unless attr[:style] =~ width_match width = if remaining_fields > 1 (remaining_width / remaining_fields).to_i else remaining_width end Incline::Log::warn "Computed field width of 0% in multi_input affecting field \"#{meth}\"." if width == 0 attr[:style] += "width: #{width}%;" remaining_width -= width remaining_fields -= 1 remaining_width = 0 if remaining_width < 0 end end fld = [] fields.each do |meth, attr| attr[:value] = object.send(meth) fld << text_field(meth, attr) end "
#{fld.join}
".html_safe end ## # Creates a currency entry field. # # *Valid options:* # # currency_symbol:: # A string used to prefix the input field. Defaults to '$'. # # All other options will be passed through to the {FormHelper#text_field}[http://apidock.com/rails/ActionView/Helpers/FormHelper/text_field] method. # # The value will be formatted with comma delimiters and two decimal places. # # f.currency :pay_rate # def currency_field(method, options = {}) # get the symbol for the field. sym = options.delete(:currency_symbol) || '$' # get the value if (val = object.send(method)) options[:value] = number_with_precision val, precision: 2, delimiter: ',' end # build the field fld = text_field(method, options) # return the value. "
#{CGI::escape_html sym}#{fld}
".html_safe end ## # Creates a label followed by an optional small text description. # For instance, (World) # # Valid options: # # text:: # The text for the label. If not set, the method name is humanized and that value will be used. # # small_text:: # The small text to follow the label. If not set, then no small text will be included. # This is useful for flagging fields as optional. # # For additional options, see {FormHelper#label}[http://apidock.com/rails/ActionView/Helpers/FormHelper/label]. def label_w_small(method, options = {}) text = options.delete(:text) || method.to_s.humanize small_text = options.delete(:small_text) label(method, text, options) + (small_text ? " (#{CGI::escape_html small_text})" : '').html_safe end ## # Creates a standard form group with a label and text field. # # The +options+ is a hash containing label, field, and group options. # Prefix label options with +label_+ and field options with +field_+. # All other options will apply to the group itself. # # Group options: # # class:: # The CSS class for the form group. Defaults to 'form-group'. # # style:: # Any styles to apply to the form group. # # For label options, see #label_w_small. # For field options, see {FormHelper#text_field}[http://apidock.com/rails/ActionView/Helpers/FormHelper/text_field]. # def text_form_group(method, options = {}) gopt, lopt, fopt = split_form_group_options(options) lbl = label_w_small(method, lopt) fld = gopt[:wrap].call(text_field(method, fopt)) form_group lbl, fld, gopt end ## # Creates a standard form group with a label and password field. # # The +options+ is a hash containing label, field, and group options. # Prefix label options with +label_+ and field options with +field_+. # All other options will apply to the group itself. # # Group options: # # class:: # The CSS class for the form group. Defaults to 'form-group'. # # style:: # Any styles to apply to the form group. # # For label options, see #label_w_small. # For field options, see {FormHelper#password_field}[http://apidock.com/rails/ActionView/Helpers/FormHelper/password_field]. # def password_form_group(method, options = {}) gopt, lopt, fopt = split_form_group_options(options) lbl = label_w_small(method, lopt) fld = gopt[:wrap].call(password_field(method, fopt)) form_group lbl, fld, gopt end ## # Creates a form group including a label and a text area. # # The +options+ is a hash containing label, field, and group options. # Prefix label options with +label_+ and field options with +field_+. # All other options will apply to the group itself. # # Group options: # # class:: # The CSS class for the form group. Defaults to 'form-group'. # # style:: # Any styles to apply to the form group. # # For label options, see #label_w_small. # For field options, see {FormHelper#text_area}[http://apidock.com/rails/ActionView/Helpers/FormHelper/text_area]. def textarea_form_group(method, options = {}) gopt, lopt, fopt = split_form_group_options(options) lbl = label_w_small method, lopt fld = gopt[:wrap].call(text_area(method, fopt)) form_group lbl, fld, gopt end ## # Creates a standard form group with a label and currency field. # # The +options+ is a hash containing label, field, and group options. # Prefix label options with +label_+ and field options with +field_+. # All other options will apply to the group itself. # # Group options: # # class:: # The CSS class for the form group. Defaults to 'form-group'. # # style:: # Any styles to apply to the form group. # # For label options, see #label_w_small. # For field options, see #currency_field. # def currency_form_group(method, options = {}) gopt, lopt, fopt = split_form_group_options(options) lbl = label_w_small(method, lopt) fld = gopt[:wrap].call(currency_field(method, fopt)) form_group lbl, fld, gopt end ## # Creates a standard form group with a label and a static text field. # # The +options+ is a hash containing label, field, and group options. # Prefix label options with +label_+ and field options with +field_+. # All other options will apply to the group itself. # # Group options: # # class:: # The CSS class for the form group. Defaults to 'form-group'. # # style:: # Any styles to apply to the form group. # # For label options, see #label_w_small. # # Field options: # # value:: # Allows you to specify a value for the static field, otherwise the value from +method+ will be used. # def static_form_group(method, options = {}) gopt, lopt, fopt = split_form_group_options(options) lbl = label_w_small(method, lopt) fld = gopt[:wrap].call("") form_group lbl, fld, gopt end ## # Creates a standard form group with a datepicker field. # # The +options+ is a hash containing label, field, and group options. # Prefix label options with +label_+ and field options with +field_+. # All other options will apply to the group itself. # # Group options: # # class:: # The CSS class for the form group. Defaults to 'form-group'. # # style:: # Any styles to apply to the form group. # # For label options, see #label_w_small. # For field options, see #date_picker. # def datepicker_form_group(method, options = {}) gopt, lopt, fopt = split_form_group_options(options) lbl = label_w_small(method, lopt) fld = gopt[:wrap].call(date_picker(method, fopt)) form_group lbl, fld, gopt end ## # Creates a standard form group with a multiple input control. # # The +options+ is a hash containing label, field, and group options. # Prefix label options with +label_+ and field options with +field_+. # All other options will apply to the group itself. # # Group options: # # class:: # The CSS class for the form group. Defaults to 'form-group'. # # style:: # Any styles to apply to the form group. # # For label options, see #label_w_small. # For field options, see #multi_input_field. # def multi_input_form_group(methods, options = {}) gopt, lopt, fopt = split_form_group_options(options) lopt[:text] ||= gopt[:label] if lopt[:text].blank? lopt[:text] = methods.map {|k,_| k.to_s.humanize }.join(', ') end lbl = label_w_small(methods.map{|k,_| k}.first, lopt) fld = gopt[:wrap].call(multi_input(methods, fopt)) form_group lbl, fld, gopt end ## # Creates a standard form group with a checkbox field. # # The +options+ is a hash containing label, field, and group options. # Prefix label options with +label_+ and field options with +field_+. # All other options will apply to the group itself. # # Group options: # # class:: # The CSS class for the form group. # # h_align:: # Create a checkbox aligned to a certain column (1-12) if set. # If not set, then a regular form group is generated. # # For label options, see #label_w_small. # For field options, see {FormHelper#check_box}[http://apidock.com/rails/ActionView/Helpers/FormHelper/check_box]. # def check_box_form_group(method, options = {}) gopt, lopt, fopt = split_form_group_options({ class: 'checkbox', field_class: ''}.merge(options)) if gopt[:h_align] gopt[:class] = gopt[:class].blank? ? "col-sm-#{12-gopt[:h_align]} col-sm-offset-#{gopt[:h_align]}" : "#{gopt[:class]} col-sm-#{12-gopt[:h_align]} col-sm-offset-#{gopt[:h_align]}" end lbl = label method do check_box(method, fopt) + CGI::escape_html(lopt[:text] || method.to_s.humanize) + (lopt[:small_text] ? " (#{CGI::escape_html lopt[:small_text]})" : '').html_safe end "
#{lbl}
".html_safe end ## # Creates a standard form group with a collection select field. # # The +collection+ should be an enumerable object (responds to 'each'). # # The +value_method+ would be the method to call on the objects in the collection to get the value. # This default to 'to_s' and is appropriate for any array of strings. # # The +text_method+ would be the method to call on the objects in the collection to get the display text. # This defaults to 'to_s' as well, and should be appropriate for most objects. # # The +options+ is a hash containing label, field, and group options. # Prefix label options with +label_+ and field options with +field_+. # All other options will apply to the group itself. # # Group options: # # class:: # The CSS class for the form group. Defaults to 'form-group'. # # style:: # Any styles to apply to the form group. # # For label options, see #label_w_small. # For field options, see {FormOptionsHelper#collection_select}[http://apidock.com/rails/ActionView/Helpers/FormOptionsHelper/collection_select]. # def select_form_group(method, collection, value_method = :to_s, text_method = :to_s, options = {}) gopt, lopt, fopt = split_form_group_options({ field_include_blank: true }.merge(options)) lbl = label_w_small(method, lopt) opt = {} [:include_blank, :prompt, :include_hidden].each do |attr| if fopt[attr] != nil opt[attr] = fopt[attr] fopt.except! attr end end fld = gopt[:wrap].call(collection_select(method, collection, value_method, text_method, opt, fopt)) form_group lbl, fld, gopt end ## # Adds a recaptcha challenge to the form configured to set the specified attribute to the recaptcha response. # # Valid options: # theme:: # Can be :dark or :light, defaults to :light. # type:: # Can be :image or :audio, defaults to :image. # size:: # Can be :compact or :normal, defaults to :normal. # tab_index:: # Can be any valid integer if you want a specific tab order, defaults to 0. # def recaptcha(method, options = {}) Incline::Recaptcha::Tag.new(@object_name, method, @template, options).render end private def form_group(lbl, fld, opt) ret = ' 6 f = 12 - l group[:h_align] = l label[:class] = label[:class].blank? ? "col-sm-#{l} control-label" : "#{label[:class]} col-sm-#{l} control-label" group[:wrap] = Proc.new do |fld| "
#{fld}
" end end [group, label, field] end end end ActionView::Helpers::FormBuilder.include Incline::Extensions::FormBuilder