Using Script and CSS loading plug-ins for easy Sugar integrations

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.