-
Notifications
You must be signed in to change notification settings - Fork 1.3k
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
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
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
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
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.
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.
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.
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
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]
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
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.