Let the platform do the work

Creating Custom Field Types

Overview

In this example, we create a custom field type called "Highlightfield", which will mimic the base text field type with the added feature that the displayed text for the field will be highlighted in a color chosen when the field is created in Studio.

This example requires the following steps, which are covered in the sections and subsections below:

  1. Creating the JavaScript Controller
  2. Defining the Handlebar Templates
  3. Adding the Field Type to Studio
  4. Enabling Search and Filtering

Note: Custom field types are not currently functional for use in Reports and will break Report Wizard.

Naming a Custom Field Type in Sugar

When choosing a name for a custom field type, keep in mind the following rules:

  • The first letter of a custom field type's name must be capitalized.
  • All subsequent letters in the field type's name must be lowercase.
  • A field type's name cannot contain non-letter characters such as 0-9, hyphens, or dashes.

Therefore, in this example, the field type "Highlightfield" cannot be called HighLightField or highlightfield.

Creating the JavaScript Controller

First, create a controller file. Since we are starting from scratch, we need to extend the base field template. To accomplish this, create ./custom/clients/base/fields/Highlightfield/Highlightfield.js. This file will contain the JavaScript needed to render the field and format the values. By default, all fields extend the base template and do not require you to add the extendsFrom property. An example template is shown below:

./custom/clients/base/fields/Highlightfield/Highlightfield.js

  ({
    /**
     * Called when initializing the field
     * @param options
     */
    initialize: function(options) {
        this._super('initialize', [options]);
    },

    /**
     * Called when rendering the field
     * @private
     */
    _render: function() {
        this._super('_render');
    },

    /**
     * Called when formatting the value for display
     * @param value
     */
    format: function(value) {
        return this._super('format', [value]);
    },

    /**
     * Called when unformatting the value for storage
     * @param value
     */
    unformat: function(value) {
        return this._super('unformat', [value]);
    }
})

Note: This controller file example contains methods for initialize, _render, format, and unformat. These are shown for your ease of use but are not necessarily needed for this customization. For example, you could choose to create an empty controller consisting of nothing other than ({}) and the field would render as expected in this example.

Defining the Handlebar Templates

Next, define the handlebar templates. The templates will nearly match the base template found in ./clients/base/fields/base/ with the minor difference of an additional attribute of style="background:{{def.backcolor}}; color:{{def.textcolor}}" for the detail and list templates.

Detail View

The first template to define is the Sidecar detail view. This template handles the display of data on the record view.

./custom/clients/base/fields/Highlightfield/detail.hbs

  {{!
    The data for field colors are passed into the handlebars template through the def array. For this example, the def.backcolor and def.textcolor properties. These indexes are defined in:
    ./custom/modules/DynamicFields/templates/Fields/TemplateHighlightfield.php
}}

{{#if value}}
    <div class="ellipsis_inline" data-placement="bottom" style="background:{{def.backcolor}}; color:{{def.textcolor}}">
        {{value}}
    </div>
{{/if}}

Edit View

Now define the Sidecar edit view. This template handles the display of the field in the edit view.

./custom/clients/base/fields/Highlightfield/edit.hbs.

  {{!
    We have not made any edits to this file that differ from stock, however,
    we could add styling here just as we did for the detail and list templates.
}}

<input type="text"
    name="{{name}}"
    value="{{value}}"
    {{#if def.len}}maxlength="{{def.len}}"{{/if}}
    {{#if def.placeholder}}placeholder="{{str def.placeholder this.model.module}}"{{/if}}
    class="inherit-width">
<p class="help-block">

List View

Finally, define the list view. This template handles the display of the custom field in list views.

./custom/clients/base/fields/Highlightfield/list.hbs 

  {{!
    The data for field colors are passed into the handlebars template through the def array. Our case
    being the def.backcolor and def.textcolor properties.  These indexes are defined in:
    ./custom/modules/DynamicFields/templates/Fields/TemplateHighlightfield.php
}}

<div class="ellipsis_inline" data-placement="bottom" data-original-title="{{#unless value}}
    {{#if def.placeholder}}{{str def.placeholder this.model.module}}{{/if}}{{/unless}}{{value}}"
     style="background:{{def.backcolor}}; color:{{def.textcolor}}">

    {{#if def.link}}
       <a href="{{#if def.events}}javascript:void(0);{{else}}{{href}}{{/if}}">{{value}}</a>{{else}}{{value}}
    {{/if}}
</div>

Adding the Field Type to Studio

To enable the new field type for use in Studio, define the Studio field template. This will also allow us to map any additional properties we need for the templates. For this example, map the ext1 and ext2 fields from the fields_meta_data table to the backcolor and textcolor definitions. Also, set the dbfield definition to varchar so that the correct database field type is created.

Note: We are setting "reportable" to false to avoid issues with Report Wizard.

./custom/modules/DynamicFields/templates/Fields/TemplateHighlightfield.php

  <?php

if (!defined('sugarEntry') || !sugarEntry) die('Not A Valid Entry Point');

require_once 'modules/DynamicFields/templates/Fields/TemplateField.php';

class TemplateHighlightfield extends TemplateField
{
    var $type = 'varchar';
    var $supports_unified_search = true;

    /**
     * TemplateAutoincrement Constructor: Map the ext attribute fields to the relevant color properties
     *
     * References:      get_field_def function below
     *
     * @returns field_type
     */
    function __construct()
    {
        $this->vardef_map['ext1'] = 'backcolor';
        $this->vardef_map['ext2'] = 'textcolor';
        $this->vardef_map['backcolor'] = 'ext1';
        $this->vardef_map['textcolor'] = 'ext2';
    }

    //BEGIN BACKWARD COMPATIBILITY
    // AS 7.x does not have EditViews and DetailViews anymore these are here
    // for any modules in backwards compatibility mode.

    function get_xtpl_edit()
    {
        $name = $this->name;
        $returnXTPL = array();

        if (!empty($this->help)) {
            $returnXTPL[strtoupper($this->name . '_help')] = translate($this->help, $this->bean->module_dir);
        }

        if (isset($this->bean->$name)) {
            $returnXTPL[$this->name] = $this->bean->$name;
        } else {
            if (empty($this->bean->id)) {
                $returnXTPL[$this->name] = $this->default_value;
            }
        }
        return $returnXTPL;
    }

    function get_xtpl_search()
    {
        if (!empty($_REQUEST[$this->name])) {
            return $_REQUEST[$this->name];
        }
    }

    function get_xtpl_detail()
    {
        $name = $this->name;
        if (isset($this->bean->$name)) {
            return $this->bean->$name;
        }
        return '';
    }

    //END BACKWARD COMPATIBILITY

    /**
     * Function:        get_field_def
     * Description:        Get the field definition attributes that are required for the Highlightfield Field
     *                      the primary reason this function is here is to set the dbType to 'varchar',
     *                      otherwise 'Highlightfield' would be used by default.
     * References:      __construct function above
     *
     * @return            Field Definition
     */
    function get_field_def()
    {
        $def = parent::get_field_def();

        //set our fields database type
        $def['dbType'] = 'varchar';

        //set our fields to false to avoid issues with Report Wizard.
        $def['reportable'] = false;

        //set our field as custom type
        $def['custom_type'] = 'varchar';

        //map our extension fields for colorizing the field
        $def['backcolor'] = !empty($this->backcolor) ? $this->backcolor : $this->ext1;
        $def['textcolor'] = !empty($this->textcolor) ? $this->textcolor : $this->ext2;

        return $def;
    }
}

Note: For the custom field type, the ext1, ext2, ext3, and ext4 database fields in the fields_meta_data table offer additional property storage.

Creating the Form Controller

Next, set up the field's form controller. This controller will handle the field's form template in Admin > Studio > Fields and allow us to assign color values to the Smarty template.

./custom/modules/DynamicFields/templates/Fields/Forms/Highlightfield.php

  <?php

if (!defined('sugarEntry') || !sugarEntry) die('Not A Valid Entry Point');

require_once 'custom/modules/DynamicFields/templates/Fields/TemplateHighlightfield.php';

/**
 * Implement get_body function to correctly populate the template for the ModuleBuilder/Studio
 * Add field page.
 *
 * @param Sugar_Smarty $ss
 * @param array $vardef
 *
 */
function get_body(&$ss, $vardef)
{
    global $app_list_strings, $mod_strings;
    $vars = $ss->get_template_vars();
    $fields = $vars['module']->mbvardefs->vardefs['fields'];
    $fieldOptions = array();
    foreach ($fields as $id => $def) {
        $fieldOptions[$id] = $def['name'];
    }
    $ss->assign('fieldOpts', $fieldOptions);

    //If there are no colors defined, use black text on
    // a white background
    if (isset($vardef['backcolor'])) {
        $backcolor = $vardef['backcolor'];
    } else {
        $backcolor = '#ffffff';
    }
    if (isset($vardef['textcolor'])) {
        $textcolor = $vardef['textcolor'];
    } else {
        $textcolor = '#000000';
    }
    $ss->assign('BACKCOLOR', $backcolor);
    $ss->assign('TEXTCOLOR', $textcolor);

    $colorArray = $app_list_strings['highlightColors'];
    asort($colorArray);

    $ss->assign('highlightColors', $colorArray);
    $ss->assign('textColors', $colorArray);

    $ss->assign('BACKCOLORNAME', $app_list_strings['highlightColors'][$backcolor]);
    $ss->assign('TEXTCOLORNAME', $app_list_strings['highlightColors'][$textcolor]);

    return $ss->fetch('custom/modules/DynamicFields/templates/Fields/Forms/Highlightfield.tpl');
}
?>

Creating the Smarty Template

Once the form controller is in place, create the Smarty template. The .tpl below will define the center content of the field's Studio edit view. The template includes a coreTop.tpl and coreBottom.tpl file. These files add the base field properties such as "Name" and "Display Label" that are common across all field types. This allows us to focus on the fields that are specific to our new field type.

./custom/modules/DynamicFields/templates/Fields/Forms/Highlightfield.tpl

  {include file="modules/DynamicFields/templates/Fields/Forms/coreTop.tpl"}
<tr>
    <td class='mbLBL'>{sugar_translate module="DynamicFields" label="COLUMN_TITLE_DEFAULT_VALUE"}:</td>
    <td>
        {if $hideLevel < 5}
            <input type='text' name='default' id='default' value='{$vardef.default}'
                   maxlength='{$vardef.len|default:50}'>
        {else}
            <input type='hidden' id='default' name='default' value='{$vardef.default}'>{$vardef.default}
        {/if}
    </td>
</tr>
<tr>
    <td class='mbLBL'>{sugar_translate module="DynamicFields" label="COLUMN_TITLE_MAX_SIZE"}:</td>
    <td>
        {if $hideLevel < 5}
            <input type='text' name='len' id='field_len' value='{$vardef.len|default:25}'
                  onchange="forceRange(this,1,255);changeMaxLength(document.getElementById('default'),this.value);">
            <input type='hidden' id="orig_len" name='orig_len' value='{$vardef.len}'>
            {if $action=="saveSugarField"}
                <input type='hidden' name='customTypeValidate' id='customTypeValidate' value='{$vardef.len|default:25}'

                       onchange="if (parseInt(document.getElementById('field_len').value) < parseInt(document.getElementById('orig_len').value)) return confirm(SUGAR.language.get('ModuleBuilder', 'LBL_CONFIRM_LOWER_LENGTH')); return true;">
            {/if}
        {literal}
            <script>
                function forceRange(field, min, max) {
                    field.value = parseInt(field.value);
                    if (field.value == 'NaN')field.value = max;
                    if (field.value > max) field.value = max;
                    if (field.value < min) field.value = min;
                }
                function changeMaxLength(field, length) {
                    field.maxLength = parseInt(length);
                    field.value = field.value.substr(0, field.maxLength);
                }
            </script>
        {/literal}
        {else}
            <input type='hidden' name='len' value='{$vardef.len}'>{$vardef.len}
        {/if}
    </td>
</tr>
<tr>
    <td class='mbLBL'>{sugar_translate module="DynamicFields" label="LBL_HIGHLIGHTFIELD_BACKCOLOR"}:</td>
    <td>
        {if $hideLevel < 5}
            {html_options name="ext1" id="ext1" selected=$BACKCOLOR options=$highlightColors}
        {else}
            <input type='hidden' id='ext1' name='ext1' value='{$BACKCOLOR}'>
            {$BACKCOLORNAME}
        {/if}
    </td>
</tr>
<tr>
    <td class='mbLBL'>{sugar_translate module="DynamicFields" label="LBL_HIGHLIGHTFIELD_TEXTCOLOR"}:</td>
    <td>
        {if $hideLevel < 5}
            {html_options name="ext2" id="ext2" selected=$TEXTCOLOR options=$highlightColors}
        {else}
            <input type='hidden' id='ext2' name='ext2' value='{$TEXTCOLOR}'>
            {$TEXTCOLORNAME}
        {/if}
    </td>
</tr>

{include file="modules/DynamicFields/templates/Fields/Forms/coreBottom.tpl"}

Registering the Field Type

Once you have defined the Studio template, register the field as a valid field type. For this example, the field will inherit the base field type as it is a text field. In doing this, we are able to override the core functions. The most used override is the save function shown below.

./custom/include/SugarFields/Fields/Highlightfield/SugarFieldHighlightfield.php

  <?php

require_once 'include/SugarFields/Fields/Base/SugarFieldBase.php';
require_once 'data/SugarBean.php';

class SugarFieldHighlightfield extends SugarFieldBase
{
    //this function is called to format the field before saving.  For example we could put code in here
    // to check spelling or to change the case of all the letters
    public function save(&$bean, $params, $field, $properties, $prefix = '')
    {
        $GLOBALS['log']->debug("SugarFieldHighlightfield::save() function called.");
        parent::save($bean, $params, $field, $properties, $prefix);
    }
}
?>

Creating Language Definitions

For the new field, you must define several language extensions to ensure everything is displayed correctly. This section includes three steps:

  • Adding the Custom Field to the Type List
  • Creating Labels for Studio
  • Defining Dropdown Controls

Adding the Custom Field to the Type List

Define the new field type in the field types list. This will allow an Administrator to select the Highlightfield field type in Studio when creating a new field.

./custom/Extension/modules/ModuleBuilder/Ext/Language/en_us.Highlightfield.php

  <?php

$mod_strings['fieldTypes']['Highlightfield'] = 'Highlighted Text';

Creating Labels for Studio

Once the field type is defined, add the labels for the Studio template.

./custom/Extension/modules/DynamicFields/Ext/Language/en_us.Highlightfield.php

  <?php

$mod_strings['LBL_HIGHLIGHTFIELD'] = 'Highlighted Text';
$mod_strings['LBL_HIGHLIGHTFIELD_FORMAT_HELP'] = '';

$mod_strings['LBL_HIGHLIGHTFIELD_BACKCOLOR'] = 'Background Color';
$mod_strings['LBL_HIGHLIGHTFIELD_TEXTCOLOR'] = 'Text Color';

Defining Dropdown Controls

For this example, we must define dropdown lists, which will be available in Studio for an administrator to add or remove color options for the field type.

./custom/Extension/application/Ext/Language/en_us.Highlightfield.php

  <?php

$app_strings['LBL_HIGHLIGHTFIELD_OPERATOR_CONTAINS'] = 'contains';
$app_strings['LBL_HIGHLIGHTFIELD_OPERATOR_NOT_CONTAINS'] = 'does not contain';
$app_strings['LBL_HIGHLIGHTFIELD_OPERATOR_STARTS_WITH'] = 'starts with';

$app_list_strings['highlightColors'] = array(
    '#0000FF' => 'Blue',
    '#00ffff' => 'Aqua',
    '#FF00FF' => 'Fuchsia',
    '#808080' => 'Gray',
    '#ffff00' => 'Olive',
    '#000000' => 'Black',
    '#800000' => 'Maroon',
    '#ff0000' => 'Red',
    '#ffA500' => 'Orange',
    '#ffff00' => 'Yellow',
    '#800080' => 'Purple',
    '#ffffff' => 'White',
    '#00ff00' => 'Lime',
    '#008000' => 'Green',
    '#008080' => 'Teal',
    '#c0c0c0' => 'Silver',
    '#000080' => 'Navy'
);

Enabling Search and Filtering

To enable Highlightfield for searching and filtering, define the filter operators and the Sugar widget.

Defining the Filter Operators

The filter operators, defined in ./custom/clients/base/filters/operators/operators.php,  allow the custom field be used for searching in Sidecar listviews. 

./custom/clients/base/filters/operators/operators.php

  <?php

require 'clients/base/filters/operators/operators.php';

$viewdefs['base']['filter']['operators']['Highlightfield'] = array(
    '$contains' => 'LBL_HIGHLIGHTFIELD_OPERATOR_CONTAINS',
    '$not_contains' => 'LBL_HIGHLIGHTFIELD_OPERATOR_NOT_CONTAINS',
    '$starts' => 'LBL_HIGHLIGHTFIELD_OPERATOR_STARTS_WITH',
);

Note: The labels for the filters in this example are defined in  ./custom/Extension/application/Ext/Language/en_us.Highlightfield.php. For the full list of filters in the system, you can look at ./clients/base/filters/operators/operators.php.

Defining the Sugar Widget

Finally, define the Sugar widget. The widget will help our field for display on Reports and subpanels in backward compatibility. It also controls how search filters are applied to our field.

./custom/include/generic/SugarWidgets/SugarWidgetFieldHighlightfield.php

  <?php

if (!defined('sugarEntry') || !sugarEntry) die('Not A Valid Entry Point');

require_once 'include/generic/SugarWidgets/SugarWidgetFieldvarchar.php';

class SugarWidgetFieldHighlightfield extends SugarWidgetFieldVarchar
{
    function SugarWidgetFieldText(&$layout_manager)
    {
        parent::SugarWidgetFieldVarchar($layout_manager);
    }

    function queryFilterEquals($layout_def)
    {
        return $this->reporter->db->convert($this->_get_column_select($layout_def), "text2char") .
        " = " . $this->reporter->db->quoted($layout_def['input_name0']);
    }

    function queryFilterNot_Equals_Str($layout_def)
    {
        $column = $this->_get_column_select($layout_def);
        return "($column IS NULL OR " . $this->reporter->db->convert($column, "text2char") . " != " .
        $this->reporter->db->quoted($layout_def['input_name0']) . ")";
    }

    function queryFilterNot_Empty($layout_def)
    {
        $column = $this->_get_column_select($layout_def);
        return "($column IS NOT NULL AND " . $this->reporter->db->convert($column, "length") . " > 0)";
    }

    function queryFilterEmpty($layout_def)
    {
        $column = $this->_get_column_select($layout_def);
        return "($column IS NULL OR " . $this->reporter->db->convert($column, "length") . " = 0)";
    }

    function displayList($layout_def)
    {
        return nl2br(parent::displayListPlain($layout_def));
    }
}

?>

Once the files are in place, navigate to Admin > Repair > Quick Repair and Rebuild. It is also best practice to clear your browser cache before using the new field type in Studio.