Skip to content

Instantly share code, notes, and snippets.

@bazzel
Last active December 6, 2019 07:38
Show Gist options
  • Save bazzel/a03bfc72dbd8966b0bedb74e164ddce0 to your computer and use it in GitHub Desktop.
Save bazzel/a03bfc72dbd8966b0bedb74e164ddce0 to your computer and use it in GitHub Desktop.
Simply create Material-like forms in Rails with Simple Form

This document is under construction.

First setup Rails 6 and Daemonite's Material UI as descrided in this gist.

# config/initializers/simple_form_daemonite.rb

Dir[Rails.root.join('lib/components/**/*.rb')].each { |f| require f }

SimpleForm.setup do |config|
  # Custom wrapper to support text field boxes
  # See http://daemonite.github.io/material/docs/4.1/material/text-fields/#text-field-boxes
  config.wrappers :vertical_form_w_text_field_boxes, tag: 'div', class: 'form-group', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b|
    b.use :html5
    b.use :placeholder
    b.optional :maxlength
    b.optional :minlength
    b.optional :pattern
    b.optional :min_max
    b.optional :readonly
    
    b.wrapper tag: :div, class: 'textfield-box' do |c|
      c.optional :label
      c.use :input, class: 'form-control', error_class: 'is-invalid', valid_class: false
      c.use :full_error, wrap_with: { tag: 'div', class: 'invalid-feedback' }
      c.use :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' }
    end
  end
  
  config.wrappers :inline_form_w_text_field_boxes, tag: 'span', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b|
    b.use :html5
    b.use :placeholder
    b.optional :maxlength
    b.optional :minlength
    b.optional :pattern
    b.optional :min_max
    b.optional :readonly
    
    b.wrapper tag: :div, class: 'textfield-box' do |c|
      c.use :label, class: 'sr-only'
      c.use :input, class: 'form-control', error_class: 'is-invalid', valid_class: 'is-valid'
      c.use :full_error, wrap_with: { tag: 'div', class: 'invalid-feedback' }
      c.optional :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' }
    end
  end

  # Input Group - custom component
  # see example app and config at https://github.com/rafaelfranca/simple_form-bootstrap
  config.wrappers :input_group, tag: 'div', class: 'form-group', error_class: 'form-group-invalid', valid_class: 'form-group-valid' do |b|
    b.use :html5
    b.use :placeholder
    b.optional :maxlength
    b.optional :minlength
    b.optional :pattern
    b.optional :min_max
    b.optional :readonly
    b.optional :label
    
    b.wrapper :input_group_tag, tag: 'div', class: 'input-group textfield-box' do |ba|
      ba.optional :prepend
      ba.use :input, class: 'form-control', error_class: 'is-invalid', valid_class: false
      ba.optional :append
      ba.use :full_error, wrap_with: { tag: 'div', class: 'invalid-feedback' }
      ba.use :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' }
    end
  end
  
  # The default wrapper to be used by the FormBuilder.
  config.default_wrapper = :vertical_form_w_text_field_boxes
end
# lib/components/input_group_component.rb

# custom component requires input group wrapper
module InputGroup
  def prepend(_wrapper_options = nil)
    span_tag = content_tag(:span, options[:prepend], class: 'input-group-text')
    template.content_tag(:div, span_tag, class: 'input-group-prepend')
  end

  def append(_wrapper_options = nil)
    span_tag = content_tag(:span, options[:append], class: 'input-group-text')
    template.content_tag(:div, span_tag, class: 'input-group-append')
  end
end

# Register the component in Simple Form.
SimpleForm.include_component(InputGroup)
  • Redefine StringInput, TextInput and NumericInput:
# app/inputs/text_field_box.rb

module TextFieldBox
  def input(wrapper_options)
    ensure_placeholder
    decorate_w_required
    swap_hint_w_error

    super
  end

  private

  def decorate_w_required
    return unless @required

    append_required_mark
    prepend_required_text
  end

  def ensure_placeholder
    input_html_options[:placeholder] ||= raw_label_text
  end

  def append_required_mark
    input_html_options[:placeholder].concat(self.class.translate_required_mark)
  end

  def prepend_required_text
    @hint = [
      "#{self.class.translate_required_mark} #{self.class.translate_required_text.titleize}",
      hint
    ].compact.join(' - ')
  end

  # see https://material.io/components/text-fields/#anatomy
  def swap_hint_w_error
    input_options[:hint] = false if object.errors[attribute_name].any?
  end
end
# app/inputs/string_input.rb

require 'text_field_box'
class StringInput < SimpleForm::Inputs::StringInput
  include TextFieldBox
end
# app/inputs/text_input.rb

require 'text_field_box'
class TextInput < SimpleForm::Inputs::TextInput
  include TextFieldBox
end
# app/inputs/numeric_input.rb

require 'text_field_box'
class NumericInput < SimpleForm::Inputs::NumericInput
  include TextFieldBox
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment