class ComfyBootstrapForm::FormBuilder

Constants

DATE_SELECT_HELPERS
FIELD_HELPERS

Attributes

form_bootstrap[RW]

Bootstrap settings set on the form itself

Public Class Methods

new(object_name, object, template, options) click to toggle source
Calls superclass method
# File lib/comfy_bootstrap_form/form_builder.rb, line 23
def initialize(object_name, object, template, options)
  @form_bootstrap = ComfyBootstrapForm::BootstrapOptions.new(options.delete(:bootstrap))
  super(object_name, object, template, options)
end

Public Instance Methods

check_box(method, options = {}, checked_value = "1", unchecked_value = "0") click to toggle source

Wrapper around checkbox. Example usage:

checkbox :agree, bootstrap: {label: {text: "Do you agree?"}}
Calls superclass method
# File lib/comfy_bootstrap_form/form_builder.rb, line 176
def check_box(method, options = {}, checked_value = "1", unchecked_value = "0")
  bootstrap = form_bootstrap.scoped(options.delete(:bootstrap))
  return super if bootstrap.disabled

  help_text = draw_help(bootstrap)
  errors    = draw_errors(bootstrap, method)

  add_css_class!(options, "form-check-input")
  add_css_class!(options, "is-invalid") if errors.present?

  label_text = nil
  if (custom_text = bootstrap.label[:text]).present?
    label_text = custom_text
  end

  fieldset_css_class = "form-group"
  fieldset_css_class += " row" if bootstrap.horizontal?
  fieldset_css_class += " #{bootstrap.inline_margin_class}" if bootstrap.inline?

  content_tag(:fieldset, class: fieldset_css_class) do
    draw_control_column(bootstrap, offset: true) do
      if bootstrap.custom_control
        content_tag(:div, class: "custom-control custom-checkbox") do
          add_css_class!(options, "custom-control-input")
          remove_css_class!(options, "form-check-input")
          concat super(method, options, checked_value, unchecked_value)
          concat label(method, label_text, class: "custom-control-label")
          concat errors     if errors.present?
          concat help_text  if help_text.present?
        end
      else
        content_tag(:div, class: "form-check") do
          concat super(method, options, checked_value, unchecked_value)
          concat label(method, label_text, class: "form-check-label")
          concat errors     if errors.present?
          concat help_text  if help_text.present?
        end
      end
    end
  end
end
collection_check_boxes(method, collection, value_method, text_method, options = {}, html_options = {}) click to toggle source

Helper to generate multiple checkboxes. Same options as for radio buttons. Example usage:

collection_check_boxes :choices, Choice.all, :id, :label
Calls superclass method
# File lib/comfy_bootstrap_form/form_builder.rb, line 244
def collection_check_boxes(method, collection, value_method, text_method, options = {}, html_options = {})
  bootstrap = form_bootstrap.scoped(options.delete(:bootstrap))
  return super if bootstrap.disabled

  content = "".html_safe
  unless options[:include_hidden] == false
    content << hidden_field(method, multiple: true, value: "")
  end

  args = [bootstrap, :check_box, method, collection, value_method, text_method, options, html_options]
  content << draw_choices(*args) do |m, v, opts|
    opts[:multiple]       = true
    opts[:include_hidden] = false
    check_box(m, opts.merge(bootstrap: { disabled: true }), v)
  end
end
collection_radio_buttons(method, collection, value_method, text_method, options = {}, html_options = {}) click to toggle source

Helper to generate multiple radio buttons. Example usage:

collection_radio_buttons :choices, ["a", "b"], :to_s, :to_s %>
collection_radio_buttons :choices, [["a", "Label A"], ["b", "Label B"]], :first, :second
collection_radio_buttons :choices, Choice.all, :id, :label

Takes bootstrap options:

inline: true            - to render inputs inline
label: {text: "Custom"} - to specify a label
label: {hide: true}     - to not render label at all
Calls superclass method
# File lib/comfy_bootstrap_form/form_builder.rb, line 229
def collection_radio_buttons(method, collection, value_method, text_method, options = {}, html_options = {})
  bootstrap = form_bootstrap.scoped(options.delete(:bootstrap))
  return super if bootstrap.disabled

  args = [bootstrap, :radio_button, method, collection, value_method, text_method, options, html_options]
  draw_choices(*args) do |m, v, opts|
    radio_button(m, v, opts.merge(bootstrap: { disabled: true }))
  end
end
file_field(method, options = {}) click to toggle source

Wrapper for file_field helper. It can accept `custom_control` option.

file_field :photo, bootstrap: {custom_control: true}
Calls superclass method
# File lib/comfy_bootstrap_form/form_builder.rb, line 104
def file_field(method, options = {})
  bootstrap = form_bootstrap.scoped(options.delete(:bootstrap))
  return super if bootstrap.disabled

  draw_form_group(bootstrap, method, options) do
    if bootstrap.custom_control
      content_tag(:div, class: "custom-file") do
        add_css_class!(options, "custom-file-input")
        remove_css_class!(options, "form-control")
        label_text = options.delete(:placeholder)
        concat super(method, options)

        label_options = { class: "custom-file-label" }
        label_options[:for] = options[:id] if options[:id].present?
        concat label(method, label_text, label_options)
      end
    else
      super(method, options)
    end
  end
end
form_group(options = {}) { || ... } click to toggle source

Helper method to put arbitrary content in markup that renders correctly for the Bootstrap form. Example:

form_group bootstrap: {label: {text: "Label"}} do
  "Some content"
end
# File lib/comfy_bootstrap_form/form_builder.rb, line 323
def form_group(options = {})
  bootstrap = form_bootstrap.scoped(options.delete(:bootstrap))

  label_options = bootstrap.label.clone
  label_text = label_options.delete(:text)

  label =
    if label_text.present?
      if bootstrap.horizontal?
        add_css_class!(label_options, "col-form-label")
        add_css_class!(label_options, bootstrap.label_col_class)
        add_css_class!(label_options, bootstrap.label_align_class)
      elsif bootstrap.inline?
        add_css_class!(label_options, bootstrap.inline_margin_class)
      end

      content_tag(:label, label_text, label_options)
    end

  form_group_class = "form-group"
  form_group_class += " row"      if bootstrap.horizontal?
  form_group_class += " mr-sm-2"  if bootstrap.inline?

  content_tag(:div, class: form_group_class) do
    content = "".html_safe
    content << label if label.present?
    content << draw_control_column(bootstrap, offset: label.blank?) do
      yield
    end
  end
end
number_field(method, options = {}) click to toggle source

Wrapper for the number field. It has default changed from `step: β€œ1”` to `step: β€œany”` to prevent confusion when dealing with decimal numbers.

number_field :amount, step: 5
Calls superclass method
# File lib/comfy_bootstrap_form/form_builder.rb, line 75
def number_field(method, options = {})
  bootstrap = form_bootstrap.scoped(options.delete(:bootstrap))
  options.reverse_merge!(step: "any")
  return super(method, options) if bootstrap.disabled

  draw_form_group(bootstrap, method, options) do
    super(method, options)
  end
end
plaintext(method, options = {}) click to toggle source

Bootstrap wrapper for readonly text field that is shown as plain text.

plaintext(:value)
# File lib/comfy_bootstrap_form/form_builder.rb, line 265
def plaintext(method, options = {})
  bootstrap = form_bootstrap.scoped(options.delete(:bootstrap))
  draw_form_group(bootstrap, method, options) do
    remove_css_class!(options, "form-control")
    add_css_class!(options, "form-control-plaintext")
    options[:readonly] = true
    ActionView::Helpers::FormBuilder.instance_method(:text_field).bind(self).call(method, options)
  end
end
primary(value = nil, options = {}, &block) click to toggle source

Same as submit button, only with btn-primary class added

# File lib/comfy_bootstrap_form/form_builder.rb, line 311
def primary(value = nil, options = {}, &block)
  add_css_class!(options, "btn-primary")
  submit(value, options, &block)
end
radio_button(method, tag_value, options = {}) click to toggle source

Wrapper around radio button. Example usage:

radio_button :choice, "value", bootstrap: {label: {text: "Do you agree?"}}
Calls superclass method
# File lib/comfy_bootstrap_form/form_builder.rb, line 130
def radio_button(method, tag_value, options = {})
  bootstrap = form_bootstrap.scoped(options.delete(:bootstrap))
  return super if bootstrap.disabled

  help_text = draw_help(bootstrap)
  errors    = draw_errors(bootstrap, method)

  add_css_class!(options, "form-check-input")
  add_css_class!(options, "is-invalid") if errors.present?

  label_text = nil
  if (custom_text = bootstrap.label[:text]).present?
    label_text = custom_text
  end

  fieldset_css_class = "form-group"
  fieldset_css_class += " row" if bootstrap.horizontal?
  fieldset_css_class += " #{bootstrap.inline_margin_class}" if bootstrap.inline?

  content_tag(:fieldset, class: fieldset_css_class) do
    draw_control_column(bootstrap, offset: true) do
      if bootstrap.custom_control
        content_tag(:div, class: "custom-control custom-radio") do
          add_css_class!(options, "custom-control-input")
          remove_css_class!(options, "form-check-input")
          concat super(method, tag_value, options)
          concat label(method, label_text, value: tag_value, class: "custom-control-label")
          concat errors     if errors.present?
          concat help_text  if help_text.present?
        end
      else
        content_tag(:div, class: "form-check") do
          concat super(method, tag_value, options)
          concat label(method, label_text, value: tag_value, class: "form-check-label")
          concat errors     if errors.present?
          concat help_text  if help_text.present?
        end
      end
    end
  end
end
select(method, choices = nil, options = {}, html_options = {}, &block) click to toggle source

Wrapper for select helper. Boostrap options are sent via options hash:

select :choices, ["a", "b"], bootstrap: {label: {text: "Custom"}}
Calls superclass method
# File lib/comfy_bootstrap_form/form_builder.rb, line 89
def select(method, choices = nil, options = {}, html_options = {}, &block)
  bootstrap = form_bootstrap.scoped(options.delete(:bootstrap))
  return super if bootstrap.disabled

  add_css_class!(html_options, "custom-select") if bootstrap.custom_control

  draw_form_group(bootstrap, method, html_options) do
    super(method, choices, options, html_options, &block)
  end
end
submit(value = nil, options = {}, &block) click to toggle source

Add bootstrap formatted submit button. If you need to change its type or add another css class, you need to override all css classes like so:

submit(class: "btn btn-info custom-class")

You may add additional content that directly follows the button. Here's an example of a cancel link:

submit do
  link_to("Cancel", "/", class: "btn btn-link")
end
Calls superclass method
# File lib/comfy_bootstrap_form/form_builder.rb, line 287
def submit(value = nil, options = {}, &block)
  if value.is_a?(Hash)
    options = value
    value   = nil
  end

  bootstrap = form_bootstrap.scoped(options.delete(:bootstrap))
  return super if bootstrap.disabled

  add_css_class!(options, "btn")

  form_group_class = "form-group"
  form_group_class += " row" if bootstrap.horizontal?

  content_tag(:div, class: form_group_class) do
    draw_control_column(bootstrap, offset: true) do
      out = super(value, options)
      out << capture(&block) if block_given?
      out
    end
  end
end

Private Instance Methods

add_css_class!(options, string) click to toggle source
# File lib/comfy_bootstrap_form/form_builder.rb, line 603
def add_css_class!(options, string)
  css_class = [options[:class], string].compact.join(" ")
  options[:class] = css_class if css_class.present?
end
draw_choices(bootstrap, type, method, collection, value_method, text_method, _options, html_options) { |method, item_value, html_options| ... } click to toggle source

Rendering of choices for checkboxes and radio buttons

# File lib/comfy_bootstrap_form/form_builder.rb, line 512
def draw_choices(bootstrap, type, method, collection, value_method, text_method, _options, html_options)
  draw_form_group_fieldset(bootstrap, method) do
    if bootstrap.custom_control
      label_css_class = "custom-control-label"

      form_check_css_class = "custom-control"
      form_check_css_class +=
        case type
        when :radio_button then " custom-radio"
        when :check_box    then " custom-checkbox"
        end

      form_check_css_class += " custom-control-inline" if bootstrap.check_inline

      add_css_class!(html_options, "custom-control-input")

    else
      label_css_class = "form-check-label"

      form_check_css_class = "form-check"
      form_check_css_class += " form-check-inline" if bootstrap.check_inline

      add_css_class!(html_options, "form-check-input")
    end

    errors    = draw_errors(bootstrap, method)
    help_text = draw_help(bootstrap)

    add_css_class!(html_options, "is-invalid") if errors.present?

    content = "".html_safe
    collection.each_with_index do |item, index|
      item_value  = item.send(value_method)
      item_text   = item.send(text_method)

      content << content_tag(:div, class: form_check_css_class) do
        concat yield method, item_value, html_options
        concat label(method, item_text, value: item_value, class: label_css_class)
        if ((collection.count - 1) == index) && !bootstrap.check_inline
          concat errors     if errors.present?
          concat help_text  if help_text.present?
        end
      end
    end

    if bootstrap.check_inline
      content << errors     if errors.present?
      content << help_text  if help_text.present?
    end

    content
  end
end
draw_control(bootstrap, errors, _method, options) { || ... } click to toggle source

Renders control for a given field

# File lib/comfy_bootstrap_form/form_builder.rb, line 433
def draw_control(bootstrap, errors, _method, options)
  add_css_class!(options, "form-control")
  add_css_class!(options, "is-invalid") if errors.present?

  draw_control_column(bootstrap, offset: bootstrap.label[:hide]) do
    draw_input_group(bootstrap, errors) do
      yield
    end
  end
end
draw_control_column(bootstrap, offset:) { || ... } click to toggle source

Wrapping in control in column wrapper

# File lib/comfy_bootstrap_form/form_builder.rb, line 446
def draw_control_column(bootstrap, offset:)
  return yield unless bootstrap.horizontal?

  css_class = bootstrap.control_col_class.to_s
  css_class += " #{bootstrap.offset_col_class}" if offset
  content_tag(:div, class: css_class) do
    yield
  end
end
draw_errors(bootstrap, method) click to toggle source
# File lib/comfy_bootstrap_form/form_builder.rb, line 376
def draw_errors(bootstrap, method)
  errors = []

  if bootstrap.error.present?
    errors = [bootstrap.error]
  else
    return if object.nil?

    errors = object.errors[method]

    # If error is on association like `belongs_to :foo`, we need to render it
    # on an input field with `:foo_id` name.
    if errors.blank?
      errors = object.errors[method.to_s.sub(%r{_id$}, "")]
    end
  end

  return if errors.blank?

  content_tag(:div, class: "invalid-feedback") do
    errors.join(", ")
  end
end
draw_form_group(bootstrap, method, options) { || ... } click to toggle source

form group wrapper for input fields

# File lib/comfy_bootstrap_form/form_builder.rb, line 358
def draw_form_group(bootstrap, method, options)
  label  = draw_label(bootstrap, method, for_attr: options[:id])
  errors = draw_errors(bootstrap, method)

  control = draw_control(bootstrap, errors, method, options) do
    yield
  end

  form_group_class = "form-group"
  form_group_class += " row"      if bootstrap.horizontal?
  form_group_class += " mr-sm-2"  if bootstrap.inline?

  content_tag(:div, class: form_group_class) do
    concat label
    concat control
  end
end
draw_form_group_fieldset(bootstrap, method) { || ... } click to toggle source

Wrapper for collections of radio buttons and checkboxes

# File lib/comfy_bootstrap_form/form_builder.rb, line 567
def draw_form_group_fieldset(bootstrap, method)
  options = {}

  unless bootstrap.label[:hide]
    label_text = bootstrap.label[:text]
    label_text ||= ActionView::Helpers::Tags::Label::LabelBuilder
      .new(@template, @object_name.to_s, method, @object, nil).translation

    add_css_class!(options, "col-form-label pt-0")
    add_css_class!(options, bootstrap.label[:class])

    if bootstrap.horizontal?
      add_css_class!(options, bootstrap.label_col_class)
      add_css_class!(options, bootstrap.label_align_class)
    end

    label = content_tag(:legend, options) do
      label_text
    end
  end

  content_tag(:fieldset, class: "form-group") do
    content = "".html_safe
    content << label if label.present?
    content << draw_control_column(bootstrap, offset: bootstrap.label[:hide]) do
      yield
    end

    if bootstrap.horizontal?
      content_tag(:div, content, class: "row")
    else
      content
    end
  end
end
draw_help(bootstrap) click to toggle source

Drawing boostrap form field help text. Example usage:

text_field(:value, bootstrap: {help: "help text"})
# File lib/comfy_bootstrap_form/form_builder.rb, line 504
def draw_help(bootstrap)
  text = bootstrap.help
  return if text.blank?

  content_tag(:small, text, class: "form-text text-muted")
end
draw_input_group(bootstrap, errors, &block) click to toggle source

Wraps input field in input group container that allows prepending and appending text or html. Example:

text_field(:value, bootstrap: {prepend: "$.$$"}})
text_field(:value, bootstrap: {append: {html: "<button>Go</button>"}}})
# File lib/comfy_bootstrap_form/form_builder.rb, line 462
def draw_input_group(bootstrap, errors, &block)
  prepend_html  = draw_input_group_content(bootstrap, :prepend)
  append_html   = draw_input_group_content(bootstrap, :append)

  help_text = draw_help(bootstrap)

  # Not prepending or appending anything. Bail.
  if prepend_html.blank? && append_html.blank?
    content = capture(&block)
    content << errors     if errors.present?
    content << help_text  if help_text.present?
    return content
  end

  content = "".html_safe
  content << content_tag(:div, class: "input-group") do
    concat prepend_html if prepend_html.present?
    concat capture(&block)
    concat append_html  if append_html.present?
    concat errors       if errors.present?
  end
  content << help_text if help_text.present?
  content
end
draw_input_group_content(bootstrap, type) click to toggle source
# File lib/comfy_bootstrap_form/form_builder.rb, line 487
def draw_input_group_content(bootstrap, type)
  value = bootstrap.send(type)
  return unless value.present?

  content_tag(:div, class: "input-group-#{type}") do
    if value.is_a?(Hash) && value[:html].present?
      value[:html]
    else
      content_tag(:span, value, class: "input-group-text")
    end
  end
end
draw_label(bootstrap, method, for_attr: nil) click to toggle source

Renders label for a given field. Takes following bootstrap options:

:text - replace default label text :class - css class on the label :hide - if `true` will render for screen readers only

This is how those options can be passed in:

text_field(:value, bootstrap: {label: {text: "Custom", class: "custom"}})

You may also just set the label text by passing a string instead of label hash:

text_field(:value, bootstrap: {label: "Custom Label"})
# File lib/comfy_bootstrap_form/form_builder.rb, line 414
def draw_label(bootstrap, method, for_attr: nil)
  options = bootstrap.label.dup
  text    = options.delete(:text)

  options[:for] = for_attr if for_attr.present?

  add_css_class!(options, "sr-only") if options.delete(:hide)
  add_css_class!(options, bootstrap.inline_margin_class) if bootstrap.inline?

  if bootstrap.horizontal?
    add_css_class!(options, "col-form-label")
    add_css_class!(options, bootstrap.label_col_class)
    add_css_class!(options, bootstrap.label_align_class)
  end

  label(method, text, options)
end
remove_css_class!(options, string) click to toggle source
# File lib/comfy_bootstrap_form/form_builder.rb, line 608
def remove_css_class!(options, string)
  css_class = options[:class].to_s.split(" ")
  options[:class] = (css_class - [string]).compact.join(" ")
  options.delete(:class) if options[:class].blank?
end