SugarCRM Developers

Creating a Dashlet for Sugar 7 List Views

Blog Post created by SugarCRM Developers on Jul 7, 2015

Post originally written by tshubbard.

 

In our previous "Hello World" dashlet post, we established what a minimal dashlet entailed.  In these next post, we'll be building on those skills to create a more useful dashlet that takes advantage of Sugar 7 List Views.  We will be creating a dashlet for Cases that binds to the list's Collection and sums the number of Cases by their status.  So if the Cases list contains 5 records, and 3 of those are in "New" state and 2 are in "Closed" state then we want our dashlet to display "New: 3" and "Closed: 2".  To the code!

 

File Structure

 

Again, using what we learned in the previous post, we're going to create a folder in custom/clients/base/views/ called "case-count-by-status". Inside that folder you should create 3 files:

  • case-count-by-status.php
  • case-count-by-status.js
  • case-count-by-status.hbs

 

You should have something that looks like the following screenshot:

 

While technically optional, we will also utilize the Language extension in order to provide multilingual support for our example dashlet.  This extension file will be located at custom/Extension/application/Ext/Language/en_us.case-count-by-status.php.

 

Dashlet Metadata (.php file)

 

Dashlet metadata is going to look almost identical to our previous "Hello World" dashlet. We're not doing anything too fancy here, so everything should look basically the same.

 

case-count-by-status.php

<?php

/**
* Metadata for the Case Count by Status example dashlet view
*
* This dashlet is only allowed to appear on the Case module's list view
* which is also known as the 'records' layout.
*/
$viewdefs['base']['view']['case-count-by-status'] = array(
'dashlets' => array(
array(
//Display label for this dashlet
'label' => 'LBL_CASE_COUNT_BY_STATUS',
//Description label for this Dashlet
'description' => 'LBL_CASE_COUNT_BY_STATUS_DESCRIPTION',
'config' => array(
            ),
'preview' => array(
            ),
//Filter array decides where this dashlet is allowed to appear
'filter' => array(
//Modules where this dashlet can appear
'module' => array(
'Cases',
                ),
//Views where this dashlet can appear
'view' => array(
'records',
                )
            )
        ),
    ),
);

 

Dashlet Metadata Filter Options

 

Currently there are two main dashlet filter keys that you'll see in the codebase; "module" and "view".  Across of these filter keys, the main thing to remember is that not specifying a filter at all means that your dashlet will be available in all views of all modules. You only need to add filters if you desire to restrict your dashlet to a specific module or view.  Let's look at the filter keys in more detail.

Specifying a filter means your dashlet will be restricted to specified modules and views.  Not specifying a filter means your dashlet will be available in all modules and views.

 

"module"

 

The module filter lets you add an array of modules where your dashlet can appear. If you wanted your dashlet to appear in the list of available dashlets for only the Accounts, Cases, and Contacts modules then your module filter would look like the following.

 'filter' => array(

 

     'module' => array(

 

         'Cases',

 

         'Accounts',

 

         'Contacts',

 

     ),

 

)

 

"view"

 

The view filter lets you add an array of views to limit on which views your dashlet can appear. If you wanted your dashlet to appear only on the Record view, your view filter would look like the following.

 'filter' => array(

 

     'view' => array(

 

         'record',

 

     ),

 

)

 

Currently, there are two possible values for the view filter. The List View is indicated by using "records".  The Record View is indicated by using "record".

 

 

Dashlet Controller (.js file)

 

Enough metadata nonsense, now for the fun stuff!  Here is the JavaScript controller for a Case Count By Status dashlet.

 

case-count-by-status.js

/**
* Case Count by Status example dashlet controller
*
* Controller logic watches the current collection on display and updates the
* dashlet automatically whenever the current collection changes.
*
* This is a simple example of a dashlet for List Views in Sugar 7.x.
*
**/
({
//This view uses the essential Dashlet plug-in
    plugins: ['Dashlet'],

/**
     * Values is used by the template to display the statuses and counts.  Backs our Handlebars template.
*/
    values: undefined,

/**
     * Keeps track of how many cases in total we're displaying.  Also used in our Handlebars template.
*/
    totalCases: undefined,

/**
     * Keeps a map of status types by model ID as the key.
*/
    modelsMap: undefined,

/**
     * @inheritdoc
*/
initialize: function(options) {
// call the parent's (View's) initialize function
// passing options as an array
this._super('initialize', [options]);

// initialize vars
this.modelsMap = {};
this.totalCases = 0;
this.values = {};
    },

/**
     * @inheritdoc
*/
bindDataChange: function() {
var ctx = this.context,
            collection = ctx.get("collection");
if(_.isEmpty(collection)){  //Collection will be empty in "preview" mode
return;
        }

//Listening to 'reset' events on the collection
collection.on('reset', function(collection) {
// Ensure that collection exists, has models, then parse out models for display
if(collection && collection.length) {
this._parseModels(collection.models, false);
            }
        }, this);

//Listening to 'add' and 'remove' events on the collection
collection.on('add remove', function(model, collection, options)  {
// The Backbone's options argument for 'add' and 'remove' events are different
// if options.removed doesn't exist, then we will know this is a 'remove' event
if (_.isUndefined(options.remove)) {
options.remove = true;
            }

// Backbone passes add/remove options as an event param, so we can tell
// if this was the add or remove event and pass it to parseModels
this._parseModels([model], options.remove);
        }, this);

if(collection.models && _.isEmpty(this.modelsMap)) {
// manually cause a parsing of the models
// this covers the scenario when a user creates a new record
this._parseModels(collection.models, true);
        }
    },

/**
     * Recalculates values used in the template from modelsMap
     *
     * @private
*/
_recalcValues: function() {
// reset values
this.values = {};
this.totalCases = 0;

_.each(_.values(this.modelsMap), function(status) {
this.totalCases++;
// check to see if we've already set a value
// for this status
if (this.values[status]) {
// status is already set so just increment
this.values[status].count++;
            } else {
// add a new entry on the values object
// with status as the key and an Object
// with name and count for our template
this.values[status] = {
                    name: status,
                    count: 1
                };
            }
        }, this);
    },

/**
     * Takes an array of models and parses them into modelsMap then (re)counts the values in this map
     *
     * @param {Array} models The Array of models to parse
     * @param {Boolean} remove If the models passed in should be removed or not
     * @private
*/
_parseModels: function(models, remove) {
var id,
            status;

_.each(models, function(model) {
// get the case id & status
            id = model.get('id');
            status = model.get('status');

if (remove && this.modelsMap[id]) {
// if the function was called
// to remove models, delete the id/value
delete this.modelsMap[id];
            } else {
// otherwise, add the id and status
// to modelsMap
this.modelsMap[id] = status;
            }
        }, this);

// now that we've updated the modelsMap,
// recalculate the this.values object for rendering
this._recalcValues();

// double-check that the view has not been disposed
// if not, then re-render the dashlet
if (!this.disposed) {
this.render();
        }
    }
})

On a List View, this.context points to the BeanCollection.  this.context.parent points to a parent model (when it exists, such as on a dashlet preview).

 

Dashlet Template (.hbs file)

 

Again, with a Dashlet you are free to format the display any way you like.  However, we do recommend leveraging Sugar's Styleguide so that your dashlet appears like a seamless extension of the Sugar user interface.  It is a great reference for you to leverage that allows your dashlets to appear as just another seamless part of the Sugar 7 application.

 

In this example, we leveraged some dashlet design patterns and CSS pulled directly from the Sugar 7 Styleguide.

 



The Sugar Styleguide describes how Sugar 7's CSS works and the design patterns used in common components such as Dashlets.  The Styleguide is how you build seamless UI for Sugar 7.x.

 

As a Sugar Admin, navigate to Styleguide > Core Elements > Dashboards > Dashlets to view the Dashlets style guide.  Specifically, we borrowed CSS from the "Summary" dashlet example listed in the Styleguide.

 



 

Here is our Handlebars template we put together leveraging this "Summary" pattern.

 

case-count-by-status.hbs

{{!
Case Count by Status example dashlet Handlebars template

We are reusing styling from the Sugar 7 Styleguide for our example dashlet.

Here we are borrowing CSS used in our Forecast Details dashlet which is suitable for display any set of name value pairs.
}}
<div class="forecast-details">
{{#each values}}
        <div class="row-fluid">
            <span class="span6">
{{name}}
            </span>
            <span class="span6 tright">
{{count}}
            </span>
        </div>
{{/each}}
    <div class="row-fluid">
        <strong class="span6">
{{! 'str' is the Sugar 7 Handlebars helper that translates a label into a localized language string}}
{{str "LBL_CASE_COUNT_BY_STATUS_TOTAL"}}
        </strong>
        <strong class="span6 tright">
{{totalCases}}
        </strong>
    </div>
</div>

 

Language Extension (.php file)

 

Finally, in the above Handlebars template and in the dashlet metadata file we have defined some display labels that need to be translated into human readable strings.  To accomplish this, we have added a language extension for these new labels that we have introduced.

It is a development best practice to use labels for strings so that your user interface can be translated and supported in multiple languages

 

en_us.case-count-by-status.php

<?php
// This file will provide English strings for our new labels.
// Additional extensions could be created so that our dashlet supports other languages.
$app_strings['LBL_CASE_COUNT_BY_STATUS'] = 'Case Count By Status';
$app_strings['LBL_CASE_COUNT_BY_STATUS_DESCRIPTION'] = 'Shows the number of Cases on the Cases List view by status.';
$app_strings['LBL_CASE_COUNT_BY_STATUS_TOTAL'] = 'Total Cases:';

 

The "en_us" at the beginning of this filename is significant.  It represents the user locale where these strings are used.  If you wanted to created translations for other locales, then this value would be different.  For example, French language extensions must start with "fr_FR".

 

Wrapping Up

 

So now we've got a working example dashlet that takes advantage of a module's List View.  Remember to run a Quick Repair and Rebuild and then navigate to your Cases module and add the new dashlet.  It should look something like what you see below.

 



Outcomes