This page applies to the legacy "BWC" calendar offering. Sugar has released a Sidecar-based calendar in version 11.2.0; this content does not apply to the Sidecar calendar.
Modifying Calendar Item Colors
Overview
Each calendar event type (Meetings, Calls, and Tasks) has its own distinct color scheme. This article will review how to modify these colors, as well as demonstrate how to set the event color based on the event status.
The color scheme for each calendar activity type is defined in the stock file ./modules/Calendar/CalendarDisplay.phpÂ in the $activity_colors array as shown below:
public $activity_colors = array(
	'Meetings' => array(
		'border' => '#1C5FBD',
		'body' => '#D2E5FC',
	),
	'Calls' => array(


),
	'Calls' => array(
		'border' => '#DE4040',
		'body' => '#FCDCDC',
	),
	'Tasks' => array(
		'border' => '#015900',
		'body' => '#B1F5AE',
	),
);
This file can not be overridden or extended in an upgrade-safe way, but this array can still be overridden later in the rendering process allowing for an upgrade safe customization.
Modifying the Calendar Activity Colors
The CalendarDisplay class passes the $activity_colors array to javascript by way of the ./modules/Calendar/tpls/main.tpl Smarty template.
In this template file, you will see the following code where the CAL.activity_colorsÂ object is setup:


CAL.activity_colors = [];
{foreach name=colors from=$activity_colors key=module item=v}
	CAL.activity_colors['{$module}'] = [];
	CAL.activity_colors['{$module}']['border'] = '{$v.border}';
	CAL.activity_colors['{$module}']['body'] = '{$v.body}'
{/foreach}
This is where we can update the $activity_colors array in an upgrade safe manner. To do this, copy the template file from./modules/Calendar/tpls/main.tpl to ./custom/modules/Calendar/tpls/main.tpl.
If we wanted to change Meetings to have a purple color scheme, we might choose:


If we wanted to change Meetings to have a purple color scheme, we might choose:
border: #580059body : #F2AEF5
To implement this, we'll update the activity colors array in the custom template file as follows:
./custom/modules/Calendar/tpls/main.tpl
CAL.activity_colors = [];
{foreach name=colors from=$activity_colors key=module item=v}
	CAL.activity_colors['{$module}'] = [];
	CAL.activity_colors['{$module}']['border'] = '{$v.border}';
	CAL.activity_colors['{$module}']['body'] = '{$v.body}'
{/foreach}
{literal}


{/foreach}
{literal}
CAL.activity_colors["Meetings"] = {
	"border": "#580059",
	"body": "#F2AEF5"
};
{/literal}
In the above code, the activity_colors entry for "Meetings" has specifically been altered to set the custom colors just for Meetings, leaving the other activity types as they were. Alternatively, the entire activity_colors array could be updated, replacing the original coming from CalendarDisplay. For example:
./custom/modules/Calendar/tpls/main.tpl
CAL.activity_colors = [];
{foreach name=colors from=$activity_colors key=module item=v}
	CAL.activity_colors['{$module}'] = [];


CAL.activity_colors['{$module}'] = [];
	CAL.activity_colors['{$module}']['border'] = '{$v.border}';
	CAL.activity_colors['{$module}']['body'] = '{$v.body}'
{/foreach}
{literal}
CAL.activity_colors = {
		"Meetings": {
			"border": "#580059",
			"body": "#F2AEF5"
		},
		"Calls": {
			"border": "#DE4040",
			"body": "#FCDCDC"
		},
		"Tasks": {
			"border": "#015900",


"Tasks": {
			"border": "#015900",
			"body": "#B1F5AE"
		}
	};
{/literal}
While Calls and Tasks have not been modified from their original colors, having the entire array defined here may make it easier to make further color customizations later on, since the modified activity_colors will be completely defined in the custom template file.
After making the above changes, run a Quick Repair and Rebuild and reload the Calendar module in your browser to see the changes take effect.
Customizing colors based on event status


Customizing colors based on event status
If you wanted to go a step further, you might want to customize the colors based on the status of the activity. For example, perhaps for Meetings, the color should reflect if the Meeting status is Scheduled, Held, or Canceled. To achieve this, we will need to customize the javascript that actually renders the activities onto the calendar view, so we will copyÂ ./modules/Calendar/Cal.js to ./custom/modules/Calendar/Cal.js.
Additionally, in order for our custom template to load the custom Cal.js, it will need to be updated to point to the custom Cal.js by changing the line:
{sugar_getscript file="modules/Calendar/Cal.js"}
to
{sugar_getscript file="custom/modules/Calendar/Cal.js"}


{sugar_getscript file="custom/modules/Calendar/Cal.js"}
The activity colors from the CAL.activity_colors object get applied in the Cal.js script at:
el.style.backgroundColor = CAL.activity_colors[item.module_name]['body'];
el.style.borderColor = CAL.activity_colors[item.module_name]['border'];
The activity type (Meetings, Calls, Tasks) comes from item.module_name, as shown above. The activity's status can be found in the item.status variable. Using these, we can modify the color-setting logic to apply a status-specific color scheme to the Meeting items in the calendar, for example:
if (item.module_name =="Meetings") {


if (item.module_name =="Meetings") {
    el.style.backgroundColor = CAL.activity_colors[item.module_name][item.status]['body'];
    el.style.borderColor     = CAL.activity_colors[item.module_name][item.status]['border'];
}
else {
    el.style.backgroundColor = CAL.activity_colors[item.module_name]['body'];
    el.style.borderColor     = CAL.activity_colors[item.module_name]['border'];
}
This assumes that the 'Meetings' part of CAL.activity_colors has status-specific colors set. Therefore we will need to update our custom template file so that this is the case:
./custom/modules/Calendar/tpls/main.tpl
{literal}


{literal}
CAL.activity_colors = {
    "Meetings": {
        "Planned": {
            "border": "#FF6666",
            "body": "#FF6666"
        },
        "Held": {
            "border": "#FF9999",
            "body": "#FF9999"
        },
        "Not Held": {
            "border": "#6C8CD5",
            "body": "#6C8CD5"
        }
    },
    "Calls": {
        "border": "#DE4040",
        "body": "#FCDCDC"
    },
    "Tasks": {
        "border": "#015900",
        "body": "#B1F5AE"
    },
    "Planned": {
        "border": "#ff6666",


},
    "Planned": {
        "border": "#ff6666",
        "body": "#ff6666"
    },
    "Held": {
        "border": "#FF9999",
        "body": "#FF9999"
    },
    "Not Held": {
        "border": "#6C8CD5",
        "body": "#6C8CD5"
    }
};
{/literal}
After making the above changes, run Rebuild JS Grouping Files followed by a Quick Repair and Rebuild, then reload the Calendar module in your browser to see the changes take effect.
  	Last modified: 2023-02-03 21:04:03


Prepopulating the Compose Email View
Overview
When composing an email in Sugar, it may be useful to modify the compose view to better suit your common business practices. In this article, we will use JavaScript to create a code-level customization which causes the To, CC, and Subject fields of the email to prepopulate with data from a related Opportunity.
Steps to Complete
Create a Custom Compose Email View
To modify the default compose email functionality, we will need to create our own compose-email view that extends from the base compose-email view. To accomplish this, we will need to create the following file:
./custom/modules/Emails/clients/base/views/compose-email/compose-email.js
({
    extendsFrom: 'EmailsComposeEmailView',
    initialize: function(options){
        this._super('initialize',[options]);


initialize: function(options){
        this._super('initialize',[options]);
        this.configureDefaults();
    },
    /**
     * Configure the default data
     */
    configureDefaults: function(){
        var model = this.context.parent.get('model');
        var module = this.context.parent.get('module');
        if (module == 'Opportunities'){
            var contacts;
            var oppName = model.get('name');
            if (this.model.get('to_collection').length === 0) {
                var email = this.model;
                //Get the related contacts to the Opportunity
                contacts = model.getRelatedCollection('contacts');
                contacts.fetch({
                    relate: true,
                    success: function (data) {
                        var ccRecords = [],


success: function (data) {
                        var ccRecords = [],
                            toRecords = [];
                        data.forEach(function (record) {
                            var parentName = app.utils.getRecordName(record);
                            if (record.attributes.opportunity_role == 'Primary Decision Maker') {
                                //Primary decisions makers are added to the TO Recipients
                                toRecords.push(app.data.createBean('EmailParticipants', {
                                    _link: 'to',
                                    parent: _.extend({type: record.module}, record.attributes),
                                    parent_type: record.module,
                                    parent_id: record.get('id'),
                                    parent_name: parentName
                                }));
                            } else {


parent_name: parentName
                                }));
                            } else {
                                //All other contacts are added to the CC Recipients
                                ccRecords.push(app.data.createBean('EmailParticipants', {
                                    _link: 'cc',
                                    parent: _.extend({type: record.module}, record.attributes),
                                    parent_type: record.module,
                                    parent_id: record.get('id'),
                                    parent_name: parentName
                                }));
                            }
                        });
                        email.get('to_collection').add(toRecords);
                        email.get('cc_collection').add(ccRecords);
                    },
                    fields: ['id', 'full_name', 'email', 'opportunity_role']
                });
            }


});
            }
            //Default the Subject of Email to the Opportunity Name
            email.set('name',oppName);
        }
    }
})
Some key things to note about this customization:
The custom view should extend from EmailsComposeEmailView
When adding recipients to an Email Bean Model, you should create an EmailParticipants Bean Model and specify the _link property that will be used.
This view will be used when clicking on the "Create" button in the Emails subpanel, or when clicking on Email Address on a record when the user is configured to use the Sugar Email Client
Once you have added the custom code, execute a Quick Repair and rebuild via Admin > Repair > Quick Repair and Rebuild, and your changes should now be reflected in your instance.
  	Last modified: 2023-02-03 21:04:03


Refreshing Subpanels on the RecordView
Overview
How to refresh specific subpanels on the Record View.
Refreshing Subpanels
When Working with the Record View, it is sometimes necessary to force the refresh of a subpanel. The following example will demonstrate how to add buttons to force refresh a specific subpanel or all subpanels on the Accounts RecordView.
Adding the Button Metadata
For our example, we will first create a metadata extension file to append our custom refresh buttons to the Accounts RecordView action menu.
./custom/Extension/modules/Accounts/Ext/clients/base/views/record/refreshButtons.php
<?php
//module name
$module = 'Accounts';
//buttons to append
$addButtons = array(
    array(
        'type' => 'divider',
    ),
    array (


'type' => 'divider',
    ),
    array (
        'type' => 'rowaction',
        'event' => 'button:refresh_specific_subpanel:click',
        'name' => 'refresh_specific_subpanel',
        'label' => 'LBL_REFRESH_SPECIFIC_SUBPANEL',
        'acl_action' => 'view',
    ),
    array (
        'type' => 'rowaction',
        'event' => 'button:refresh_all_subpanels:click',
        'name' => 'refresh_all_subpanels',
        'label' => 'LBL_REFRESH_ALL_SUBPANELS',
        'acl_action' => 'view',
    )
);


'acl_action' => 'view',
    )
);
//if the buttons are missing in our base modules metadata, include core buttons
if (!isset($viewdefs[$module]['base']['view']['record']['buttons']))
{
    require('clients/base/views/record/record.php');
    $viewdefs[$module]['base']['view']['record']['buttons'] = $viewdefs['base']['view']['record']['buttons'];
    unset($viewdefs['base']);
}
foreach($viewdefs[$module]['base']['view']['record']['buttons'] as $outerKey => $outerButton)
{
    if (
        isset($outerButton['type'])


{
    if (
        isset($outerButton['type'])
        && $outerButton['type'] == 'actiondropdown'
        && isset($outerButton['name'])
        && $outerButton['name'] == 'main_dropdown'
        && isset($outerButton['buttons'])
    )
    {
        /*
        //removing buttons by name
        foreach($viewdefs[$module]['base']['view']['record']['buttons'][$outerKey]['buttons'] as $innerKey => $innerButton)
        {
            if (
                isset($innerButton['name'])
                && $innerButton['name'] == 'button_name'
            )
            {


)
            {
                unset($viewdefs[$module]['base']['view']['record']['buttons'][$outerKey]['buttons'][$innerKey]);
            }
        }
        */
        //appending buttons
        foreach ($addButtons as $addButton)
        {
            $viewdefs[$module]['base']['view']['record']['buttons'][$outerKey]['buttons'][]=$addButton;
        }
    }
}
Next, we will create our language labels for theÂ buttons.
./custom/Extension/modules/Accounts/Ext/Language/en_us.refreshButtons.php
<?php


<?php
$mod_strings['LBL_REFRESH_SPECIFIC_SUBPANEL'] = 'Refresh Specific Subpanel';
$mod_strings['LBL_REFRESH_ALL_SUBPANELS'] = 'Refresh All Subpanels';
Our next step is to extend the Accounts controller file. This is where we will add our code to refresh the subpanels when the buttons are clicked.
./custom/modules/Accounts/clients/base/views/record/record.js
({
    extendsFrom: 'RecordView',
    initialize: function (options) {
       this._super('initialize', [options]);
        //add listeners for custom buttons
        this.context.on('button:refresh_specific_subpanel:click', this.refresh_specific_subpanel, this);


this.context.on('button:refresh_all_subpanels:click', this.refresh_all_subpanels, this);
    },
    /**
     * Refreshes a specific subpanel given a link
     */
    refresh_specific_subpanel: function() {
        var linkName = 'contacts';
        var subpanelCollection = this.model.getRelatedCollection(linkName);
        subpanelCollection.fetch({relate: true});
    },
    /**
     * Refreshes all subpanels
     */
    refresh_all_subpanels: function() {
        _.each(this.model._relatedCollections, function(collection){
            collection.fetch({relate: true});
        });
    }
})


collection.fetch({relate: true});
        });
    }
})
Note:Â To refresh a specific subpanel, you will need to pass the linkname of the relationship to theÂ this.model.getRelatedCollection() method.
Finally, we will then need to navigate to Admin > Repair > Quick Repair and Rebuild. This will rebuild our extensions and make the refresh buttons available on our RecordView.
  	Last modified: 2023-02-03 21:04:03


Disabling Tooltips
Overview
This article will demonstrate how to disable the tooltips in Sugar.
Steps to Complete
Creating a Custom JavaScript File
First, we will need to create a custom JavaScript file. This file can technically exist anywhere within the root of yourÂ Sugar instance.
custom/JavaScript/disable_tooltips.js
(function(app){
     app.events.on('app:init', function(){
          // Clear existing ones
          app.tooltip.clear();
          // Disable all
          app.tooltip._disable();
     });
})(SUGAR.App);
Appending to JSGroupings
Second, we need to add our disable_tooltips.js file to sugar_grp7.min.js in ourÂ JSGroupings.
custom/Extension/application/Ext/JSGroupings/disable_tooltips.php
<?php


<?php
foreach ($js_groupings as $key => $groupings) {
    foreach ($groupings as $file => $target) {
        if ($target == 'include/javascript/sugar_grp7.min.js') {
            $js_groupings[$key]['custom/JavaScript/disable_tooltips.js'] = 'include/javascript/sugar_grp7.min.js';
        }
        break;
    }
}
Note: More information about JSGroupings can be found here.
Quick Repair and Rebuild


Note: More information about JSGroupings can be found here.
Quick Repair and Rebuild
After creating theÂ ./custom/JavaScript/disable_tooltips.js andÂ ./custom/Extension/application/Ext/JSGroupings/disable_tooltips.php files, navigate to Admin > Repairs andÂ perform a "Quick Repair and Rebuild". This will rebuild the cached files to fully implementÂ the changes.Â  Once "Quick Repair and Rebuild" finishes, the tooltips will be disabled through Sugar.
  	Last modified: 2023-02-03 21:04:03


Enabling Importing for Custom Modules
Overview
When designing a custom module in Module Builder, you have the option to enable importing for the module. If the custom module is deployed without enabling this option, it is not recommended that you redeploy the module since any changes made in Studio and potentially other areas of the application could be lost. This article will cover how to enable importing for custom modules via a code-level changeÂ to preserve any additional configurations made to the module since being deployed from Module Builder.
Steps to Complete


Steps to Complete
To enable importing for your custom module, you mustÂ modify certain PHP files depending on the version of Sugar that you have. Please note that the instructions below apply to custom modules created via Admin > Module Builder and are not applicable to any stock modules which come out-of-the-box with Sugar. All of the directory paths are relative to the root directory of Sugar on the web server and require you to replace the <module_key> and <module_name> variables with appropriate values for your situation. For instance, if your module is installed in the directory of ./modules/abc_custom_module/ then the <module_key> would be abcÂ and <module_name> would be custom_module.
Edit the ./modules/<module_key>_<module_name>/<module_key>_<module_name>_sugar.php file in your Sugar file system.


Around line 24 of the file, locate and changeÂ public $importable = false;Â toÂ public $importable = true;.
Save your changes to the file.
Next, edit the ./modules/<module_key>_<module_name>/clients/base/menus/header/header.php file which should look similar to this:$viewdefs[$moduleName]['base']['menu']['header'] = array(
  array(
    'route' => "#$moduleName/create",
    'label' => 'LNK_NEW_RECORD',
    'acl_action' => 'create',
    'acl_module' => $moduleName,
    'icon' => 'fa-plus',
  ),
  array(
    'route' => "#$moduleName",


),
  array(
    'route' => "#$moduleName",
    'label' => 'LNK_LIST',
    'acl_action' => 'list',
    'acl_module' => $moduleName,
    'icon' => 'fa-bars',
  ),
);
Add the following line to the end of the file after the lastÂ  ),Â but before the ending );:array(
 'route' => "#bwc/index.php?module=Import&action=Step1&import_module=$moduleName&return_module=$moduleName&return_action=index",
 'label' => 'LBL_IMPORT',
 'acl_action' => 'import',
 'acl_module' => $moduleName,
 'icon' => 'icon-upload',
 ),


'icon' => 'icon-upload',
 ),
Save your changes to the file. The updated file should then look similar to this:$viewdefs[$moduleName]['base']['menu']['header'] = array(
  array(
    'route' => "#$moduleName/create",
    'label' => 'LNK_NEW_RECORD',
    'acl_action' => 'create',
    'acl_module' => $moduleName,
    'icon' => 'fa-plus',
  ),
  array(
    'route' => "#$moduleName",
    'label' => 'LNK_LIST',
    'acl_action' => 'list',
    'acl_module' => $moduleName,
    'icon' => 'fa-bars',
  ),
  array(


'icon' => 'fa-bars',
  ),
  array(
    'route' => "#bwc/index.php?module=Import&action=Step1&import_module=$moduleName&return_module=$moduleName&return_action=index",
    'label' => 'LBL_IMPORT',
    'acl_action' => 'import',
    'acl_module' => $moduleName,
    'icon' => 'icon-upload',
 ),
);
Once the necessary changes have been made, log into Sugar and navigate to Admin > Repair and perform a "Quick Repair and Rebuild". This will rebuild the cached files to fully implementÂ the changes.Â
Application


Application
After making theseÂ changes, the "Import {Module Name}" option will appear in the Actions menu of the custom module's module tab. Simply click the triangle in the module tab, then select the Import option to create or update records for your custom module. For instructions on using Sugar's Import tool, please refer to the Import documentation.
Â
  	Last modified: 2023-02-03 21:04:03


Moving Footer content to Sidebar Nav
Overview
This example explains how to add additional buttons to a new section in the sidebar nav layout. You may have previously customized a button that was added to the footer layout, which can be repurposed here and added to the sidebar-nav layout. We will create a custom view and append the view component to the sidebar-nav layout metadata. The additional button will merely show an alert and a flyout menu with a link to the Accounts module but can be expanded to do much more.
Steps To Complete
This tutorial requires the following steps, which are explained in the sections below:
Extend the language files to include the new labels
Creating the Custom View
Appending the View to Layout Metadata
Adding a Secondary Action
Extending the Language files to Include New Labels
We'll want to add a couple of new labels for our new sidebar-nav-item. To do this, create the following file:


./custom/Extension/application/Ext/Language/en_us.greetingNavItem.php
<?php
// Create the "Hello" label
$app_strings['LBL_HELLO_C'] = 'Hello!';
Creating the Custom View with a Primary Action
To add a new sidebar-nav-item, you will first need to create the custom view that extends sidebar-nav-item. To create the custom view create a folder in ./custom/clients/base/views with the name of your view, such as ./custom/clients/base/views/greeting-nav-item/. Once your folder is in place, you can create the two primary files that make up your view. By default, we can use the out-of-the-box template for our view.
greeting-nav-item.jsÂ - Contains the JavaScript controller logic


greeting-nav-item.jsÂ - Contains the JavaScript controller logic
greeting-nav-item.phpÂ - Contains the metadata your view might use. For this example, we simply use the metadata to define whether the greeting will auto-close or not.
./custom/clients/base/views/greeting-nav-item/greeting-nav-item.js
({
    extendsFrom: 'SidebarNavItemView',
    primaryActionOnClick: function() {
        app.alert.show('greeting-alert', {
            level: 'info',
            messages: 'Hello '+ app.user.get('full_name') + '!',
            autoClose: this.meta.autoClose || false
        });
    },
})


});
    },
})
./custom/clients/base/views/greeting-nav-item/greeting-nav-item.php:
<?php
$viewdefs['base']['view']['greeting-nav-item'] = array(
    'autoClose' => true
);
Appending the View to Layout Metadata


'autoClose' => true
);
Appending the View to Layout Metadata
Once the view is created, you simply need to add the view to theÂ sidebar-navÂ Layout metadata. The sidebar-nav Layout is located inÂ ./clients/base/layouts/sidebar-nav/, so appending the view to this layout can be done via the Extension Framework. Create a fileÂ Â ./custom/Extension/application/Ext/clients/base/layouts/sidebar-nav/Â and the Extension Framework will append the metadata to theÂ sidebar-navÂ Layout.
./custom/Extension/application/Ext/clients/base/layouts/sidebar-nav/greeting-nav-item.php
<?php


<?php
$viewdefs['base']['layout']['sidebar-nav']['components'][] = [
  'view' => [
      'name' => 'greeting-nav-item',
      'type' => 'greeting-nav-item',
      'icon' => 'sicon-bell-lg',
      'label' => 'LBL_HELLO_C',
      'template' => 'sidebar-nav-item',
   ],
];
Once all the files are in place, you can run a Quick Repair and Rebuild and a new groupÂ Â sidebar-nav-itemÂ at the bottom of theÂ sidebar-navÂ bar layout.
Â
Note:Â You may need to refresh the page to see the profile menu items removed.
Adding a Secondary Action


Adding a Secondary Action
With the introduction of theÂ sidebar-nav-itemÂ component, we're now introducing Secondary Actions. These appear by way of a kebab menu when hovering on theÂ sidebar-nav-itemÂ container to which they are added. It can be configured similarly to a Primary Action.
In this example, we'll add a secondary action to ourÂ greeting-nav-itemÂ view. It will trigger a flyout menu that has a link to the Accounts module.
First, let's add a new label to the language file we created earlier for our flyout menu header:
<?php
// Create the "Greetings" header
$app_strings['LBL_GREETINGS_C'] = 'Greetings';
Â


Â
Next, we can define the components that will be used in the flyout menus via metadata inÂ greeting-nav-item.php. The new metadata looks like this:
./custom/Extension/application/Ext/clients/base/layouts/sidebar-nav/greeting-nav-item.php
<?php
$viewdefs['base']['layout']['sidebar-nav']['components'][] = [
    'view' => [
        'name' => 'greeting-nav-item',
        'type' => 'greeting-nav-item',
        'icon' => 'sicon-bell-lg',
        'label' => 'LBL_HELLO_C',
        'secondary_action' => true,
        'template' => 'sidebar-nav-item',


'template' => 'sidebar-nav-item',
        'flyoutComponents' => [
            [
                'view' => 'sidebar-nav-flyout-header',
                'title' => 'LBL_GREETINGS_C',
            ],
            [
                'view' => [
                    'type' => 'sidebar-nav-flyout-actions',
                    'actions' => [
                        [
                            'route' => '#Accounts',
                            'label' => 'LBL_ACCOUNTS',
                            'icon' => 'sicon-account-lg',
                        ],
                    ],
                ],
            ],
        ],
    ],
];
Â
Once these changes are added, you canÂ run a Quick Repair and Rebuild and sidebar-nav-item will be updated with a Secondary Action.


Â
Note:Â You may need to refresh the page to see the profile menu items removed.
Â
Customizing the sidebar-nav-item Template
By default, our newÂ sidebar-nav-itemÂ is using the base Handlebars template. If you'd like to customize the template, create the following Handlebar Template fileÂ greeting-nav-item.hbs. The following snippet is the template for the base view for reference.Â
./custom/clients/base/views/greeting-nav-item/greeting-nav-item.hbs
{{#if appIsSynced}}
    <button class="sidebar-nav-item-btn w-full bg-transparent absolute p-0 text-base text-initial"
       rel="tooltip"
       data-placement="{{tooltipPlacement}}"


data-placement="{{tooltipPlacement}}"
       title="{{str label}}"
    >

            {{str label}}

    </button>


</span>
    </button>

    {{#if secondaryAction}}
        <button class="sidebar-nav-item-kebab bg-transparent absolute p-0 h-full">
            <i class="{{buildIcon 'sicon-kebab'}} text-white"></i>
        </button>
    {{/if}}
{{/if}}
Next, inÂ ./custom/Extension/application/Ext/clients/base/layouts/sidebar-nav/greeting-nav-item.php remove theÂ 'template' => 'sidebar-nav-item'Â metadata entry.Â


Note: Sidebar components must be expanded/collapsed state aware now, therefore you must make sure to provideÂ collapsedÂ (simple icon) andÂ expandedÂ (icon + text)Â class elements in your buttons.
Changing position of sidebar-nav-item
If you need to change the position of theÂ sidebar-nav-itemÂ inÂ theÂ sidebar-navÂ layout, it is recommended that the layout be redefined using custom metadata. For example, if youÂ wanted to move the group we created above, we could define the custom metadata as such:
./custom/clients/base/layouts/sidebar-nav/sidebar-nav.php
<?php
/*
 * Your installation or use of this SugarCRM file is subject to the applicable
 * terms available at


* terms available at
 * http://support.sugarcrm.com/Resources/Master_Subscription_Agreements/.
 * If you do not agree to all of the applicable terms or do not have the
 * authority to bind the entity as an authorized representative, then do not
 * install or use this SugarCRM file.
 *
 * Copyright (C) SugarCRM Inc. All rights reserved.
 */
$viewdefs['base']['layout']['sidebar-nav'] = [
    'components' => [
        [
            'layout' => '...',
        ],
        [
            'layout' => [
                'type' => 'sidebar-nav-item-group-modules',
                'css_class' => 'flex-grow flex-shrink',
            ],
        ],
        [
            'layout' => [


],
        ],
        [
            'layout' => [
                'type' => 'sidebar-nav-item-group',
                'name' => 'sidebar-nav-item-group-bottom',
                'css_class' => 'flex-grow-0 flex-shrink-0',
                'components' => [
                    [
                        'view' => [
                            'name' => 'footer-greet-button',
                            'type' => 'footer-greet-button',
                            'route' => '#Accounts',
                            'icon' => 'sicon-bell-lg',
                            'label' => 'Hello!',
                            'flyoutComponents' => [
                                [
                                    'view' => 'sidebar-nav-flyout-header',


'view' => 'sidebar-nav-flyout-header',
                                    'title' => 'Greetings',
                                ],
                                [
                                    'view' => [
                                        'type' => 'sidebar-nav-flyout-actions',
                                        'actions' => [
                                            [
                                                'route' => '#Accounts',
                                                'label' => 'LBL_ACCOUNTS',
                                                'icon' => 'sicon-account-lg',
                                            ],
                                        ],
                                    ],
                                ],
                            ],
                        ],
                    ],
                ],
            ],
        ],
        [
            'layout' => [
                'type' => 'sidebar-nav-item-group',


'type' => 'sidebar-nav-item-group',
                'name' => 'sidebar-nav-item-group-bottom',
                'css_class' => 'flex-grow-0 flex-shrink-0',
                'components' => [ ... ],
            ],
        ],
    ],
];
Note:Â Although this is a best practice for customizing base metadata, these base layouts are subject to change and may be changed in the future. Maintenance of these customizations belongs to the owner of the instance.
Â
  	Last modified: 2023-02-03 21:04:03


Customizing the Start Speed of List View Search
Overview
When searching a Sidecar module's list view, SugarÂ® begins returning results automatically once aÂ predefined number of milliseconds have passed. This article covers how to customize the start speed of the list view search for Sidecar modules in Sugar.
Prerequisites
This change requires code-level customizations, and you will need direct access to the server in order to make the necessary changes in the file system.Â If you already have a relationship with a Sugar partner, you can work with them to make this customization. If not, please refer to the Partner Page to find a reselling partner to help with your development needs.Â
Note: If yourÂ instance is hosted in Sugar's cloud environment, you can create a package that can be installed within Admin > Module Loader. For more information, please refer to the Creating an Installable Package That Copies Files article.


Note: Sugar Sell Essentials customers do not have the ability to upload custom file packages to Sugar using Module Loader.
Use Case
By default, the list view search process in Sugar begins to run 400 milliseconds after a user stops typing or pasting values into one of the search fields.Â For some users or situations, the system-defined start speed for list view search may be considered too fast. We will walk through increasing the length of time it takes before the search starts to run, using 750 milliseconds as an example.
Steps to Complete
The function that controls the list view search speed in Sugar can be found in the following file: ./clients/base/views/filter-quicksearch/filter-quicksearch.jsÂ In order to customize this function, please use the following steps to create an extension in the ./custom/ directory:


Navigate to the ./custom/ directory in the Sugar file system and create the following directory structure if it does not already exist: ./clients/base/views/filter-quicksearch/.
In a text editor application, create a new file.
Copy the code provided below into this new file:({
  extendsFrom: 'FilterQuicksearchView',
  /**
   * @override
   * @param {Object} opts
   */
  initialize: function(opts) {
      this._super('initialize', [opts]);
  },
  /**
   * Fire quick search
   * @param {Event} e
   */
    throttledSearch: _.debounce(function(e) {
      var newSearch = this.$el.val();
      if(this.currentSearch !== newSearch) {
          this.currentSearch = newSearch;


this.currentSearch = newSearch;
          this.layout.trigger('filter:apply', newSearch);
      }
    }, 750),
})
For our example, the value of "750" at the end of the code represents the new start speed of list view search. Feel free to replace it withÂ your desired value in milliseconds.
Save the new file as./custom/clients/base/views/filter-quicksearch/filter-quicksearch.js.
Update the Ownership and Permissions of the directories and file that were created.
For more information on Linux based stacks, please refer to the article RequiredÂ FileÂ SystemÂ PermissionsÂ on Linux.
For more information on Windows-based stacks, please refer to the article Required File System Permissions on Windows With IIS.
Finally, log into Sugar and navigate to Admin > Repair then perform a "Quick Repair and Rebuild".


After completing the steps in this article, Sugar will wait 750 milliseconds before returning search results onceÂ the user has stopped entering search criteria.
  	Last modified: 2023-02-03 21:04:03


Adding the Email Field to a Bean
Overview
This example explains how to add an emails field for modules that don't extend the Person module template. We will create an email field and bring the email functionality module bean. The steps are applicable for stock and custom modules. In this example, we will add the email field into the Opportunities module.
Steps to Complete
This tutorial requires the following steps, which are explained in the sections below:
Creating a Custom Vardef file for email Field and Relationships
Creating a Custom Bean and Modify the save function
Replacing the stock bean with the custom bean
Adding Emails Field into Record View
Creating a Custom Vardef file for email Field and Relationships
You will create a file in the custom/Extension/modules/Opportunities/Â folder for the field and relationships definitions.


./custom/Extension/modules/Opportunities/Ext/Vardefs/custom_email_field.php
<?php
$module = 'Opportunity';
$table_name = 'opportunities';
$dictionary[$module]['fields']['email'] = array(
    'name' => 'email',
    'type' => 'email',
    'query_type' => 'default',
    'source' => 'non-db',
    'operator' => 'subquery',
    'subquery' => 'SELECT eabr.bean_id FROM email_addr_bean_rel eabr JOIN email_addresses ea ON (ea.id = eabr.email_address_id) WHERE eabr.deleted=0 AND ea.email_address LIKE',
    'db_field' => array(


'db_field' => array(
        'id',
    ),
    'vname' => 'LBL_EMAIL_ADDRESS',
    'studio' => array(
        'visible' => true,
        'searchview' => true,
        'editview' => true,
        'editField' => true,
    ),
    'duplicate_on_record_copy' => 'always',
    'len' => 100,
    'link' => 'email_addresses_primary',
    'rname' => 'email_address',
    'module' => 'EmailAddresses',
    'full_text_search' => array(
        'enabled' => true,
        'searchable' => true,
        'boost' => 1.50,
    ),
    'audited' => true,


),
    'audited' => true,
    'pii' => true,
);
$dictionary[$module]['fields']['email1'] = array(
    'name' => 'email1',
    'vname' => 'LBL_EMAIL_ADDRESS',
    'type' => 'varchar',
    'function' => array(
        'name' => 'getEmailAddressWidget',
        'returns' => 'html',
    ),
    'source' => 'non-db',
    'link' => 'email_addresses_primary',
    'rname' => 'email_address',
    'group' => 'email1',
    'merge_filter' => 'enabled',
    'module' => 'EmailAddresses',
    'studio' => false,


'module' => 'EmailAddresses',
    'studio' => false,
    'duplicate_on_record_copy' => 'always',
    'importable' => false,
);
$dictionary[$module]['fields']['email2'] = array(
    'name' => 'email2',
    'vname' => 'LBL_OTHER_EMAIL_ADDRESS',
    'type' => 'varchar',
    'function' => array(
        'name' => 'getEmailAddressWidget',
        'returns' => 'html',
    ),
    'source' => 'non-db',
    'group' => 'email2',
    'merge_filter' => 'enabled',
    'studio' => 'false',


'studio' => 'false',
    'duplicate_on_record_copy' => 'always',
    'importable' => false,
    'workflow' => false,
);
$dictionary[$module]['fields']['invalid_email'] = array(
    'name' => 'invalid_email',
    'vname' => 'LBL_INVALID_EMAIL',
    'source' => 'non-db',
    'type' => 'bool',
    'link' => 'email_addresses_primary',
    'rname' => 'invalid_email',
    'massupdate' => false,
    'studio' => 'false',
    'duplicate_on_record_copy' => 'always',
);


'duplicate_on_record_copy' => 'always',
);
$dictionary[$module]['fields']['email_opt_out'] = array(
    'name' => 'email_opt_out',
    'vname' => 'LBL_EMAIL_OPT_OUT',
    'source' => 'non-db',
    'type' => 'bool',
    'link' => 'email_addresses_primary',
    'rname' => 'opt_out',
    'massupdate' => false,
    'studio' => 'false',
    'duplicate_on_record_copy' => 'always',
);
$dictionary[$module]['fields']['email_addresses_primary'] = array(
    'name' => 'email_addresses_primary',


'name' => 'email_addresses_primary',
    'type' => 'link',
    'relationship' => strtolower($table_name) . '_email_addresses_primary',
    'source' => 'non-db',
    'vname' => 'LBL_EMAIL_ADDRESS_PRIMARY',
    'duplicate_merge' => 'disabled',
    'primary_only' => true,
);
$dictionary[$module]['fields']['email_addresses'] = array(
    'name' => 'email_addresses',
    'type' => 'link',
    'relationship' => strtolower($table_name) . '_email_addresses',
    'source' => 'non-db',


'source' => 'non-db',
    'vname' => 'LBL_EMAIL_ADDRESSES',
    'reportable' => false,
    'unified_search' => true,
    'rel_fields' => array('primary_address' => array('type' => 'bool')),
);
// Used for non-primary mail import
$dictionary[$module]['fields']['email_addresses_non_primary'] = array(
    'name' => 'email_addresses_non_primary',
    'type' => 'varchar',
    'source' => 'non-db',
    'vname' => 'LBL_EMAIL_NON_PRIMARY',
    'studio' => false,
    'reportable' => false,
    'massupdate' => false,
);


'reportable' => false,
    'massupdate' => false,
);
$dictionary[$module]['relationships'][strtolower($table_name) . '_email_addresses'] = array(
    'lhs_module' => $table_name,
    'lhs_table' => strtolower($table_name),
    'lhs_key' => 'id',
    'rhs_module' => 'EmailAddresses',
    'rhs_table' => 'email_addresses',
    'rhs_key' => 'id',
    'relationship_type' => 'many-to-many',
    'join_table' => 'email_addr_bean_rel',
    'join_key_lhs' => 'bean_id',


'join_key_lhs' => 'bean_id',
    'join_key_rhs' => 'email_address_id',
    'relationship_role_column' => 'bean_module',
    'relationship_role_column_value' => $table_name,
);
$dictionary[$module]['relationships'][strtolower($table_name) . '_email_addresses_primary'] = array(
    'lhs_module' => $table_name,
    'lhs_table' => strtolower($table_name),
    'lhs_key' => 'id',
    'rhs_module' => 'EmailAddresses',
    'rhs_table' => 'email_addresses',
    'rhs_key' => 'id',


'rhs_key' => 'id',
    'relationship_type' => 'many-to-many',
    'join_table' => 'email_addr_bean_rel',
    'join_key_lhs' => 'bean_id',
    'join_key_rhs' => 'email_address_id',
    'relationship_role_column' => 'bean_module',
    'relationship_role_column_value' => $module,
    'primary_flag_column' => 'primary_address',
);
Creating a Custom Bean and Modifying the Save Function
Next step, you will need to create a custom bean that extends from the stock bean. In our example, we choose the Opportunities.Â
./custom/modules/Opportunities/CustomOpportunity.php
  <?php
class CustomOpportunity extends Opportunity


<?php
class CustomOpportunity extends Opportunity
{
    /**
     * Constructor
     */
    public function __construct()
    {
        parent::__construct();
        $this->emailAddress = BeanFactory::newBean('EmailAddresses');
    }
    /**
     * Populate email address fields here instead of retrieve() so that they are properly available for logic hooks
     *
     * @see parent::fill_in_relationship_fields()
     */
    public function fill_in_relationship_fields()
    {
        parent::fill_in_relationship_fields();
        $this->emailAddress->handleLegacyRetrieve($this);
    }
    /**
     * @see parent::get_list_view_data()
     */
    public function get_list_view_data()
    {


*/
    public function get_list_view_data()
    {
        global $current_user;
        $temp_array = $this->get_list_view_array();
        $temp_array['EMAIL'] = $this->emailAddress->getPrimaryAddress($this);
        // Fill in the email1 field only if the user has access to it
        // This is a special case, because getEmailLink() uses email1 field for making the link
        // Otherwise get_list_view_data() shouldn't set any fields except fill the template data
        if ($this->ACLFieldAccess('email1', 'read')) {
            $this->email1 = $temp_array['EMAIL'];
        }


$this->email1 = $temp_array['EMAIL'];
        }
        $temp_array['EMAIL_LINK'] = $current_user->getEmailLink('email1', $this, '', '', 'ListView');
        return $temp_array;
    }
    /**
     *
     * @see parent::save()
     */
    public function save($check_notify = false)
    {
        if (static::inOperation('saving_related')) {
            parent::save($check_notify);
            return $this;
        }

        $ori_in_workflow = empty($this->in_workflow) ? false : true;
        $this->emailAddress->handleLegacySave($this, $this->module_dir);
        parent::save($check_notify);


parent::save($check_notify);
        $override_email = array();
        if (!empty($this->email1_set_in_workflow)) {
            $override_email['emailAddress0'] = $this->email1_set_in_workflow;
        }
        if (!empty($this->email2_set_in_workflow)) {
            $override_email['emailAddress1'] = $this->email2_set_in_workflow;
        }
        if (!isset($this->in_workflow)) {
            $this->in_workflow = false;
        }
        if ($ori_in_workflow === false || !empty($override_email)) {


$result = $this->emailAddress->save($this->id, $this->module_dir, $override_email, '', '', '', '',
                $this->in_workflow);
        }
        return $this;
    }
}
Replacing the Stock Bean with a Custom Bean
Once you created the custom bean, you will need to show Sugar to use Custom bean. To do that you will create ./custom/modules/<modulename>/<modulename>.php and update the class name and file path in the $beanList and $beanFiles. (See: Modules $beanListÂ and Customizing Core SugarBeans)
custom/Extension/application/Ext/Include/customOpportunities.php
<?php
$objectList['Opportunities'] = 'Opportunity';


<?php
$objectList['Opportunities'] = 'Opportunity';
$beanList['Opportunities'] = 'CustomOpportunity';
$beanFiles['CustomOpportunity'] = 'custom/modules/Opportunities/CustomOpportunity.php';
Once you have created these files, you will need to navigate Admin > Repairs and perform Quick Repair and Rebuild.
Adding the Emails Field to the Record View
At the stage, you have already created the field and updated the bean. The last step is placing the email field to record view. You can perform this step using Studio. Once you navigate to Studio you will find an Email field on the left column where available fields are listed. After adding the field the record views of Opportunities will look like this:
./custom/modules/Opportunities/clients/base/views/record/record.php
<?php


<?php
$viewdefs['Opportunities'] =
array (
  'base' =>
  array (
    'view' =>
    array (
      'record' =>
      array (
        'buttons' =>
        array (
          0 =>
          array (
            'type' => 'button',
            'name' => 'cancel_button',
            'label' => 'LBL_CANCEL_BUTTON_LABEL',
            'css_class' => 'btn-invisible btn-link',
            'showOn' => 'edit',
            'events' =>
            array (
              'click' => 'button:cancel_button:click',
            ),
          ),
          1 =>
          array (
            'type' => 'rowaction',


1 =>
          array (
            'type' => 'rowaction',
            'event' => 'button:save_button:click',
            'name' => 'save_button',
            'label' => 'LBL_SAVE_BUTTON_LABEL',
            'css_class' => 'btn btn-primary',
            'showOn' => 'edit',
            'acl_action' => 'edit',
          ),
          2 =>
          array (
            'type' => 'actiondropdown',
            'name' => 'main_dropdown',
            'primary' => true,
            'showOn' => 'view',
            'buttons' =>
            array (
              0 =>
              array (
                'type' => 'rowaction',
                'event' => 'button:edit_button:click',


'event' => 'button:edit_button:click',
                'name' => 'edit_button',
                'label' => 'LBL_EDIT_BUTTON_LABEL',
                'acl_action' => 'edit',
              ),
              1 =>
              array (
                'type' => 'shareaction',
                'name' => 'share',
                'label' => 'LBL_RECORD_SHARE_BUTTON',
                'acl_action' => 'view',
              ),
              2 =>
              array (
                'type' => 'pdfaction',
                'name' => 'download-pdf',
                'label' => 'LBL_PDF_VIEW',
                'action' => 'download',
                'acl_action' => 'view',
              ),
              3 =>
              array (


),
              3 =>
              array (
                'type' => 'pdfaction',
                'name' => 'email-pdf',
                'label' => 'LBL_PDF_EMAIL',
                'action' => 'email',
                'acl_action' => 'view',
              ),
              4 =>
              array (
                'type' => 'divider',
              ),
              5 =>
              array (
                'type' => 'rowaction',
                'event' => 'button:find_duplicates_button:click',
                'name' => 'find_duplicates_button',
                'label' => 'LBL_DUP_MERGE',
                'acl_action' => 'edit',
              ),
              6 =>
              array (
                'type' => 'rowaction',


6 =>
              array (
                'type' => 'rowaction',
                'event' => 'button:duplicate_button:click',
                'name' => 'duplicate_button',
                'label' => 'LBL_DUPLICATE_BUTTON_LABEL',
                'acl_module' => 'Opportunities',
                'acl_action' => 'create',
              ),
              7 =>
              array (
                'type' => 'rowaction',
                'event' => 'button:historical_summary_button:click',
                'name' => 'historical_summary_button',
                'label' => 'LBL_HISTORICAL_SUMMARY',
                'acl_action' => 'view',
              ),
              8 =>
              array (


),
              8 =>
              array (
                'type' => 'rowaction',
                'event' => 'button:audit_button:click',
                'name' => 'audit_button',
                'label' => 'LNK_VIEW_CHANGE_LOG',
                'acl_action' => 'view',
              ),
              9 =>
              array (
                'type' => 'divider',
              ),
              10 =>
              array (
                'type' => 'rowaction',
                'event' => 'button:delete_button:click',
                'name' => 'delete_button',
                'label' => 'LBL_DELETE_BUTTON_LABEL',
                'acl_action' => 'delete',
              ),
            ),
          ),
          3 =>
          array (


),
            ),
          ),
          3 =>
          array (
            'name' => 'sidebar_toggle',
            'type' => 'sidebartoggle',
          ),
        ),
        'panels' =>
        array (
          0 =>
          array (
            'name' => 'panel_header',
            'header' => true,
            'fields' =>
            array (
              0 =>
              array (
                'name' => 'picture',
                'type' => 'avatar',
                'size' => 'large',
                'dismiss_label' => true,
                'readonly' => true,
              ),
              1 =>
              array (
                'name' => 'name',
                'related_fields' =>
                array (


'related_fields' =>
                array (
                  0 => 'total_revenue_line_items',
                  1 => 'closed_revenue_line_items',
                  2 => 'included_revenue_line_items',
                ),
              ),
              2 =>
              array (
                'name' => 'favorite',
                'label' => 'LBL_FAVORITE',
                'type' => 'favorite',
                'dismiss_label' => true,
              ),
              3 =>
              array (
                'name' => 'follow',
                'label' => 'LBL_FOLLOW',
                'type' => 'follow',
                'readonly' => true,
                'dismiss_label' => true,
              ),
            ),
          ),
          1 =>
          array (


),
            ),
          ),
          1 =>
          array (
            'name' => 'panel_body',
            'label' => 'LBL_RECORD_BODY',
            'columns' => 2,
            'labels' => true,
            'labelsOnTop' => true,
            'placeholders' => true,
            'newTab' => false,
            'panelDefault' => 'expanded',
            'fields' =>
            array (
              0 =>
              array (
                'name' => 'account_name',
                'related_fields' =>
                array (
                  0 => 'account_id',
                ),
              ),
              1 =>
              array (
                'name' => 'date_closed',
                'related_fields' =>
                array (


'related_fields' =>
                array (
                  0 => 'date_closed_timestamp',
                ),
              ),
              2 =>
              array (
                'name' => 'amount',
                'type' => 'currency',
                'label' => 'LBL_LIKELY',
                'related_fields' =>
                array (
                  0 => 'amount',
                  1 => 'currency_id',
                  2 => 'base_rate',
                ),
                'currency_field' => 'currency_id',
                'base_rate_field' => 'base_rate',
                'span' => 12,
              ),
              3 =>
              array (
                'name' => 'best_case',
                'type' => 'currency',
                'label' => 'LBL_BEST',


'label' => 'LBL_BEST',
                'related_fields' =>
                array (
                  0 => 'best_case',
                  1 => 'currency_id',
                  2 => 'base_rate',
                ),
                'currency_field' => 'currency_id',
                'base_rate_field' => 'base_rate',
              ),
              4 =>
              array (
                'name' => 'worst_case',
                'type' => 'currency',
                'label' => 'LBL_WORST',
                'related_fields' =>
                array (
                  0 => 'worst_case',
                  1 => 'currency_id',
                  2 => 'base_rate',
                ),
                'currency_field' => 'currency_id',
                'base_rate_field' => 'base_rate',


'base_rate_field' => 'base_rate',
              ),
              5 =>
              array (
                'name' => 'tag',
                'span' => 6,
              ),
              6 =>
              array (
                'name' => 'sales_status',
                'readonly' => true,
                'studio' => true,
                'label' => 'LBL_SALES_STATUS',
                'span' => 6,
              ),
              7 =>
              array (
                'name' => 'email',
                'studio' =>
                array (
                  'visible' => true,
                  'searchview' => true,
                  'editview' => true,
                  'editField' => true,
                ),
                'label' => 'LBL_EMAIL_ADDRESS',


),
                'label' => 'LBL_EMAIL_ADDRESS',
                'span' => 12,
              ),
            ),
          ),
          2 =>
          array (
            'name' => 'panel_hidden',
            'label' => 'LBL_RECORD_SHOWMORE',
            'hide' => true,
            'labelsOnTop' => true,
            'placeholders' => true,
            'columns' => 2,
            'newTab' => false,
            'panelDefault' => 'expanded',
            'fields' =>
            array (
              0 => 'next_step',
              1 => 'opportunity_type',
              2 => 'lead_source',
              3 => 'campaign_name',
              4 =>
              array (
                'name' => 'description',


4 =>
              array (
                'name' => 'description',
                'span' => 12,
              ),
              5 => 'assigned_user_name',
              6 => 'team_name',
              7 =>
              array (
                'name' => 'date_entered_by',
                'readonly' => true,
                'type' => 'fieldset',
                'label' => 'LBL_DATE_ENTERED',
                'fields' =>
                array (
                  0 =>
                  array (
                    'name' => 'date_entered',
                  ),
                  1 =>
                  array (
                    'type' => 'label',
                    'default_value' => 'LBL_BY',
                  ),
                  2 =>
                  array (


),
                  2 =>
                  array (
                    'name' => 'created_by_name',
                  ),
                ),
              ),
              8 =>
              array (
                'name' => 'date_modified_by',
                'readonly' => true,
                'type' => 'fieldset',
                'label' => 'LBL_DATE_MODIFIED',
                'fields' =>
                array (
                  0 =>
                  array (
                    'name' => 'date_modified',
                  ),
                  1 =>
                  array (
                    'type' => 'label',
                    'default_value' => 'LBL_BY',
                  ),
                  2 =>
                  array (
                    'name' => 'modified_by_name',
                  ),
                ),
              ),
            ),
          ),
        ),


),
                ),
              ),
            ),
          ),
        ),
        'templateMeta' =>
        array (
          'useTabs' => false,
        ),
      ),
    ),
  ),
);
Once the files are in place, navigate to Admin > Repair > Quick Repair & Rebuild to regenerate the metadata.
  	Last modified: 2023-02-03 21:04:03


Adding Buttons to the Record View
Overview
This example explains how to create additional buttons on the record view and add events.Â We will extend and override the stock Accounts record view to add a custom button. The custom button will be called "Validate Postal Code" and ping the Zippopotamus REST service to validate the records billing state and postal code.
Steps To Complete
This tutorial requires the following steps, which are explained in the sections below:
Defining the Metadata
Adding Custom Buttons
Defining the Button Label
Extending and Overriding the Controller
Defining the Metadata


Extending and Overriding the Controller
Defining the Metadata
To add a button to the record view, you will first need to create the custom metadata for the view if it doesn't exist. You can easily do this by opening and saving your modules record layout in studio. Depending on your module, the path will then be ./custom/modules/<module>/clients/base/views/record/record.php. Once your file is in place, you will need to copy the button array from ./clients/base/views/record/record.php and add it to the $viewdefs['<module>']['base']['view']['record'] portion of your metadata array. An example of the button array is shown below:
'buttons' => array(
        array(
            'type' => 'button',
            'name' => 'cancel_button',


'name' => 'cancel_button',
            'label' => 'LBL_CANCEL_BUTTON_LABEL',
            'css_class' => 'btn-invisible btn-link',
            'showOn' => 'edit',
        ),
        array(
            'type' => 'rowaction',
            'event' => 'button:save_button:click',
            'name' => 'save_button',
            'label' => 'LBL_SAVE_BUTTON_LABEL',
            'css_class' => 'btn btn-primary',
            'showOn' => 'edit',
            'acl_action' => 'edit',
        ),
        array(
            'type' => 'actiondropdown',
            'name' => 'main_dropdown',
            'primary' => true,


'name' => 'main_dropdown',
            'primary' => true,
            'showOn' => 'view',
            'buttons' => array(
                array(
                    'type' => 'rowaction',
                    'event' => 'button:edit_button:click',
                    'name' => 'edit_button',
                    'label' => 'LBL_EDIT_BUTTON_LABEL',
                    'acl_action' => 'edit',
                ),
                array(
                    'type' => 'shareaction',
                    'name' => 'share',
                    'label' => 'LBL_RECORD_SHARE_BUTTON',
                    'acl_action' => 'view',
                ),
                array(
                    'type' => 'pdfaction',
                    'name' => 'download-pdf',


'name' => 'download-pdf',
                    'label' => 'LBL_PDF_VIEW',
                    'action' => 'download',
                    'acl_action' => 'view',
                ),
                array(
                    'type' => 'pdfaction',
                    'name' => 'email-pdf',
                    'label' => 'LBL_PDF_EMAIL',
                    'action' => 'email',
                    'acl_action' => 'view',
                ),
                array(
                    'type' => 'divider',
                ),
                array(
                    'type' => 'rowaction',
                    'event' => 'button:find_duplicates_button:click',
                    'name' => 'find_duplicates_button',
                    'label' => 'LBL_DUP_MERGE',


'label' => 'LBL_DUP_MERGE',
                    'acl_action' => 'edit',
                ),
                array(
                    'type' => 'rowaction',
                    'event' => 'button:duplicate_button:click',
                    'name' => 'duplicate_button',
                    'label' => 'LBL_DUPLICATE_BUTTON_LABEL',
                    'acl_module' => $module,
                ),
                array(
                    'type' => 'rowaction',
                    'event' => 'button:audit_button:click',
                    'name' => 'audit_button',
                    'label' => 'LNK_VIEW_CHANGE_LOG',
                    'acl_action' => 'view',
                ),
                array(
                    'type' => 'divider',
                ),


),
                array(
                    'type' => 'divider',
                ),
                array(
                    'type' => 'rowaction',
                    'event' => 'button:delete_button:click',
                    'name' => 'delete_button',
                    'label' => 'LBL_DELETE_BUTTON_LABEL',
                    'acl_action' => 'delete',
                ),
            ),
        ),
        array(
            'name' => 'sidebar_toggle',
            'type' => 'sidebartoggle',
        ),
    ),
Note: When copying this array into your metadata, you will need to replaceÂ $module with the text string of your module's name.
For standard button types, the button definitions will contain the following properties:
Property
Potential Values
Description
type
button, rowaction, shareaction, actiondropdown


Potential Values
Description
type
button, rowaction, shareaction, actiondropdown
The widget type
name
Â
The name of the button
label
Â
The label string key for the display text of the button
css_class
Â
The CSS class to append to the button
showOn
edit, view
The ACL action of the button
For this example, we will addÂ theÂ custom button to the main dropdown. For actiondropdown types, thereÂ is an additional buttons array for you to specify the dropdown list of buttons. The button definitions in this array will contain the following properties:
Property
Potential Values
Description
type
button, rowaction, shareaction, actiondropdown
The widget type; Most custom buttons are 'rowaction'
event
button:button_name:click
The event name of the button
name
Â


button:button_name:click
The event name of the button
name
Â
The name of the button
label
Â
The label string key for the display text of the button
acl_action
Â edit, view
The ACL action of the button
Adding Custom Buttons
For this example, modify the accounts' metadata to add the button definition to main_dropdown:
array(
    'type' => 'rowaction',
    'event' => 'button:validate_postal_code:click',
    'name' => 'validate_postal_code',
    'label' => 'LBL_VALIDATE_POSTAL_CODE',
    'acl_action' => 'view',
),
A full example is shown below:
./custom/modules/Accounts/clients/base/views/record/record.php
<?php


<?php
$viewdefs['Accounts'] =
array (
  'base' =>
  array (
    'view' =>
    array (
      'record' =>
      array (
        'buttons' =>
        array (
          0 =>
          array (
            'type' => 'button',
            'name' => 'cancel_button',
            'label' => 'LBL_CANCEL_BUTTON_LABEL',
            'css_class' => 'btn-invisible btn-link',
            'showOn' => 'edit',
          ),
          1 =>
          array (
            'type' => 'rowaction',
            'event' => 'button:save_button:click',
            'name' => 'save_button',
            'label' => 'LBL_SAVE_BUTTON_LABEL',


'label' => 'LBL_SAVE_BUTTON_LABEL',
            'css_class' => 'btn btn-primary',
            'showOn' => 'edit',
            'acl_action' => 'edit',
          ),
          2 =>
          array (
            'type' => 'actiondropdown',
            'name' => 'main_dropdown',
            'primary' => true,
            'showOn' => 'view',
            'buttons' =>
            array (
              0 =>
              array (
                'type' => 'rowaction',
                'event' => 'button:edit_button:click',
                'name' => 'edit_button',
                'label' => 'LBL_EDIT_BUTTON_LABEL',
                'acl_action' => 'edit',
              ),
              1 =>


'acl_action' => 'edit',
              ),
              1 =>
              array (
                'type' => 'shareaction',
                'name' => 'share',
                'label' => 'LBL_RECORD_SHARE_BUTTON',
                'acl_action' => 'view',
              ),
              2 =>
              array (
                'type' => 'rowaction',
                'event' => 'button:validate_postal_code:click',
                'name' => 'validate_postal_code',
                'label' => 'LBL_VALIDATE_POSTAL_CODE',
                'acl_action' => 'view',
              ),
              3 =>
              array (
                'type' => 'divider',
              ),
              4 =>
              array (
                'type' => 'rowaction',


4 =>
              array (
                'type' => 'rowaction',
                'event' => 'button:duplicate_button:click',
                'name' => 'duplicate_button',
                'label' => 'LBL_DUPLICATE_BUTTON_LABEL',
                'acl_module' => 'Accounts',
              ),
              5 =>
              array (
                'type' => 'rowaction',
                'event' => 'button:audit_button:click',
                'name' => 'audit_button',
                'label' => 'LNK_VIEW_CHANGE_LOG',
                'acl_action' => 'view',
              ),
              6 =>
              array (
                'type' => 'divider',
              ),
              7 =>
              array (
                'type' => 'rowaction',


7 =>
              array (
                'type' => 'rowaction',
                'event' => 'button:delete_button:click',
                'name' => 'delete_button',
                'label' => 'LBL_DELETE_BUTTON_LABEL',
                'acl_action' => 'delete',
              ),
            ),
          ),
          3 =>
          array (
            'name' => 'sidebar_toggle',
            'type' => 'sidebartoggle',
          ),
        ),
        'panels' =>
        array (
          0 =>
          array (
            'name' => 'panel_header',
            'header' => true,
            'fields' =>
            array (
              0 =>
              array (
                'name' => 'picture',
                'type' => 'avatar',


'name' => 'picture',
                'type' => 'avatar',
                'width' => 42,
                'height' => 42,
                'dismiss_label' => true,
                'readonly' => true,
              ),
              1 => 'name',
              2 =>
              array (
                'name' => 'favorite',
                'label' => 'LBL_FAVORITE',
                'type' => 'favorite',
                'dismiss_label' => true,
              ),
              3 =>
              array (
                'name' => 'follow',
                'label' => 'LBL_FOLLOW',
                'type' => 'follow',
                'readonly' => true,
                'dismiss_label' => true,
              ),
            ),
          ),
          1 =>
          array (


),
            ),
          ),
          1 =>
          array (
            'name' => 'panel_body',
            'columns' => 2,
            'labelsOnTop' => true,
            'placeholders' => true,
            'fields' =>
            array (
              0 => 'website',
              1 => 'industry',
              2 => 'parent_name',
              3 => 'account_type',
              4 => 'assigned_user_name',
              5 => 'phone_office',
            ),
          ),
          2 =>
          array (
            'name' => 'panel_hidden',
            'hide' => true,
            'columns' => 2,
            'labelsOnTop' => true,
            'placeholders' => true,
            'fields' =>
            array (
              0 =>


'fields' =>
            array (
              0 =>
              array (
                'name' => 'fieldset_address',
                'type' => 'fieldset',
                'css_class' => 'address',
                'label' => 'LBL_BILLING_ADDRESS',
                'fields' =>
                array (
                  0 =>
                  array (
                    'name' => 'billing_address_street',
                    'css_class' => 'address_street',
                    'placeholder' => 'LBL_BILLING_ADDRESS_STREET',
                  ),
                  1 =>
                  array (
                    'name' => 'billing_address_city',
                    'css_class' => 'address_city',
                    'placeholder' => 'LBL_BILLING_ADDRESS_CITY',
                  ),


),
                  2 =>
                  array (
                    'name' => 'billing_address_state',
                    'css_class' => 'address_state',
                    'placeholder' => 'LBL_BILLING_ADDRESS_STATE',
                  ),
                  3 =>
                  array (
                    'name' => 'billing_address_postalcode',
                    'css_class' => 'address_zip',
                    'placeholder' => 'LBL_BILLING_ADDRESS_POSTALCODE',
                  ),
                  4 =>
                  array (
                    'name' => 'billing_address_country',
                    'css_class' => 'address_country',
                    'placeholder' => 'LBL_BILLING_ADDRESS_COUNTRY',
                  ),
                ),
              ),
              1 =>
              array (


),
                ),
              ),
              1 =>
              array (
                'name' => 'fieldset_shipping_address',
                'type' => 'fieldset',
                'css_class' => 'address',
                'label' => 'LBL_SHIPPING_ADDRESS',
                'fields' =>
                array (
                  0 =>
                  array (
                    'name' => 'shipping_address_street',
                    'css_class' => 'address_street',
                    'placeholder' => 'LBL_SHIPPING_ADDRESS_STREET',
                  ),
                  1 =>
                  array (
                    'name' => 'shipping_address_city',
                    'css_class' => 'address_city',
                    'placeholder' => 'LBL_SHIPPING_ADDRESS_CITY',


),
                  2 =>
                  array (
                    'name' => 'shipping_address_state',
                    'css_class' => 'address_state',
                    'placeholder' => 'LBL_SHIPPING_ADDRESS_STATE',
                  ),
                  3 =>
                  array (
                    'name' => 'shipping_address_postalcode',
                    'css_class' => 'address_zip',
                    'placeholder' => 'LBL_SHIPPING_ADDRESS_POSTALCODE',
                  ),
                  4 =>
                  array (
                    'name' => 'shipping_address_country',
                    'css_class' => 'address_country',
                    'placeholder' => 'LBL_SHIPPING_ADDRESS_COUNTRY',
                  ),
                  5 =>
                  array (


),
                  5 =>
                  array (
                    'name' => 'copy',
                    'label' => 'NTC_COPY_BILLING_ADDRESS',
                    'type' => 'copy',
                    'mapping' =>
                    array (
                      'billing_address_street' => 'shipping_address_street',
                      'billing_address_city' => 'shipping_address_city',
                      'billing_address_state' => 'shipping_address_state',
                      'billing_address_postalcode' => 'shipping_address_postalcode',
                      'billing_address_country' => 'shipping_address_country',
                    ),
                  ),
                ),
              ),
              2 =>
              array (
                'name' => 'phone_alternate',


2 =>
              array (
                'name' => 'phone_alternate',
                'label' => 'LBL_OTHER_PHONE',
              ),
              3 => 'email',
              4 => 'phone_fax',
              5 => 'campaign_name',
              6 =>
              array (
                'name' => 'description',
                'span' => 12,
              ),
              7 => 'sic_code',
              8 => 'ticker_symbol',
              9 => 'annual_revenue',
              10 => 'employees',
              11 => 'ownership',
              12 => 'rating',
              13 =>
              array (
                'name' => 'date_entered_by',
                'readonly' => true,
                'type' => 'fieldset',


'readonly' => true,
                'type' => 'fieldset',
                'label' => 'LBL_DATE_ENTERED',
                'fields' =>
                array (
                  0 =>
                  array (
                    'name' => 'date_entered',
                  ),
                  1 =>
                  array (
                    'type' => 'label',
                    'default_value' => 'LBL_BY',
                  ),
                  2 =>
                  array (
                    'name' => 'created_by_name',
                  ),
                ),
              ),
              14 => 'team_name',
              15 =>
              array (
                'name' => 'date_modified_by',
                'readonly' => true,
                'type' => 'fieldset',
                'label' => 'LBL_DATE_MODIFIED',


'label' => 'LBL_DATE_MODIFIED',
                'fields' =>
                array (
                  0 =>
                  array (
                    'name' => 'date_modified',
                  ),
                  1 =>
                  array (
                    'type' => 'label',
                    'default_value' => 'LBL_BY',
                  ),
                  2 =>
                  array (
                    'name' => 'modified_by_name',
                  ),
                ),
                'span' => 12,
              ),
            ),
          ),
        ),
        'templateMeta' =>
        array (
          'useTabs' => false,
          'tabDefs' =>
          array (
            'LBL_RECORD_BODY' =>
            array (
              'newTab' => false,
              'panelDefault' => 'expanded',


'newTab' => false,
              'panelDefault' => 'expanded',
            ),
            'LBL_RECORD_SHOWMORE' =>
            array (
              'newTab' => false,
              'panelDefault' => 'expanded',
            ),
          ),
        ),
      ),
    ),
  ),
);
Defining the Button Label
Next, define the label for the button:
./custom/Extension/modules/Accounts/Ext/Language/en_us.validatePostalCode.php
<?php
$mod_strings['LBL_VALIDATE_POSTAL_CODE'] = 'Validate Postal Code';
Extending and Overriding the Controller
Once the button has been added to the metadata, extend and override the record view controller:


Once the button has been added to the metadata, extend and override the record view controller:
./custom/modules/Accounts/clients/base/views/record/record.js
({
    extendsFrom: 'RecordView',
    zipJSON: {},
    initialize: function (options) {
        this._super('initialize', [options]);
        //add listener for custom button
        this.context.on('button:validate_postal_code:click', this.validate_postal_code, this);
    },
    validate_postal_code: function() {
        //example of getting field data from current record
        var AcctID = this.model.get('id');
        var currentCity = this.model.get('billing_address_city');
        var currentZip = this.model.get('billing_address_postalcode');


//jQuery AJAX call to Zippopotamus REST API
        $.ajax({
            url: 'http://api.zippopotam.us/us/' + currentZip,
            success: function(data) {
                    this.zipJSON = data;
                    var city = this.zipJSON.places[0]['place name'];
                    if (city === currentCity)
                    {
                        app.alert.show('address-ok', {
                            level: 'success',
                            messages: 'City and Zipcode match.',
                            autoClose: true
                        });
                    }
                    else
                    {
                        app.alert.show('address-ok', {
                            level: 'error',
                            messages: 'City and Zipcode do not match.',
                            autoClose: false
                        });
                    }
                }


autoClose: false
                        });
                    }
                }
            });
    }
})
Once the files are in place, navigate to Admin > Repair > Quick Repair and Rebuild.
Adding Buttons without Overriding View Controllers
Sometimes your customization might need to add the same buttons to multiple views, but you don't want to overwrite other possible customizations already on the view. The following will walk through creating a custom button like the above example, add adding the buttons to views without overriding the module view controllers.
Create a Custom Button
Buttons typically come in two forms, a standard button and a 'rowaction' in an Action Menu. We can create two buttons to handle both scenarios.
./clients/base/fields/zipcode-check-button/zipcode-check-button.js
/**
 * Zipcode Check button will check if zipcode matches city field
 *


/**
 * Zipcode Check button will check if zipcode matches city field
 *
 * @class View.Fields.Base.ZipcodeCheckButtonField
 * @alias SUGAR.App.view.fields.BaseZipcodeCheckButtonField
 * @extends View.Fields.Base.ButtonField
 */
({
    extendsFrom: 'ButtonField',
    defaultZipCodeField: 'billing_address_postalcode',
    defaultCityField: 'billing_address_city',
    /**
     * @inheritdoc
     */
    initialize: function(options) {
        options.def.events = _.extend({}, options.def.events, {
            'click .zip-check-btn': 'validatePostalCode'
        });
        this._super('initialize', [options]);


});
        this._super('initialize', [options]);
        this.def.zip_code_field = _.isEmpty(this.def.zip_code_field)?this.defaultZipCodeField:this.def.zip_code_field;
        this.def.city_field = _.isEmpty(this.def.city_field)?this.defaultCityField:this.def.city_field;
    },
    validatePostalCode: function(evt) {
        var currentCity = this.model.get(this.def.city_field);
        var currentZip = this.model.get(this.def.zip_code_field);
        //jQuery AJAX call to Zippopotamus REST API
        $.ajax({
            url: 'http://api.zippopotam.us/us/' + currentZip,


success: function(data) {
                this.zipJSON = data;
                var city = this.zipJSON.places[0]['place name'];
                if (city === currentCity)
                {
                    app.alert.show('address-ok', {
                        level: 'success',
                        messages: 'City and Zipcode match.',
                        autoClose: true
                    });
                }
                else
                {
                    app.alert.show('address-ok', {
                        level: 'error',
                        messages: 'City and Zipcode do not match.',
                        autoClose: false
                    });
                }
            }
        });
    }
})
./clients/base/fields/zipcode-check-rowaction/zipcode-check-rowaction.js
/**
 * Zipcode Check button will check if zipcode matches city field
 *


/**
 * Zipcode Check button will check if zipcode matches city field
 *
 * @class View.Fields.Base.ZipcodeCheckRowactionField
 * @alias SUGAR.App.view.fields.BaseZipcodeCheckRowactionField
 * @extends View.Fields.Base.RowactionField
 */
({
    extendsFrom: 'RowactionField',
    defaultZipCodeField: 'billing_address_postalcode',
    defaultCityField: 'billing_address_city',
    /**
     * @inheritdoc
     */
    initialize: function(options) {
        this._super('initialize', [options]);
        this.def.zip_code_field = _.isEmpty(this.def.zip_code_field)?this.defaultZipCodeField:this.def.zip_code_field;


this.def.city_field = _.isEmpty(this.def.city_field)?this.defaultCityField:this.def.city_field;
    },
    /**
     * Rowaction fields have a default event which calls rowActionSelect
     */
    rowActionSelect: function(evt) {
        this.validatePostalCode(evt);
    },
    validatePostalCode: function(evt) {
        var currentCity = this.model.get(this.def.city_field);
        var currentZip = this.model.get(this.def.zip_code_field);
        //jQuery AJAX call to Zippopotamus REST API
        $.ajax({
            url: 'http://api.zippopotam.us/us/' + currentZip,
            success: function(data) {


success: function(data) {
                this.zipJSON = data;
                var city = this.zipJSON.places[0]['place name'];
                if (city === currentCity)
                {
                    app.alert.show('address-check-msg', {
                        level: 'success',
                        messages: 'City and Zipcode match.',
                        autoClose: true
                    });
                }
                else
                {
                    app.alert.show('address-check-msg', {
                        level: 'error',
                        messages: 'City and Zipcode do not match.',
                        autoClose: false
                    });
                }
            }
        });
    }
})


});
                }
            }
        });
    }
})
Along with the JavaScript controllers for the buttons, you can copy over the detail.hbs and edit.hbs from the base controllers that each button isÂ extended from into each folder. The folder structure will look as follows:
./clients/base/fields/zipcode-check-button/
zipcode-check-button.js
detail.hbs
edit.hbs
./clients/base/fields/zipcode-check-rowaction/
zipcode-check-rowaction.js
detail.hbs
edit.hbs
Â In the Handlebar files, you should add a custom CSSÂ class called zip-check-btn to each of the layouts, as a way to find the elements on the page. This is also used for the ZipcodeCheckButton to isolate the event trigger. Example below:


./cilents/base/fields/zipcode-check-button/edit.hbs
<a href="{{#if fullRoute}}#{{fullRoute}}{{else}}{{#if def.route}}#{{buildRoute context=context model=model action=def.route.action}}{{else}}javascript:void(0);{{/if}}{{/if}}"
    class="btn{{#if def.primary}} btn-primary{{/if}} zip-check-btn"
    {{#if def.tooltip}}
        rel="tooltip"
        data-placement="bottom"
        title="{{str def.tooltip module}}"
    {{/if}}
    {{#if ariaLabel}}aria-label="{{ariaLabel}}"{{/if}}


role="button" tabindex="{{tabIndex}}" name="{{name}}">{{#if def.icon}} {{/if}}{{label}}
Once we have the button controllers and the templates setup, we can add the buttons to the View metadata for particular modules.
Appending Buttons to Metadata
After creating your buttons, you will need to append the buttons to the views metadata. For this, you can use the Extension Framework. The following examples will add a button to the action menu on the Accounts record view and a button to the Contacts record view. Please note that this example assumes that you have created theÂ rowaction buttonÂ above.
./custom/Extension/modules/Accounts/Ext/clients/base/views/record/addZipCodeCheckRowaction.php


$buttons = isset($viewdefs['Accounts']['base']['view']['record']['buttons'])?$viewdefs['Accounts']['base']['view']['record']['buttons']:array();
if (!empty($buttons)) {
    foreach ($buttons as $key => $button) {
        if ($button['type'] == 'actiondropdown' && $button['name'] == 'main_dropdown') {
            $viewdefs['Accounts']['base']['view']['record']['buttons'][$key]['buttons'][] = array(
                'type' => 'divider',
            );


'type' => 'divider',
            );
            $viewdefs['Accounts']['base']['view']['record']['buttons'][$key]['buttons'][] = array(
                'type' => 'zipcode-check-rowaction',
                'event' => 'button:zipcode_check:click',
                'name' => 'zipcode_check_button',
                'label' => 'LBL_ZIPCODE_CHECK_BUTTON_LABEL',
                'acl_action' => 'edit',
                'showOn' => 'view',
            );
            break;
        }
    }
}
./custom/Extension/modules/Contacts/Ext/clients/base/views/record/addZipCodeCheckButton.php


$buttons = isset($viewdefs['Contacts']['base']['view']['record']['buttons'])?$viewdefs['Contacts']['base']['view']['record']['buttons']:array();
$zipCodeButton = array (
    'type' => 'zipcode-check-button',
    'event' => 'button:zipcode_check:click',
    'name' => 'zipcode_check_button',
    'label' => 'LBL_ZIPCODE_CHECK_BUTTON_LABEL',
    'acl_action' => 'edit',
    'zip_code_field' => 'primary_address_postalcode',
    'city_field' => 'primary_address_city'
);
if (!empty($buttons)){


);
if (!empty($buttons)){
    foreach($buttons as $key => $button){
        if ($button['type'] == 'actiondropdown' && $button['name'] == 'main_dropdown'){
            //Get everything from this point down
            $slicedBtns = array_slice($viewdefs['Contacts']['base']['view']['record']['buttons'],$key);
            //Remove everything from this point down
            array_splice($viewdefs['Contacts']['base']['view']['record']['buttons'],$key);
            //Add Zip Code Button
            $viewdefs['Contacts']['base']['view']['record']['buttons'][] = $zipCodeButton;
            //Add back the buttons we removed


//Add back the buttons we removed
            foreach($slicedBtns as $oldButton){
                $viewdefs['Contacts']['base']['view']['record']['buttons'][] = $oldButton;
            }
            break;
        }
    }
} else {
    $viewdefs['Contacts']['base']['view']['record']['buttons'] = array(
        $zipCodeButton
    );
}
unset($zipCodeButton);
The last thing that is needed now, is to define the button's label.
Defining the Button Labels
Since the button was made to work globally on multiple modules, we can define the label at the application level.
./custom/Extension/application/Ext/Language/en_us.ZipCodeCheckButton.php


$app_strings['LBL_ZIPCODE_CHECK_BUTTON_LABEL'] = 'Verify Zip Code';
Â Once all files are in place, you can run a Quick Repair and Rebuild and the buttons will display and check the Zipcode fields for both the Contacts and Accounts record views without having to extend either modules RecordView controllers.
  	Last modified: 2023-02-03 21:04:03


Dynamically Hiding Subpanels Based on Record Values
Overview
This article will demonstrate how to dynamically hide subpanels that are dependent on record values by overriding the subpanels layout for a module. This example is for an account record where we will hide the Contacts subpanel when the account type is not 'Customer'.
Example
To accomplish this, we will create a JavaScript controller that extends SubpanelsLayout. This controller will then monitor the model and control the display of the subpanels.
./custom/modules/Accounts/clients/base/layouts/subpanels/subpanels.js
Removed script tag to: https://gist.github.com/3722771.js?file=view.detail.php
({
    extendsFrom: 'SubpanelsLayout',
    _hiddenSubpanels: [],
    initialize: function(options) {


_hiddenSubpanels: [],
    initialize: function(options) {
        this._super('initialize', [options]);
        this.registerModelEvents();
    },
    /**
     * Add the model change events for fields that determine when a subpanel should be hidden
     */
    registerModelEvents: function(){
        this.model.on('change:account_type',function(model) {
            var link = 'contacts';
            if (model.get('account_type') == "Customer"){
                this.unhideSubpanel(link);
            }else{
                this.hideSubpanel(link);
            }
        },this);
    },
    /**
     * Override showSubpanel to re-hide subpanels when outside changes occur, like reordering subpanel
     * @inheritdoc
     */


* @inheritdoc
     */
    showSubpanel: function(linkName) {
        this._super('showSubpanel',[linkName]);
        _.each(this._hiddenSubpanels, function(link) {
            this.hideSubpanel(link);
        },this);
    },
    /**
     * Helper for getting the Subpanel View for a specific link
     */
    getSubpanelByLink: function(link){
        return this._components.find(function(component){
           return component.context.get('link') === link;
        });
    },
    /**
     * Add to the _hiddenSubpanels array, and hide the subpanel
     */
    hideSubpanel: function(link){
        this._hiddenSubpanels.push(link);


this._hiddenSubpanels.push(link);
        var component = this.getSubpanelByLink(link);
        if (!_.isUndefined(component)){
            component.hide();
        }
        this._hiddenSubpanels = _.unique(this._hiddenSubpanels);
    },
    /**
     * Unhide the Subpanel and remove from _hiddenSubpanels array
     */
    unhideSubpanel: function(link){
        var index = this._hiddenSubpanels.findIndex(function(l){
            return l == link;
        });
        if (_.isUndefined(index)){
            delete this._hiddenSubpanels[index];
        }
        var component = this.getSubpanelByLink(link);
        if (!_.isUndefined(component)){
            component.show();
        }


component.show();
        }
    }
})
Once the file is in place, run a Quick Repair and Rebuild. The changes will then be reflected in the Accounts record view.
Related
  	Last modified: 2023-02-03 21:04:03


Changing the Default Module When Logging a New Call or Meeting
Overview
When creatingÂ aÂ callÂ or meeting directly from theÂ Calls orÂ Meetings moduleÂ in Sugar, the default module for the Related To field is Accounts. IfÂ your sales team frequently schedules calls and meetings related toÂ records from a module other than Accounts, it may make sense to adjustÂ the behavior so that the Related To field defaults to a more commonly used module.Â This article coversÂ how to change the default related module for calls and meetings in Sugar.
Note: This article pertains to Sugar versions 6.x and 7.x.
Prerequisites


Prerequisites
This change requires code-level customizations, which requiresÂ direct access to the server or familiarity with creating and installing module loadable packages. If you need assistance making these changes and already have a relationship with a Sugar partner, you can work with them to make this customization.Â If not, please refer to the Partner Page to find a reselling partner to help with your development needs.
Note: Sugar Sell Essentials customers do not have the ability to upload custom file packages to Sugar using Module Loader.
Steps to Complete


Steps to Complete
For this example, we will change theÂ default "Relates To" module to Contacts for records created in the Calls module and the Meetings module.Â Please note that,Â after completing these steps, the Related To field will still defaultÂ to Accounts when creating a call or meeting from a contact that has an account relationship or to theÂ current module from any other related module'sÂ record view.
Calls
Create the following directory path if it does not already exist from the root of your Sugar instance directory:./custom/Extension/modules/Calls/Ext/Vardefs/
Create a file in the directory called sugarfield_parent_type.php with the following contents:<?php
$dictionary['Call']['fields']['parent_type']['default'] = 'Contacts';


Save the file and ensure that the file has the correct permissionsÂ by referring to the Required File System Permissions on LinuxÂ and Required File System Permissions on Windows With IISÂ articles.
Log into Sugar as an administrator and navigate to Admin > Repair and perform a Quick Repair and Rebuild.
Once the quick repair completes, navigate to the Calls module and "Contact" should now be selected by default for the Related To field when logging a new call.
Meetings
Create the following directory path if it does not already exist from the root of your Sugar instance directory: ./custom/Extension/modules/Meetings/Ext/Vardefs/.
Create a file in the directory called sugarfield_parent_type.php with the following contents:<?php
$dictionary['Meeting']['fields']['parent_type']['default'] = 'Contacts';


Save the file and ensure that the file has the correct permissionsÂ by referring to the Required File System Permissions on LinuxÂ and Required File System Permissions on Windows With IISÂ articles.
Log into Sugar as an administrator and navigate to Admin > Repair and perform a Quick Repair and Rebuild.
Once the quick repair completes, navigate to the Meetings module and "Contact" should now be selected by default for the Related To field when scheduling a new meeting.
  	Last modified: 2023-02-03 21:04:03


Adding an Existing Note to an Email as Attachment
Overview
There may be times when you want to reuse a file attached to one Note record as an attachment for an Email, similar to the ability in the Compose Email view to add an attachment using 'Sugar Document'.
Key Concepts
There are two key things to understand when implementing this functionality:
You can not relate an existing Note record to an Email record. This will throw an Exception in the API as intended. A note that is used as an attachment can only exist once and can not act as an attachment across multiple emails.


You can create a new Note record that uses an existing Note's attached file. Doing so essentially requires setting theÂ upload_idÂ field of the new Note record to the id of the existing Note you want to reuse the file from, and setting theÂ file_sourceÂ field of the new Note to 'Notes'. In addition to needing to set the upload_id, you must also set the filename and name field.
The following examples demonstrate how to do this in three different contexts: server-side (SugarBean/PHP), client-side (sidecar/Javascript), and purely through the API. But all three contexts follow the same essential steps:
Fetch the original Note record;
Create a new Note record, setting the necessary fields from the original Note record;
Link the new Note record to the Email record via the appropriate 'attachments' link.
PHP


PHP
This example would be done server-side using the SugarBean object, similar to how you might interact with the records in a custom Logic Hook or Scheduler job.
$original_note_id = '4e226282-8158-11e8-a1b3-439fe19c087a';
$email_id = 'e3e058f4-7f11-11e8-ba11-fcdd97d61bbe';
// 1. Fetch Original Note:
$original_note = BeanFactory::retrieveBean('Notes', $original_note_id);
// 2.  Create a new note based on original note, setting upload_id, name, and filename:
$new_note = BeanFactory::newBean('Notes');
$new_note->upload_id = $original_note->getUploadId();


$new_note->upload_id = $original_note->getUploadId();
$new_note->file_source = 'Notes';
$new_note->filename = $original_note->filename;
$new_note->name = $original_note->filename;
// 3. Relate the new note to Email record using 'attachments' link:
$email = BeanFactory::retrieveBean('Emails', $email_id);
$email->load_relationship('attachments');
$email->attachments->add($email, $new_note);


$email->attachments->add($email, $new_note);
Note that in most contexts, such as a Logic Hook or a custom endpoint, you will not need to call save() on either the $new_note or $email for $new_note to be related as an attachment to $email and for $new_note to save. The $new_note record will save as part of being linked to $email.
Javascript
The following example is totally standalone within Sidecar, demonstrating how to fetch the existing Note using app.data.createBean andÂ bean.fetch(), create the new Note using app.data.createBean and setting the relevant fields, and then adding the new Note to the email records attachments_collection.
var original_note_id = '4e226282-8158-11e8-a1b3-439fe19c087a';


var email_id = 'e3e058f4-7f11-11e8-ba11-fcdd97d61bbe';
// 1. Fetch Original Note:
var original_note = app.data.createBean('Notes');
original_note.set('id', original_note_id);
original_note.fetch();
// 2.  Create a new note based on original note, setting upload_id, name, and filename:
var new_note = app.data.createBean('Notes', {
	_link: 'attachments',
	upload_id: original_note.get('id'),
	file_source: 'Notes',
	filename: original_note.get('filename'),
	name: original_note.get('filename'),


name: original_note.get('filename'),
	file_mime_type: original_note.get('file_mime_type'),
	file_size: original_note.get('file_size'),
	file_ext: original_note.get('file_ext'),
});
// 3.  Relate the new note to Email record using 'attachments_collection' link:
var email = app.data.createBean('Emails');
email.set('id', email_id);
email.fetch();
email.get('attachments_collection').add(new_note,{merge:true});
email.save();


email.save();
Note: In the above example, the fields file_mime_type, file_size, and file_ext are also set. This is assuming a context where the customization is adding the attachments to the Compose Email view. Setting these fields makes the new attachments look correct to the user before saving the Email, but these fields are otherwise set automatically on save. If extending the actual compose view for emails, you also wouldn't fetch the email directly. This is added in this example purely for demonstration purposes.
REST API
The following section will outline how to related the attachment using the REST API using the /link/attachments/Â endpoint for the Email record.
Fetch Original Note
To fetch the original note, send a GET request to rest/{REST VERSION}/Notes/{$original_note_id}. An example of the response is shown below :
{


{
  "id": "4e226282-8158-11e8-a1b3-439fe19c087a",
  "name": "Note with Attachment",
  "date_entered": "2018-07-01T12:00:00-00:00",
  "description": "Send to special clients.",
  "file_mime_type": "application/pdf",
  "filename": "special_sales_doc.pdf",
  "_module": "Notes"
}
Create and Relate a Note
Create a JSON object based on the response (which we will treat as a JSON object named $original_note) like:
{
	"upload_id": "$original_note.id",
	"file_source" : "Notes"
	"name" : "$original_note.filename",


"name" : "$original_note.filename",
	"filename" : "$original_note.filename",
}
Relate the Note to the Email record using the 'attachments' link. Next, send a POST request to rest/{REST VERSION}/Emails/{$email_id}/link/attachments/Â with the JSON shown above as the request body.
  	Last modified: 2023-02-03 21:04:03


Customizing Prefill Fields When Copying Records
Overview
The copy action on the record view allows for users to duplicate records. This article will cover the various ways to customize the prefill fields on the copy view.
Modifying the Copy Prefill View Using the Vardefs
The following section will outline how to modify the fields that are prefilled when copying a Bug record from the record view using the beans Vardefs. This is helpful when the list of copied fields are static and have no dependencies.Â You can apply this to any module in the system.
./custom/Extension/modules/Bugs/Ext/Vardefs/copyPrefill.php
<?php
//remove a field from copy
$dictionary['Bug']['fields']['description']['duplicate_on_record_copy'] = 'no';
//add a field to copy