Skip to content

Creating custom field plugins

Steve Kenworthy edited this page Jan 3, 2014 · 1 revision

Creating custom field plugins

Custom fields provide you with a way to extend the functionality of Accounts, Campaigns, Contacts, Leads, and Opportunities. Fat Free CRM comes with a default set of fields for each of these entities and then allows you to effectively add your own set of fields as you see fit.

Some examples:

  • Adding a custom “date” field called ‘Birthday’ to the Contact model.
  • Adding a checkbox list to the Campaign model to ensure that it meets a certain set of criteria.

However, you might want to add a field type that is not available in a default Fat Free CRM instance. E.g. a “file upload” or a “select list” that looks up field key/value pairs from a specified model. This guide is about showing you how to do that. We’ll walk through creating a custom lookup field. Code for this tutorial is located at https://github.com/fatfreecrm/ffcrm_lookup_field

Getting started (prerequisites)

Firstly, this guide assumes you have already setup a plugin and added it to your FatFreeCRM instance. If not, follow the steps in Creating a plugin before proceeding. If you want to save yourself the trouble, the final code from this tutorial is at https://github.com/fatfreecrm/ffcrm_lookup_field

Outline

Here’s what we’ll do:

  • Create a CustomFieldLookup model
  • Register CustomFieldLookup with FatFreeCRM
  • Create a partial that gets used in the /admin/fields to display options for creating/editting our lookup field
  • Create a LookupInput class to tell simple_form how to display this field on model edit forms
  • Add some custom javascript to hook into the Chosen library for select fields

Creating the CustomFieldLookup model

In your plugin, create a file called app/models/fields/custom_field_lookup.rb and insert the following code:

class CustomFieldLookup < CustomField

  Settings = %w(lookup_class_name lookup_field lookup_method autocomplete multiselect)

  # Renders the value
  #------------------------------------------------------------------------------
  def render(value)
    value && lookup_values(value).join(', ')
  end

  # Looks up a set of values given a list of ids
  #------------------------------------------------------------------------------
  def lookup_values(value)
    return [] if value.empty? # stops ransack from returning everything
    klass = lookup_class
    klass.search("#{lookup_field}_in" => value).result.my.map(&lookup_method.to_sym).sort
  end
  
  # Returns class to lookup
  # Note: lookup_class_name can be table or model name e.g. 'contact' or 'contacts'
  #------------------------------------------------------------------------------
  def lookup_class
    lookup_class_name.tableize.classify.constantize
  end
  
  # Convenience methods for settings
  #------------------------------------------------------------------------------
  Settings.each do |name|
    define_method name do
      settings["#{name}"]
    end
    define_method "#{name}=" do |value|
      settings["#{name}"] = value
    end
  end

  # Define boolean settings methods for convenience
  #------------------------------------------------------------------------------
  %w(autocomplete multiselect).each do |name|
    define_method "#{name}?" do
      settings["#{name}"] == '1'
    end
  end

end

Key note: this must subclass from CustomField

Settings

The Field class allows a custom field to access the “settings” column. This is a hash that is stored in the Field table whenever a custom field is created. Use this to store specific configuration about this instance of the custom field. The code above stores following settings:

  • lookup_class_name – the name of the ActiveRecord class to get records from e.g. “account”
  • lookup_field – the name of the column that will act as the “key” for the model e.g. “id”
  • lookup_method – the method or column that return the value of each selected item. e.g. “name”
  • autocomplete – do we want the Chosen select box to behave like an autocomplete and query the database when we do a search – useful if model.all returns a very long list and you’d rather show just the first 25 entries.
  • multiselect – do we want the form element to be single-select or multi-select.

Further down in the class, we define methods for each of these settings to ensure the data is stored and retrieved from the settings hash.

The render method

It is important to define a ‘render’ method to display your values. This is called when the record (that your custom field is defined on) is viewed. The lookup_values method does most of the heavy lifting, ensuring that the correct model is looked up, that security is respected (using the “my” method) and that “lookup_method” is called on each of the records that match.

Registering the custom field with FatFreeCRM

You must tell FatFreeCRM about your new custom field. The simplest way to do this is to call the ‘register’ function on the Field class. Do this when your plugin is initialised.

In lib/ffcrm_lookup_field/engine.rb:

module FfcrmLookupField
  class Engine < ::Rails::Engine
    paths["app/models"] << "app/models/fields/"
    config.to_prepare do
      Field.register(:as => 'lookup', :klass => 'CustomFieldLookup', :type => 'string')
    end
  end
end

Note that Field.register takes three parameters:

  • :as – this is the id representing your custom field type. It should not have the same id as another custom field. The name here is very important and will be referenced in a number of sections below.
  • klass – this is the class name (string) referencing your custom field.
  • type – this is the database column type that will be created when an instance of your custom field is added to an entity.

Creating a partial for admin/fields

If your custom field doesn’t need any special settings, then you can start your server and head over to ‘/admin/fields’ and start creating new custom fields. Your new custom field should show up in the “Field type” list when you click ‘Create field’ and the standard set of custom field options will be shown.

However, in our example we have some settings that need to be configured each time a new custom lookup field is created.

Create a file in your plugin called “app/views/admin/custom_fields/_lookup_field.html.haml” with the following content:


= f.fields_for :settings, @field do |s|

  %table
    %tr
      %td
        .label.top.req
          = image_tag "info_tiny.png", :title => t('field_types.lookup.lookup_class_name.hint'), :class => "tooltip-icon"
          = s.label :lookup_class_name, t('field_types.lookup.lookup_class_name.title')
        = s.text_field :lookup_class_name
      %td= spacer
      %td
        .label.top.req
          = image_tag "info_tiny.png", :title => t('field_types.lookup.lookup_field.hint'), :class => "tooltip-icon"
          = s.label :lookup_field, t('field_types.lookup.lookup_field.title')
        = s.text_field :lookup_field

    %tr
      %td
        .label.top.req
          = image_tag "info_tiny.png", :title => t('field_types.lookup.lookup_method.hint'), :class => "tooltip-icon"
          = s.label :lookup_method, t('field_types.lookup.lookup_method.title')
        = s.text_field :lookup_method
      %td= spacer
      %td
        = s.check_box :autocomplete
        = s.label :autocomplete, t('field_types.lookup.autocomplete.title')
        %br
        = s.check_box :multiselect
        = s.label :multiselect, t('field_types.lookup.multiselect.title')
        
    %tr
      %td
        = f.label :hint, :class => "label top"
        = f.text_field :hint
      %td= spacer
      %td
        = f.label :placeholder, :class => "label top"
        = f.text_field :placeholder
        
    %tr
      %td
        .label.top
        = f.check_box :required
        = f.label :required
        %br
        = f.check_box :disabled
        = f.label :disabled

Whenever a custom field is created, Rails will look for a partial “field.html.haml” (or erb if you prefer). This type name corresponds to the ‘as’ parameter in the call to Field.register in the previous section. If the partial isn’t found, the standard custom field partial will be shown, otherwise, as above, the custom field template is loaded and inserted into the page via an ajax call.

You’ll note in the partial above that we are defining fields for our custom settings:

Abbreviated below:

= f.fields_for :settings, @field do |s|
  = s.text_field :lookup_class_name
  = s.text_field :lookup_field
  = s.check_box :autocomplete
  = s.text_field :lookup_method
  = s.check_box :autocomplete

= f.text_field :hint
= f.text_field :placeholder

Creating the CustomFieldLookup input class for SimpleForm

FatFreeCRM uses the simple_form gem to generate model forms. Simple_form provides a neat way of adding a new type of form field.

Create a file called “app/inputs/lookup_input.rb” with the following code:

Key note: The naming of the class is particularly important here. SimpleForm will look for a class called “Input” so it is crucial that we call it ‘LookupInput’ as “lookup” is the name registered when Field.register was called previously.

class LookupInput < SimpleForm::Inputs::CollectionSelectInput

  # Use chosen to render a lookup widget
  #------------------------------------------------------------------------------
  def input
    add_placeholder!
    add_autocomplete!
    add_multiselect!
    @builder.select(attribute_name, lookup_values, input_options, input_html_options)
  end

  private
  
  def add_placeholder!
    input_html_options['data-placeholder'] = input_html_options[:placeholder] unless input_html_options[:placeholder].blank?
  end
  
  def add_autocomplete!
    if cf.autocomplete?
      controller = cf.lookup_class_name.tableize.pluralize
      input_html_options['data-autocomplete-url'] = Rails.application.routes.url_for(:action => 'auto_complete', :controller => controller, :format => :json, :only_path => true)
      input_html_options[:class] << 'autocomplete'
    else
      input_html_options[:class] << 'chzn-select'
    end
  end
  
  def add_multiselect!
    input_html_options['multiple'] = 'multiple' if cf.multiselect?
  end
  
  # Get values to show.
  #   - order by 'method' if it is a column, otherwise use field
  #   - if using autocomplete then limit to 100 initial entries.
  #------------------------------------------------------------------------------
  def lookup_values
    klass = cf.lookup_class
    method = cf.lookup_method
    field = cf.lookup_field
    values = klass.my
    values = values.limit(100) if cf.autocomplete?
    values = object.attributes.keys.include?(method.to_s) ? values.order(method.to_s) : values.order(field.to_s)
    values.map{|item| [item.send(method), item.send(field)]}.
      sort_by{|x,y| x[0] <=> y[0]}
  end

  def cf
    @cf ||= CustomFieldLookup.find_by_name(attribute_name)
  end

  def lookup_values_for_text_field
    lookup_values.map(&:first).join(', ')
  end

end

The bulk of the work here is done in the ‘input’ method. We use SimpleForm’s builder to create the select box, ensuring various configuration options are added and, most importantly, overriding the option values that show in the select box. This is done in the ‘lookup_values’ function. It needs to provide a

[value,key], [value,key]
array that is commonly used in Rails to turn lists into select options.

Adding javascript

The final step is to add some javascript to turn on the Chosen select boxes that FatFreeCRM uses. (This converts ordinary < select > fields into funky searchable containers. See the Chosen website for examples.

Add the following code to ‘app/assets/javascripts/ffcrm_lookup_field/ffcrm_lookup_field.js.coffee’:

(($j) ->

  # Initializes ajaxChosen selectors for lookup fields
  #   by inserting ourselves into the function call stream
  old_init_chosen_fields = crm.init_chosen_fields
  crm.init_chosen_fields = ->
    $$('.lookup.autocomplete').each (field) ->
      new ajaxChosen field, {
        allow_single_deselect: true
        show_on_activate: true
        url: field.readAttribute('data-autocomplete-url')
        parameters: { limit: 25 }
        query_key: "auto_complete_query"
      }
    
    old_init_chosen_fields()

) (jQuery)

And ensure ‘app/assets/javascripts/ffcrm_lookup_field.js.coffee’ manifest contains a reference to the file you just created:

//= require ffcrm_lookup_field/ffcrm_lookup_field

Taking things further

That’s all there is to creating your own custom fields. I’m hoping this tutorial encourages others to create and opensource their own so that we can build up a repository of extra custom fields.