Change the 'Country' in Leads Module to DropDown

Dear Friends,

The default ‘Country’ is a TextField. But it causes mismatches of manual data entry. I wish to replace it with a dropdown which will contain the list of countries, so that the data consistency could be maintained.

I don’t want to delete the existing field, as it already contains the data in corresponding column of Leads table.

Is it possible to change the TYPE of this field to Dropdown by making some coding changes in SuiteCRM and MySQL (if required).

With thanks,

RK

Please read SuiteCRM Open Source Blog :
Convert the country text field in SuiteCRM to a drop-down list

Related:
Why isn’t Address Country a dropdown by default? · Issue #1099 · salesagility/SuiteCRM - https://github.com/salesagility/SuiteCRM/issues/1099

@horus68
It seem that the link you posted doesn’t work anymore?
https://suitecrm.com/suitecrm/blog/entry/convert-the-country-text-field-in-suitecrm-to-a-drop-down-list

Kind regards
PowerQuest

New link:

1 Like

I apologize for this being a bit lengthy but I waned to go through as much as I could to try and figure out what the problem was and ended up writing this out with no success and decided to post it here to see if someone else could see what I am doing wrong.

Several people have used this site ( Convert the Country Text to a Dropdown ) to set the Country field to a drop down. I am using SuiteCRM 7.12.7 and trying to modify the Accounts module country field.

At the bottom of the above linked site it states

The address country field exists on Accounts, Contacts, Leads and Targets by default. But in accounts for example it is billing_address_country and shipping_address_country you will just have to check the field names before makeing the changes.

Describe my Setup:
My table has billing_address_country instead of primary_address_country so I am using billing_address_country.

In the file custom/Extention/modules/Accounts/Ext/Language/Vardefs/_override_sugarfield_billing_address_country.php I have

$dictionary['Account']['fields']['billing_address_country']['required']=true;
$dictionary['Account']['fields']['billing_address_country']['inline_edit']=true;
$dictionary['Account']['fields']['billing_address_country']['comments']='The country used for the billing address';
$dictionary['Account']['fields']['billing_address_country']['merge_filter']='disabled';
$dictionary['Account']['fields']['billing_address_country']['type'] = 'enum';
$dictionary['Account']['fields']['billing_address_country']['options'] = 'countries_dom';
$dictionary['Account']['fields']['billing_address_country']['group'] = 'billing_address';

Several sites I found stated you must use the file en_us.EditView.tpl instead of the file EditView.tpl from the include/SugarFields/Fields/Address folder. This is true for my case since my $current_language is set to “en_us ”.

In custom/modules/Accounts/metadata/edtiviewdefs.php I have:

        array (
          0 => 
          array (
            'name' => 'billing_address_street',
            'hideLabel' => true,
            'type' => 'CustomAddress',
            'displayParams' => 
            array (
              'key' => 'billing',
              'rows' => 2,
              'cols' => 30,
              'maxlength' => 150,
            ),
            'label' => 'LBL_BILLING_ADDRESS_STREET',
          ),

Details:

I am trying to get this to work but not having any luck. For clarification, can anyone tell me if the two labels used in this post CUSTOM_FIELD_TYPE_NAME and CUSTOM_TYPE_NAME are supposed to be the same text? If they are supposed to be the same text then does it matter what text I use? If so I can’t get it to work meaning there is no drop down because the custom template is not used.

The only way I get the drop down to show is if I

  1. Place a folder in the path custom/include/SugarFields/Fields/ named Address
  2. Place a customized en_us.EditView.tpl file in this folder.
  3. In the file custom/modules/Accounts/metadata/editviewdefs.php, set the value for ‘type’ => ‘address’,. However, the dropdown is empty.

Questions:

  1. Does anyone know why my drop down is empty?
  2. Has anyone used a custom template name other than “address”? (later on got this to work see below)
  3. This appears to a global configuration. By that I mean anywhere use the “address” this template is used. Is this Global? (I think so if using Address as a field type)

Digging into base Code:

I did some searching and found where I believe this template is selected. I only found one place in the code that searches for .tpl files in the include/SugarFields/Fields folder. In the file include/SugarFields/Fields/Base/SugarFieldBase.php around line 100 you will find this method:

  /**
     * @param string $view Eg EditView
     * @return string
     */
    public function findTemplate($view)
    {
        static $tplCache = array();

        if (isset($tplCache[$this->type][$view])) {
            return $tplCache[$this->type][$view];
        }

        $lastClass = get_class($this);
        $classList = array($this->type, str_replace('SugarField', '', $lastClass));
        while ($lastClass = get_parent_class($lastClass)) {
            $classList[] = str_replace('SugarField', '', $lastClass);
        }

        $tplName = '';
        foreach ($classList as $className) {
            global $current_language;
            if (isset($current_language)) {
                $tplName = 'include/SugarFields/Fields/' . $className . '/' . $current_language . '.' . $view . '.tpl';
                if (file_exists('custom/' . $tplName)) {
                    $tplName = 'custom/' . $tplName;
                    break;
                }
                if (file_exists($tplName)) {
                    break;
                }
            }
            $tplName = 'include/SugarFields/Fields/' . $className . '/' . $view . '.tpl';
            if (file_exists('custom/' . $tplName)) {
                $tplName = 'custom/' . $tplName;
                break;
            }
            if (file_exists($tplName)) {
                break;
            }
        }

        $tplCache[$this->type][$view] = $tplName;

        return $tplName;
    }

Looking at this code in my case the variable $current_language is set to “en_us”. Dumping the $classList only contains this:

ClassName: Enum
ClassName: Base
ClassName: Name
ClassName: Base
ClassName: Base
ClassName: Phone
ClassName: Address
ClassName: Float
ClassName: Datetime
ClassName: Int

As you can see ClassNames are what is used to search for field types. So I wonder, how would one add a custom CUSTOM_FIELD_TYPE_NAME? more to come…

So I did some more digging and found that in the file include/SugarFields/SugarFieldHander.php is where CUSTOM_FIELD_TYPE_NAME search is found in method getSugarField around line 88.

 /**
     * return the singleton of the SugarField
     * @param $field
     * @param bool $returnNullIfBase
     * @return mixed
     */
    public static function getSugarField($field, $returnNullIfBase=false)
    {
        static $sugarFieldObjects = array();

        $field = self::fixupFieldType($field);
        $field = ucfirst($field);

        if (!isset($sugarFieldObjects[$field])) {
            //check custom directory
            if (file_exists('custom/include/SugarFields/Fields/' . $field . '/SugarField' . $field. '.php')) {
                $file = 'custom/include/SugarFields/Fields/' . $field . '/SugarField' . $field. '.php';
                $type = $field;
                error_log("file:{$file}");
                error_log("Type: {$type}");        
            //else check the fields directory
            } elseif (file_exists('include/SugarFields/Fields/' . $field . '/SugarField' . $field. '.php')) {
                $file = 'include/SugarFields/Fields/' . $field . '/SugarField' . $field. '.php';
                $type = $field;
            } else {
                // No direct class, check the directories to see if they are defined
                if ($returnNullIfBase &&
                    !is_dir('custom/include/SugarFields/Fields/'.$field) &&
                    !is_dir('include/SugarFields/Fields/'.$field)) {
                    return null;
                }
                $file = get_custom_file_if_exists('include/SugarFields/Fields/Base/SugarFieldBase.php');
                $type = 'Base';
            }
            require_once($file);

            $class = 'SugarField' . $type;
            //could be a custom class check it
            $customClass = 'Custom' . $class;
            if (class_exists($customClass)) {
                $sugarFieldObjects[$field] = new $customClass($field);
            } else {
                $sugarFieldObjects[$field] = new $class($field);
            }
        }
        return $sugarFieldObjects[$field];
    }

As you can see that the code is looking for a php file in custom/includeSugarFields/Fields/XXX folder where XXX is going to be one of the class names found in the findTemplate() method.

So more digging I found that this .php file when found needs to be a class file with a name SugarField that extends the SugarFieldBase class. So I added a file named SugarFieldCustomAddress.php in the custom/includes/SugarFields/Fields folder. This file looks like this:

<?php
require_once('include/SugarFields/Fields/Base/SugarFieldBase.php');

class SugarFieldCustomAddress extends SugarFieldBase
{
    public function getEditViewSmarty($parentFieldArray, $vardef, $displayParams, $tabindex)
    {
        $this->setup($parentFieldArray, $vardef, $displayParams, $tabindex);
        global $app_strings;
        if (!isset($displayParams['key'])) {
            $GLOBALS['log']->debug($app_strings['ERR_ADDRESS_KEY_NOT_SPECIFIED']);
            $this->ss->trigger_error($app_strings['ERR_ADDRESS_KEY_NOT_SPECIFIED']);
            return;
        }

        //Allow for overrides.  You can specify a Smarty template file location in the language file.
        if (isset($app_strings['SMARTY_ADDRESS_EDITVIEW'])) {
            $tplCode = $app_strings['SMARTY_ADDRESS_EDITVIEW'];
            return $this->fetch($tplCode);
        }


        return $this->fetch("custom/include/SugarFields/Fields/Account/CustomAddress/en_us.EditView.tpl");
    }
}

This is copy of the base file with the return value changed to point to my
This does return my custom en_us.EditView.tpl template. However, like the first method I used, the Dropdown is still empty.

I don’t know what why the drop down is empty in both methods.

Here is part of my counries_dom find in the include/en_us.lang.php file:

$app_list_strings['countries_dom'] = array(
    '' => '',
    'ABU DHABI' => 'ABU DHABI',
    'ADEN' => 'ADEN',
    'AFGHANISTAN' => 'AFGHANISTAN',
    'ALBANIA' => 'ALBANIA',
    'ALGERIA' => 'ALGERIA',
    'UNITED ARAB EMIRATES' => 'UNITED ARAB EMIRATES',
.....
    'UNITED KINGDOM' => 'UNITED KINGDOM',
    'URUGUAY' => 'URUGUAY',
    'US PACIFIC ISLAND' => 'US PACIFIC ISLAND',
    'US VIRGIN ISLANDS' => 'US VIRGIN ISLANDS',
    'USA' => 'USA',
    'ZAMBIA' => 'ZAMBIA',
    'ZIMBABWE' => 'ZIMBABWE',
);

Here is my customized en_us.EditView.tpl file in the custom/include/SugarFields/Fields/CustomAddress folder:

<script src='{sugar_getjspath file="include/SugarFields/Fields/Address/SugarFieldAddress.js"}'></script>
{{assign var="key" value=$displayParams.key|upper}}
{{assign var="street" value=$displayParams.key|cat:'_address_street'}}
{{assign var="city" value=$displayParams.key|cat:'_address_city'}}
{{assign var="state" value=$displayParams.key|cat:'_address_state'}}
{{assign var="country" value=$displayParams.key|cat:'_address_country'}}
{{assign var="postalcode" value=$displayParams.key|cat:'_address_postalcode'}}
<fieldset id='{{$key}}_address_fieldset'>
    <legend>*** NEW TEMPLATE ***{sugar_translate label='LBL_{{$key}}_ADDRESS' module='{{$module}}'}</legend>
    <table border="0" cellspacing="1" cellpadding="0" class="edit" width="100%">
        <tr>
            <td valign="top" id="{{$street}}_label" width='25%' scope='row'>
                <label for="{{$street}}">{sugar_translate label='LBL_{{$key}}_STREET' module='{{$module}}'}:</label>
                {if $fields.{{$street}}.required || {{if $street|lower|in_array:$displayParams.required}}true{{else}}false{{/if}}}
                <span class="required">{$APP.LBL_REQUIRED_SYMBOL}</span>
                {/if}
            </td>
            <td width="*">
                {{if $displayParams.maxlength}}
                <textarea id="{{$street}}" name="{{$street}}" title='{{$vardef.help}}' maxlength="{{$displayParams.maxlength}}"
                          rows="{{$displayParams.rows|default:4}}" cols="{{$displayParams.cols|default:60}}"
                          tabindex="{{$tabindex}}">{$fields.{{$street}}.value}</textarea>
                {{else}}
                <textarea id="{{$street}}" name="{{$street}}" title='{{$vardef.help}}' rows="{{$displayParams.rows|default:4}}"
                          cols="{{$displayParams.cols|default:60}}"
                          tabindex="{{$tabindex}}">{$fields.{{$street}}.value}</textarea>
                {{/if}}
            </td>
        </tr>

        <tr>

            <td id="{{$city}}_label" width='{{$def.templateMeta.widths[$smarty.foreach.colIteration.index].label}}%'
                scope='row'>
                <label for="{{$city}}">{sugar_translate label='LBL_CITY' module='{{$module}}'}:
                    {if $fields.{{$city}}.required || {{if $city|lower|in_array:$displayParams.required}}true{{else}}false{{/if}}}
                    <span class="required">{$APP.LBL_REQUIRED_SYMBOL}</span>
                    {/if}
            </td>
            <td>
                <input type="text" name="{{$city}}" id="{{$city}}" title='{$fields.{{$city}}.help}' size="{{$displayParams.size|default:30}}"
                       {{if !empty($vardef.len)}}maxlength='{{$vardef.len}}'{{/if}} value='{$fields.{{$city}}.value}'
                       tabindex="{{$tabindex}}">
            </td>
        </tr>

        <tr>
            <td id="{{$state}}_label" width='{{$def.templateMeta.widths[$smarty.foreach.colIteration.index].label}}%'
                scope='row'>
                <label for="{{$state}}">{sugar_translate label='LBL_STATE' module='{{$module}}'}:</label>
                {if $fields.{{$state}}.required || {{if $state|lower|in_array:$displayParams.required}}true{{else}}false{{/if}}}
                <span class="required">{$APP.LBL_REQUIRED_SYMBOL}</span>
                {/if}
            </td>
            <td>
                <input type="text" name="{{$state}}" id="{{$state}}" title='{$fields.{{$state}}.help}' size="{{$displayParams.size|default:30}}"
                       {{if !empty($vardef.len)}}maxlength='{{$vardef.len}}'{{/if}} value='{$fields.{{$state}}.value}'
                       tabindex="{{$tabindex}}">
            </td>
        </tr>

        <tr>

            <td id="{{$postalcode}}_label"
                width='{{$def.templateMeta.widths[$smarty.foreach.colIteration.index].label}}%' scope='row'>

                <label for="{{$postalcode}}">{sugar_translate label='LBL_POSTAL_CODE' module='{{$module}}'}:</label>
                {if $fields.{{$postalcode}}.required || {{if $postalcode|lower|in_array:$displayParams.required}}true{{else}}false{{/if}}}
                <span class="required">{$APP.LBL_REQUIRED_SYMBOL}</span>
                {/if}
            </td>
            <td>
                <input type="text" name="{{$postalcode}}" id="{{$postalcode}}" title='{$fields.{{$postalcode}}.help}' size="{{$displayParams.size|default:30}}"
                       {{if !empty($vardef.len)}}maxlength='{{$vardef.len}}'{{/if}}
                       value='{$fields.{{$postalcode}}.value}' tabindex="{{$tabindex}}">
            </td>
        </tr>

        <tr>

            <td id="{{$country}}_label" width='{{$def.templateMeta.widths[$smarty.foreach.colIteration.index].label}}%'
                scope='row'>

                <label for="{{$country}}">{sugar_translate label='LBL_COUNTRY' module='{{$module}}'}:</label>
                {if $fields.{{$country}}.required || {{if $country|lower|in_array:$displayParams.required}}true{{else}}false{{/if}}}
                <span class="required">{$APP.LBL_REQUIRED_SYMBOL}</span>
                {/if}
            </td>
            <td>
               <select name="{{$country}}" width="{{$displayParams.size|default:30}}" id="{{$country}}" title="{{$vardef.help}}" 
                {{if !empty($tabindex)}} tabindex="{{$tabindex}}" {{/if}} 
                {{if isset($displayParams.script)}}{{$displayParams.script}}{{/if}}>
                <option value='testing'>Test Option</option>
                {if isset($fields.{{$country}}.value) && $fields.{{$country}}.value != ''}
                    {html_options options=$fields.{{$country}}.options selected=$fields.{{$country}}.value}
                {else}
                    {html_options options=$fields.{{$country}}.options selected=$fields.{{$country}}.default_value}
                {/if}
                </select>
            </td>
        </tr>

        {{if $displayParams.copy}}
        <tr>
            <td scope='row' NOWRAP>
                {sugar_translate label='LBL_COPY_ADDRESS_FROM_LEFT' module=''}:
            </td>
            <td>
                <input id="{{$displayParams.key}}_checkbox" name="{{$displayParams.key}}_checkbox" type="checkbox"
                       onclick="{{$displayParams.key}}_address.syncFields();">
            </td>
        </tr>
        {{else}}
        <tr>
            <td colspan='2' NOWRAP>&nbsp;</td>
        </tr>
        {{/if}}
    </table>
</fieldset>
<script type="text/javascript">
  SUGAR.util.doWhen("typeof(SUGAR.AddressField) != 'undefined'", function () {ldelim}
      {{$displayParams.key}}_address = new SUGAR.AddressField("{{$displayParams.key}}_checkbox", '{{$displayParams.copy}}', '{{$displayParams.key}}');
      {rdelim});
</script>

I have done a Quick Repair & Rebuild multiple times during this process.

Looking for Ideas.

Thank,

Tony

I believe the answer is yes, both are the same, they would be CustomAddress in your case.

I suspect that the setup function in the Base class does not handle loading the Dropdown values into Smarty.

If you override that function, call the parent::setup, and then load your values into the Smarty templates with assign calls, you should be able to get them in the tpl.

Just an update on my problem.

Pay attention to the layout of your code! At some point, in my IDE, I must have dragged the folder custom/Extension/modules/Account/Ext/Vardefs folder to the custom/Extension/modules/Account/Ext/Language folder and did not catch it. :man_facepalming:
Of course you know what that did.

After moving the folder back in the correct place, doing a Repair / QR&R everything magically starts working. I wasted so many hours on this. However, I did learned a lot about the base code of SuiteCRM. For that I am glad.

1 Like