Simple Form Inputs - Creating Reusable Input Components | Foraker Labs in Boulder, CO

It isn’t uncommon when building web applications to require specific input mechanisms. If we were building an ecommerce site, we may ask for credit card information and want to only display the last four digits to the user. If we were building a media platform we may want to create a rating system in stars. These types of information go above and beyond our webform primitives. We need to figure out an easy way to reuse these inputs throughout our apps and ideally across multiple projects while also writing the least possible code. Simple Form custom inputs are an ideal way to solve our problem. Take, for example, the following contact information form

<%= simple_form_for @contact_information do |f| %>
  <%= f.input :name %>
  <%= f.input :favorite_dinosaur %>
  <%= f.input :note, as: :text %>
  <%= f.input :phone_number %>
<% end %>

Our contact information form is looking good. It can accept a name, a few details about the contact, and a phone number. Let’s assume we spoke with our client, Mr. Dr. Dre, who has decided to add some additional requirements. Our client only wants to accept phone numbers that have an area code from the West Coast. Now we want to split our phone number into three parts.

  1. An area code at the beginning which is used to specify a region associated with the phone number.
  2. A prefix which is three digits used to refer to the specific switch a phone line is connected to.
  3. A line number, the last four digits.

These three parts of a phone number should now proably be separate, so we might want to store that information in a phone number object. This is a nice pattern to adopt because Simple Form excels at taking ruby objects and displaying them as fields. We can now write some basic form helper code to create nested fields and use our nested fields to store phone numbers.

<%= f.simple_fields_for :phone_number do |ff| %>
  <%= ff.input :area_code %>
  <%= ff.input :prefix %>
  <%= ff.input :line_number %>
<% end %>

This gets us a lot closer to where we want to be. We now have our individual attributes of a phone number being sent to the server nested under :phone_number in our params. Now let’s pretend more requirements have come in from our client. They now also want to persist phone numbers on a variety of other objects. This is an ideal time to use a Simple Form custom attribute to DRY out our code. This means fewer lines and gives us the benefit of isolating any changes we need to make to a single location. We can cut the previous phone number input field code down to something like this:

<%= f.input :phone_number, as: :phone_number %>

So, let’s get into it.

First we will need to create an /inputs directory in our /app. Simpleform will autoload inputs from this directory and allow them to be used throughout your application. After adding our directory, we’ll need to add the basic skeleton of our input.

#app/inputs/phone_number_input.rb
class PhoneNumberInput < SimpleForm::Inputs::Base
  def input
  end
end

The input we are creating is very basic and only really needs an <input> tag. Therefore, we make our custom input a subclass of Inputs::Base. Simpleform, however, ships with many different input types and it is worth looking through the gem to see if your custom input could benefit from any of the behavior already built into an input type. Next we’ll add an ActiveSupport::SafeBuffer which will be used to output the content of our form.

#app/inputs/phone_number_input.rb
class PhoneNumberInput < SimpleForm::Inputs::Base
  def input
    ActiveSupport::SafeBuffer.new.tap do |out|
      out << "The future content of our input"
    end
  end
end

Next we want to add our fields. We’ll use the provided attribute_name, which is the name of the attribute on our parent model, to namespace the our phone number params. In our custom input we have access to our builder object, which references the Simple Form builder from the form context where we called our custom input. We can then call simplefieldsfor on the builder to get our nested fields. Note we are accessing the specific number value object using our parent form object by calling object.send(attribute_name). We then add each input to our safebuffer so it will be output, and we’re done!

class PhoneNumberInput < SimpleForm::Inputs::Base
  def input
    ActiveSupport::SafeBuffer.new.tap do |out|
      @builder.simple_fields_for attribute_name, number do |ff|
        out << ff.input(:area_code)
        out << ff.input(:prefix)
        out << ff.input(:line)
      end
    end
  end

  def number
    object.send(attribute_name)
  end
end

We now can resuse this phone number input throughout our application, and if we need to create a detailed phone number in a different application this code can be easily reused.

Notable Hooks

Javascript Hooks

In addition to being a great way to create nested fields, custom inputs are also a good place to create javascript hooks. We can call input_html_classes to push additional classes into our input.

class DatepickerInput < SimpleForm::Inputs::StringInput
  def input_html_classes
    super.push('datepicker-input')
  end
end
<%= f.input :phone_number, as: :credit_card %>

This is great because it prevents you from polluting your templates with repeated css classes and gives you a single place to change all your datepickers. Also, it can help us separate CSS selectors we are using for styling and CSS selectors we are using as hooks for behavior.

Default Values

Another valuable place to hook into Simple Form is the input_html_options. We can provide default values to our custom inputs using this method.

def input_html_options
  super.deep_merge({
    data: {
      start_date: options[:start_date]
    }
  })
end

For further information checkout the Simple Form docs, or dive into the gem itself as much of the documentation is in comments in the code.

Tweet at Foraker

Share this post!