Skip navigation
All Places > Developer > Blog > 2016 > February
2016

Subpanel Creep

 

The Subpanels section is where you find related records for the current displayed Sugar record. This is a vital feature because it allows a user to get a unified view of all the CRM records related to a particular record.  Who are the Contacts associated with the Account? When is the last time I talked to this Customer?  These answers are typically found within the subpanel section.

 

A very common customization in Sugar is to create new Custom Modules or to add new types of relationships between existing modules in the CRM.  For example, at SugarCRM we use Sugar to track Sugar Subscriptions within a custom module.  These Sugar Subscriptions are related back to the Account record for each and every customer we have. So there is a Subscriptions subpanel that appears on every Account record for this relationship.

 

Module relationships and even custom subpanels are slowly added over time to every Sugar instance - but rarely does anybody go back and remove them. This can cause problems since every subpanel has a cost associated with it.

 

Each subpanel is populated via an API request that queries additional database tables. Also each subpanel adds weight to the client user interface with additional HTML and JavaScript objects associated with them. If you have 15, 20, or more subpanels on your modules then opening a single record can generate a lot of overhead as these panels are rendered and are populated.Performance is a priority at SugarCRM Engineering. So it was necessary for us to tackle this common issue head on.

 

If you are in the Sugar Beta program, then you can try out the Sugar 7.7 Beta to observe the following changes.

Sugar Developers who have heavily customized Sidecar Subpanels need to carefully test their customizations when upgrading to Sugar 7.7.

 

Refactored to improve Subpanel performance

 

We have (re)introduced record counts on subpanels.

 



 

This is a great UX touch! It also helps avoid situations where users open subpanels just to see if there are new related records.

 

We have also optimized the rendering of Subpanels so that subpanel lists are rendered only when subpanel is expanded. Previously, they would be rendered as hidden HTML which would impact client side performance unnecessarily.

 

A full related record query that selects all fields in list view is only used when the subpanel has been expanded.

 

These changes necessitated a refactor of functions related to toggling and rendering of subpanels. Sugar Developers who have heavily customized Sidecar Subpanels need to carefully test their customizations with Sugar 7.7.

 

System Setting to collapse Subpanels by default

 

In the past, Sugar would always fetch subpanel data immediately upon navigating to a record view. Subpanels that were opened by users would be "sticky" and remain open when users visited other records within the same module.

 

For example, if you left the Contacts subpanel open on the Accounts module, then every time you returned to an Account the Contacts subpanel would be open and related record data would be fetched to populate that subpanel.

 

This behavior can now be controlled using a System Setting.

 



 

With the collapse_subpanels configuration setting enabled, all Sidecar subpanels will be collapsed by default for all users when they visit Record views.

Enabling the $sugar_config['collapse_subpanels'] setting is highly recommended for Enterprise customers or situations where "subpanel creep" has taken hold in your Sugar deployment.

We have revamped our Sugar 7 On-Site Sizing Guide to deploying Sugar on premise or in a public/private cloud. We have created a simple process with supporting hard data that will allow you to “right” size the infrastructure needed to host Sugar for any usage scenario or any deployment size: Small to Enterprise.

This is also the first piece of key documentation that will be part of an upcoming Sugar Solution Architect certification. This certification is going to be really key to our SugarCRM Partners and Enterprise Customers so I am excited that this is something that is in flight right now.

Keep in mind that not only do we give you the guide but we also provide the Tools. You can use SugarCRM designed JMeter scenarios to verify that the deployment you designed can handle the expected load.

These resources will make anyone well equipped to deploy Sugar 7.

Relaunched SugarCRM Support Site!

 

Have you had a chance to check out the new SugarCRM Support site? It is awesome. I am in there all the time looking up documentation so the new changes have really made my job easier. The new layout looks great but also organizes the content better and the search functionality has been improved.  This means I can find the information I need that much faster.

 

On top of that, the new Support site includes a couple of recent additions that are welcome to Sugar newbies and those Sugar Developer veterans who have been around the block.

 

New Sugar 7 On-Site Sizing Guide!

 

We have revamped our guide to deploying Sugar on premise or in a public/private cloud. We have created a simple process with supporting hard data that will allow you to "right" size the infrastructure needed to host Sugar for any usage scenario or any deployment size: Small to Enterprise.

 

This is also the first piece of key documentation that will be part of an upcoming Sugar Solution Architect certification. This certification is going to be really key to our SugarCRM Partners and Enterprise Customers so I am excited that this is something that is in flight right now.

 

Keep in mind that not only do we give you the guide but we also provide the Tools. You can use Sugar designed JMeter scenarios to verify that the deployment you designed can handle the expected load.

 

These resources will make anyone well equipped to deploy Sugar 7.

 

New Vagrant Development Environment instructions!

 

Quick start your Sugar development by using our new Vagrant set up instructions!  With a reasonable internet connection, this will get you up and running for local Sugar development in a matter of a few minutes.

 

A nice way to kick Sugar's tires especially for those new to the LAMP stack.

Getting 3rd party JavaScript into Sugar

 

The most common UI integration point between Sugar and external applications is the trusty Sugar Dashlet. This is why we spend so much time talking about Dashlets here on the blog and at our Sugar Developer events throughout the year. However, if you are building something more complicated than an iframe dashlet integration then there is often a requirement to pull 3rd party JavaScript into the page. For example, many integrations are facilitated using JavaScript API client libraries.  Or there is code that a developer is trying to reuse that relies on JavaScript libraries that Sidecar does not already use.

 

Out of the box, this developer has a couple different options:

 

Use JSGroupings Extension

 

Using JSGroupings extension allows you to include additional JavaScript files with the core Sugar application JS that is pulled into the page when the Sugar application is loaded. The drawback is that this JavaScript will get pulled into the page whether or not it is ever used by a user.

 

Adding script tags to page dynamically

 

You could add script tags to your Handlebars template or via a Sidecar controller. But this approach adds a bunch of ugly complexity to your code as you have to manage many routine tasks related to loading scripts.

 

Providing another way!

 

But Sugar gives us the tools we need to design another way! Below we will explore using the Sidecar plug-in framework to provide an easy way to dynamically load JavaScript and CSS for use with your custom Sidecar components.

 

Sidecar Plug-in Framework

 

In Sugar 7, we introduced a plug-in framework that allows you to Mixin additional functionality into your Sidecar component with minimal fuss. If you have created a Sugar Dashlet before then you have already used the Dashlet Sugar 7 plug-in.

 

hello-world.js

({
    plugins: ['Dashlet']
})

 

Under the Sidecar covers, the plug-in manager uses Underscore extend() to add properties from the plug-in to your component. In the case of the Dashlet plug-in, this adds the necessary APIs that Sugar needs to display and manage your view within Sugar Dashboards.

 

There are two important facts to keep in mind when working with Sidecar plug-ins.

 

Sidecar plug-ins are not extensible

 

You cannot extend a Sidecar Plug-in like you can a standard Sidecar component such as a view, layout, field, etc. So when creating Sidecar plug-ins, we recommend making them as lightweight and granular as possible so you can easily mix, match, and replace plug-in combinations in Sidecar components.

 

Sidecar plug-ins should be installed using JSGroupings extension

 

Also, we recommend registering your new custom Sidecar plug-ins using the JSGroupings extension. Below we will provide a couple of examples that shows more details on how this works.

 

Registering your Sidecar plug-in

 

Adding a plug-in to Sugar is as easy as calling app.plugins.register(). Lets break down the parameters.app.plugins.register(name, validTypes, plugin)

ParameterDescription
name(String) Plugin name that you want to use
validTypes(String|Array) list of component types this plugin can be applied to.  Limited to view, field, layout, model, and collection as of Sugar 7.6.
plugin(Object) Plug-in object

 

Plug-ins are registered after the Sugar application has been initialized. When registering a plug-in using JSGroupings extension, you should do so within an app:init event handler. You will see this in both examples below.

 

To read the full Sidecar plug-in manager documentation, view the doc blocks in sidecar/src/core/plugin-manager.js.

 

CssLoader Plug-in

 

Our CssLoader example is quite simple. The Css Loader plug-in is also available over at the Sugar Integration Building Blocks project in Github. It tracks the Stylesheets loaded in the browser and only adds a new link tag to pull in new CSS files as needed.

 

CssLoader.js

/**
* Copyright 2016 SugarCRM Inc.  Licensed by SugarCRM under the Apache 2.0 license.
*/
(function (app) {
app.events.on('app:init', function () {
/**
         * This plugin allows components to dynamically load CSS files.
         *
         * <pre><code>
         * // Sample component:
         * {
         *      plugins: ['CssLoader'],
         *
         *      css: [
         *          'include/javascript/foo/foo',
         *          'clients/base/view/bar/bar'
         *      ]
         * }
         * </code></pre>
*/
app.plugins.register('CssLoader', ['layout', 'view', 'field'], {
/**
             * Load CSS files specified in component.css array.
*/
onAttach: function() {
this.loadCss();
            },

/**
             * Load given CSS file paths.
             * @param {array} [cssFiles] - paths to css files
*/
loadCss: function(cssFiles) {
var $previouslyAdded;
_.each(cssFiles || this.css, function(file) {
var $link;
if (!this.isCssLoaded(file)) {
if(file.indexOf('.css') === -1){
                            file = file + '.css';
                        }
                        $link = $('<link>', {
                            href: file,
                            type: 'text/css',
                            rel: 'stylesheet'
                        });

if ($previouslyAdded) {
$previouslyAdded.after($link);
                        } else {
// We prepend instead of append so that styles in Styleguide is preferred over
// dynamically loaded CSS styles when they have equal specificity order.
$link.prependTo(document.head);
                        }

                        $previouslyAdded = $link;
                    }
                }, this);
            },

/**
             * Is the given CSS file already loaded in the browser?
             * @param {string} href
             * @returns {boolean}
*/
isCssLoaded: function(href) {
return !!_.find(document.styleSheets, function(style) {
return style.href && (style.href.indexOf(href) !== -1);
                });
            }
        });
    });
})(SUGAR.App);

 

 

 

ScriptLoader Plug-in

 

The Script Loader plug-in is also available over at the Sugar Integration Building Blocks open source project in Github. This plug-in relies on RequireJS to do most of the dirty work related to loading and managing JavaScript libraries.

 

ScriptLoader.js

/**
* Copyright 2016 SugarCRM Inc.  Licensed by SugarCRM under the Apache 2.0 license.
*/
(function (app) {
/**
     * Allow jquery and moment.js to be used in requirejs modules.
*/
var bootstrapScriptLoader = function() {
define('jquery', [], function() {
return jQuery;
        });
define('moment', [], function() {
return moment;
        });
    };

app.events.on("app:init", function () {
bootstrapScriptLoader();

/**
         * This plugin allows components to dynamically load JavaScript files.
         *
         * <pre><code>
         * // Sample component:
         * {
         *      plugins: ['ScriptLoader'],
         *
         *      scripts: [
         *          'include/javascript/foo/file1',
         *          'clients/base/view/bar/file2'
         *      ],
         *
         *      onLoadScript: function(file1, file2) {
         *          //code here
         *      }
         * }
         * </code></pre>
*/
app.plugins.register('ScriptLoader', ['layout', 'view', 'field'], {
/**
             * Load scripts specified in component.scripts array.
*/
onAttach: function() {
this.loadScript();
            },

/**
             * Load given JavaScript files. Once loaded, it calls onLoadScript().
             * By default, it loads the script from component.scripts array.
             * @param {Array} [scripts] - List of paths to scripts to load.
*/
loadScript: function(scripts, callback) {
var scriptsToLoad;
                callback = callback || this.onLoadScript || $.noop;

if (scripts) {
                    scriptsToLoad = scripts;
                } else if (_.isArray(this.scripts)) {
                    scriptsToLoad = this.scripts;
                }

require(scriptsToLoad, _.bind(callback, this));
            }
        });
    });
})(SUGAR.App);

 

 

 

Highcharts and Unicorn Buttons Dashlet Example

 

Now we can create a simple Dashlet that leverages both of our new plug-ins to easily pull in a couple different external libraries dynamically. In the example below, we use Highcharts and Unicorn UI Buttons but you can adapt this example to use whatever JavaScript or CSS libraries that you need.

 

highcharts-example.hbs

{{! Copyright 2016 SugarCRM Inc.  Licensed by SugarCRM under the Apache 2.0 license. }}
<h3>Example Button</h3>
{{! Unicorn UI styled buttons }}
<span class="button-wrap">
    <a class="button button-pill button-raised button-royal">Go</a>
</span>
<a class="button button-3d button-primary button-rounded">Check out the new site!</a>
<h3>Example Highchart</h3>
{{!
    div element we are reserving for use with our Highchart.

    Please don't use element IDs for this purpose, this is a
    bad practice for Sidecar components, especially Dashlets,
    since they can appear on page more than once.
}}
<div class="highchart-div" style="min-width: 310px; height: 400px; margin: 0 auto"></div>

highcharts-example.js
/**
* Copyright 2016 SugarCRM Inc.  Licensed by SugarCRM under the Apache 2.0 license.
*/
({
// ADD YOUR PLUG-INS HERE!
    plugins: ['Dashlet', 'ScriptLoader', 'CssLoader'],

// For ScriptLoader, you can specify a list of scripts to load concurrently here
    scripts: [
/* "http://code.highcharts.com/highcharts.js" */
    ],
// For CssLoader, specify your required Stylesheets here
    css: [
"http://unicorn-ui.com/buttons/css/buttons.css"
    ],

/**
     * Load Highcharts.js then Exporting.js
     *
     * We need to load these scripts in sequence, exporting.js is dependent on highcharts.js.
     *
     * @param options
*/
initialize: function(options){
this._super('initialize', [options]);
// Leverage a callback in our call to the Sidecar plug-in's loadScript function.
this.loadScript(["http://code.highcharts.com/highcharts.js"], function(){
this.loadScript(["http://code.highcharts.com/modules/exporting.js"]);
        });

    },

// When scripts are loaded, lets call the Highcharts jQuery plug-in to render the chart on the appropriate div.
onLoadScript: function(){
/**
         *
         * Please use this.$() when possible!
         *
         * Global selectors are often slower and will cause bugs if your Dashlet appears on multiple places on page.
         *
         **/
this.$(".highchart-div").highcharts({
            chart: {
                type: 'area'
            },
            title: {
                text: 'Historic and Estimated Worldwide Population Growth by Region'
            },
            subtitle: {
                text: 'Source: Wikipedia.org'
            },
            xAxis: {
                categories: ['1750', '1800', '1850', '1900', '1950', '1999', '2050'],
                tickmarkPlacement: 'on',
                title: {
                    enabled: false
                }
            },
            yAxis: {
                title: {
                    text: 'Billions'
                },
                labels: {
formatter: function () {
return this.value / 1000;
                    }
                }
            },
            tooltip: {
                shared: true,
                valueSuffix: ' millions'
            },
            plotOptions: {
                area: {
                    stacking: 'normal',
                    lineColor: '#666666',
                    lineWidth: 1,
                    marker: {
                        lineWidth: 1,
                        lineColor: '#666666'
                    }
                }
            },
// Dummy data for our chart
            series: [{
                name: 'Asia',
                data: [502, 635, 809, 947, 1402, 3634, 5268]
            }, {
                name: 'Africa',
                data: [106, 107, 111, 133, 221, 767, 1766]
            }, {
                name: 'Europe',
                data: [163, 203, 276, 408, 547, 729, 628]
            }, {
                name: 'America',
                data: [18, 31, 54, 156, 339, 818, 1201]
            }, {
                name: 'Oceania',
                data: [2, 2, 2, 6, 13, 30, 46]
            }]
        });
    }
});

highcharts-example.php
<?php
/**
* Copyright 2016 SugarCRM Inc.  Licensed by SugarCRM under the Apache 2.0 license.
*/
$viewdefs['base']['view']['highcharts-example'] = array(
'dashlets' => array(
array(
'label' => 'Highcharts Example',
'description' => 'Highcharts example dashlet',
'config' => array(
            ),
'preview' => array(
            ),
'filter' => array(
            )
        ),
    ),
);

 

 

 

Easy right? This is what it looks like when it is installed on a Sugar Dashboard.