Quantcast
Channel: JavaScript – Sugar Developer Blog – SugarCRM
Viewing all 27 articles
Browse latest View live

Integrating to Sugar using a custom drawer action

$
0
0

There are so many possible approaches for integrating with Sugar that selecting the best one can be tricky.

When deciding to add new components into the Sugar user interface, the number of possibilities to consider is dizzying.

Should I build a new component or override an existing one?

Am I building a View, Layout, or custom Fields?

Will a Dashlet give me enough screen real estate or will I need to create a new layout?

The goal of the Sugar Integration Building Blocks open source project is to provide sample code and most importantly the underlying design patterns that makes it easy to build best practice integrations.

Some integration use cases mean that a primary action has to be added to a Record or List view. For example, you may need to launch a wizard interface such one used with a Configure Price Quote (CPQ) solution on an Opportunity record.

A common CPQ flow would be to have a custom “Create Quote” action on an Opportunity record. This would launch an expansive Configurator wizard that, when complete, will need to push the new Quote, new Revenue Line Items, etc, back to the original Opportunity record.

The standard approach for this would be to add a button to the Record View or List View that launches a Drawer.

In order to make this design approach easier, we have added an HTML iframe drawer action as a new Building Block!  Now it is very easy to build a complete integration or proof of concept that utilizes a drawer!

Screen Shot 2016-06-06 at 11.26.04 AM

An example “Open Drawer” action

Read more below to learn how it works.  It was designed in an upgrade safe way that does not interfere with any existing customizations that may exist on Record views or List views.

 

The Integration Flow

This building block contributes an action to the Opportunities Record layout but can be easily adapted to make a contribution to any other application screen (or Sidecar route).

Screen Shot 2016-06-06 at 10.38.06 AM

This new button was added to Opportunities Record view

Clicking the “Open Drawer” button will launch a configurable IframeDrawerLayout. This action uses our standard Drawer API.

app.drawer.open({
    layout: 'iframe-drawer',
    context: {
        model: model,
        url: "//httpbin.org/get?record="+model.get("id")+"&module=Opportunities",
        title: "IFrame Drawer"
    }
}, function(){
    // ... on close callback ...
});
Screen Shot 2016-06-06 at 11.38.19 AM

Active IframeDrawerLayout on an Opportunity

Of course, for the example we just use the httpbin.org service to make it easy to show what the request looks like to a downstream web application. This context can give the downstream application what it needs to appropriately configure the interface appearing in the HTML iframe.  Passing the module name and the record ID would also allow this external application to push changes back into the original Opportunity record in Sugar via the REST API.

Open drawers are placed on to the foreground, which means that when the drawer is closed by hitting the “Cancel” button that the control returns to the Opportunity record. You can still see the Opportunity at the bottom of the screenshot above. You can also close the drawer programmatically by calling the following function.

app.drawer.close();

Adding the button and event handling code

It is very common for Sugar instances to have significant customizations on Record or List view controllers. So for this approach, we want to maximize our compatibility since we may not always know when such customizations exist ahead of time in a particular Sugar instance. So we have intentionally avoided overriding a Sidecar controller in this implementation.

Adding a button that triggers a Sidecar event can be accomplished entirely using a Sidecar metadata extension, such as the one below.

<?php
/**
 * Copyright 2016 SugarCRM Inc.  Licensed by SugarCRM under the Apache 2.0 license.
 */

//Insert our custom button definition into existing Record View Buttons array for Opportunities module
array_unshift($viewdefs['Opportunities']['base']['view']['record']['buttons'],
    array(
        'name' => 'custom_button',
        'type' => 'button',
        'label' => 'Open Drawer',
        'css_class' => 'btn-primary',
        //Set target object for Sidecar Event.
        //By default, button events are triggered on current Context but can optionally set target to 'view' or 'layout'
        //'target' => 'context'
        'events' => array(
            // custom Sidecar Event to trigger on click.  Event name can be anything you want.
            'click' => 'button:open_drawer:click',
        )
    )
);

We then add the following JavaScript code into the Sugar page using a JSGroupings extension.

(function(app){
    /**
     * Copyright 2016 SugarCRM Inc.  Licensed by SugarCRM under the Apache 2.0 license.
     */

    //Run callback when Sidecar metadata is fully initialized
    app.events.on('app:sync:complete', function(){

        var openDrawerCallback = function(model){...};

        //When a record layout is loaded...
        app.router.on('route:record', function(module){

            //AND the module is Opportunities...
            if(module === 'Opportunities') {

                //AND the 'button:open_drawer:click' event occurs on the current Context
                app.controller.context.once('button:open_drawer:click', openDrawerCallback);
            }
        });

    });
})(SUGAR.App);

Our custom JavaScript waits until login and metadata sync is complete and then attaches a listener to Sidecar’s Router.  This listener is used to determine when the desired layout is being displayed and add a listener to the current Context object for our button’s click event.

Get the entire example in Github!

The full example is available in Github as a complete package!  For more information on how to work with these Module Loadable packages in this repository, check out the Packages README.



Prepare for the Backbone.js upgrade in Sugar 7.8

$
0
0

backbone

Upgrading our Backbone

Have you done some Sidecar programming lately? Then you have been using Backbone. Backbone is the err… backbone of Sidecar. It provides all the base MVC classes which are extended by Sidecar to create the Sugar 7 UI. For example, all Sidecar controllers (Views, Layouts, Fields) extend from the Backbone View class.

Ultimately, a solid background in Backbone programming will turn you into a Sidecar wizard in no time.

Screen Shot 2016-07-11 at 11.12.50 AM

All Sidecar controllers, such as the Record View controller,  extends from the Backbone View class

But if you are a Backbone aficionado then you might have noticed that Sugar 7.7 and earlier versions uses an old version of Backbone (specifically Backbone 0.9.10). We have been missing out on bug fixes and miscellaneous feature improvements. So for Sugar 7.8 we will be moving to Backbone 1.2.3.  Since Backbone.js has a hard dependency on Underscore.js, we will also upgrade the Underscore library from 1.4.4 to 1.8.3.

All Sugar Developers should check out the Backbone changelog and the Underscore changelog to see if their code customizations could be impacted by this long overdue library upgrade.

Read on to learn more about some adjustments you need to make to your Sugar code.

Changes to Sidecar controller DOM event delegation

In Backbone 1.2.0, there was an important change that affects how DOM events are delegated in Backbone Views. Emphasis mine.

Views now always delegate their events in setElement. You can no longer modify the events hash or your view’s el property in initialize.

This means that modifying this.events in the initialize() function of a Backbone View to register DOM event handlers is no longer supported by Backbone. This is because DOM events set in the events hash (this.events) are delegated before initialize() is even called. However, since this was a common practice for Sugar code and customizations we have altered the default Backbone behavior within Sidecar for the Sugar 7.8 release.

Sugar will continue to continue to call delegateEvents() during initialize() in Sugar 7.8 for compatibility but the practice is deprecated since Backbone no longer supports it. Sidecar controllers that modify this.events during initialize() will continue to work until this workaround is removed in an upcoming Sugar release.

Here is a simple example of a Sidecar view that uses this deprecated practice.

A simple example

./custom/clients/base/views/example/example.js

/** This approach is deprecated in Sugar 7.8 release  **/
({
    events: {...},
    initialize: function(options) {
        if (...) {
            this.events['click'] = function(e){...};
        }
        this._super('initialize', [options]);
    },
    ...
})

This will not work in a future Sugar release.

A Record View use case

Let’s examine a common Sidecar customization use case.

Say we need to extend the out of the box Sugar RecordView controller to launch a wizard user interface on a mouse click.

We plan to listen for a special DOM click event but we also do not want to break any existing Record view event listeners.

To implement this feature, Sugar Developers commonly used code such as the following:

./custom/…./clients/base/views/record/record.js

/** This approach is deprecated in Sugar 7.8 release  **/
({
    extendsFrom: 'RecordView',
    initialize: function(options) {
        // Extending the RecordView events in a deprecated fashion
        this.events = _.extend({}, this.events, {
            'click .wizard': '_launchWizard'
        });
        this._super('initialize', [options]);
    },
    _launchWizard: function(){
      // ... do something ...
    }
})

To reiterate, the examples above will no longer work in a future Sugar release. Sugar Developers should update any similar code to use alternative approaches listed below.

Event Delegation Alternatives

Here are some alternatives that you can use for delegating DOM events with your Sidecar controllers.

Statically define your events hash

Define your events all within the events object hash. Note that when extending controllers that this would override any events defined on a parent controller.

({
    events: {
        'mousedown .title': 'edit',
        'click .button': 'save',
        'click .open': function(e) { ... }
    }
    ...
})

Use a callback function for dynamic events

You can assign the events variable of Backbone controllers a function instead of an object hash.  This function will then be used to determine the event hash used when delegateEvents() is called by Backbone.

({
    events: function(){
        if (...) {
            return {'click .one': function(){...}};
        } else {
            return {'click .two': function(){...}};
        }
    }
    ...
})

If you must, then call delegateEvents() function directly

You can optionally pass an alternative event hash using this.delegateEvents(events). When unspecified, this.events is used by default. The delegateEvents() function removes any previously delegated events at same time so it is safe to call multiple times.

({
    extendsFrom: 'RecordView',
    oneEvents: {...},
    twoEvents: {...},
    isOne = true,
    toggleOneTwo: function(){
        if (this.isOne) {
            this.delegateEvents(_.extend({}, this.events, this.oneEvents));
        } else {
            this.delegateEvents(_.extend({}, this.events, this.twoEvents));
        }
        this.isOne = !this.isOne;
    }
    ...
})

Other important Backbone changes

  • Backbone.js no longer attaches options to the Backbone.View instance by default (as of 1.2.0). Sugar Developers should know we plan to deprecate this.options on Sidecar controllers in a future Sugar release.
  • This upgrade may also break customizations of Sidecar routes that expect URL parameters to be concatenated to the first argument passed to the Backbone router’s callback. Sugar Developers should change the signature of their router callbacks to specify the additional argument for URL parameters.

For example:

Old way:

// in a sugar7.js equivalent file
{
    name: 'search',
    route: 'search(/)(:termAndParams)',
    callback: function(termAndParams) {
        // termAndParams => "?module=Accounts&foo=bar"
        // commence ugly URL parsing...
    }
}

New way:

// in a sugar7.js equivalent file
{
    name: 'search',
    route: 'search(/)(:term)',
    callback: function(term, urlParams) {
        // term => "this is a search term"
        // urlParams => "module=Accounts&foo=bar"
        // no more ugly URL parsing!
    }
}
  • Potential Breaking Change: Sugar customizations that override the sync method on any instances of Backbone.Model and Backbone.Collection should should be updated to match Backbone’s new signatures for the internal success/error callbacks for Model#fetch, Model#destroy, Model#save, and Collection#fetch methods.

For example:

Old way, Backbone < 0.9.10:

    // in a custom sidecar controller:
    sync: function(method, model, options) {
        // custom sync method
        ...
    options.success = _.bind(function(model, data, options) {
        this.collection.reset(model, data, options);
    }, this);
// in Backbone.js's Collection#fetch method...
    fetch: function(options) {
        options = options ? _.clone(options) : {};
        if (options.parse === void 0) options.parse = true;
        var success = options.success;
        // *** Note: 'collection', 'resp', 'options' are passed ***
        options.success = function(collection, resp, options) {
            var method = options.update ? 'update' : 'reset';
            collection[method](resp, options);
            if (success) success(collection, resp, options);
        };
        return this.sync('read', this, options);
    },

New way, Backbone > 1.x:

    // in a custom sidecar controller:
    sync: function(method, model, options) {
        // custom sync method
        ...

    // *** Only data should now be passed here ***
    options.success = _.bind(function(data) {
        this.collection.reset(data);
    }, this);
    // in Backbone.js's Collection#fetch method...
    fetch: function(options) {
        options = _.extend({parse: true}, options);
        var success = options.success;
        var collection = this;
        // Note: the success callback is now only passed 'resp'
        options.success = function(resp) {
            var method = options.reset ? 'reset' : 'set';
            collection[method](resp, options);
            if (success) success.call(options.context, collection, resp, options);
            collection.trigger('sync', collection, resp, options);
        };
        wrapError(this, options);
        return this.sync('read', this, options);
    },
  • Potential Breaking Change: The method signature for error callbacks for Model#fetch, Model#destroy, Model#save, and Collection#fetch has changed. The Backbone.Model or Backbone.Collection is passed as the first parameter and the second parameter is now the HttpError XHR object.

For example:

Old way:

    this.model.save({}, {
        error: function(error) {
            ...
        }
    });

New Way:

    this.model.save({}, {
        error: (model, error) {
            ...
        }
    });
  • Potential Breaking Change: Sugar customizations that set the id property directly on a Backbone.Model will not work with Backbone Collections. Sugar Developers should always use Backbone’s internal APIs/methods, meaning they should be using model.set(‘id’, …) instead.

For example:

var model = app.data.createBean('Accounts', {id: 'foo'});
var collection = app.data.createBeanCollection('Accounts');

collection.add(model);
model.id = 'bar';

console.log(collection.get('bar'));
Output >> undefined

Use model.set(‘id’, ‘bar’); instead:

var model = app.data.createBean('Accounts', {id: 'foo'});
var collection = app.data.createBeanCollection('Accounts');

collection.add(model);
model.set('id', 'bar');
console.log(collection.get('bar'));
Output >> model

How to add a client idle timeout to Sugar 7.x

$
0
0

Here is another guest post from Shijin Krishna from BHEA Technologies.

When a user logs into Sugar 7, an OAuth access token (with a 1 hour timeout by default) and a refresh token (with a 2 week timeout by default) are returned. When the access token expires, Sugar will automatically retrieve another access token as long as the refresh token is valid. This allows a user to use a Sugar browser tab for days on end without having to log back in.

Automated notification requests are made to the server on the user’s behalf at a default interval of every 5 minutes. These requests will allow the current session to remain active without actual user input. So tracking user activity by adjusting access token and refresh token expiry time or tracking network activity alone is not a good idea.

In this blog we are going to explore a way to track a user’s true idle time based on actual user interface activity. For example, one or more of clicks, typing, mouse movements etc. To track a user’s idle time we will use the JQuery IdleTimeout plugin.

This allow us to configure reasonable settings for the OAuth access token and refresh token to allow Lotus Notes, Outlook and other plugins to function for a longer period without needing to login again, while continuing to reliably enforce idle logout on the web client.

We will also learn to configure maximum idle time crossing the same will log the user out from Sugar automatically.

Step 1) Add JQuery IdleTimeout plug-in to Sugar JS

Create a new JS Grouping Extension file in the following path.
custom/Extension/application/Ext/JSGroupings/idleTimer.php

<?php
// Copyright Shijin Krishna. This work is licensed under an Apache 2.0 license.
$js_groupings[] = $sugar_grp_sidecar = array_merge($sugar_grp_sidecar, array(
        'custom/include/javascript/store.min.js' => 'include/javascript/sugar_sidecar.min.js',
        'custom/include/javascript/jquery-idleTimeout.min.js' => 'include/javascript/sugar_sidecar.min.js',
        'custom/include/javascript/idleTimer.js' => 'include/javascript/sugar_sidecar.min.js',
    )
);
  • jquery-idleTimeout.min.js – Contains the source code for JQuery IdleTimeout plugin.
  • store.min.js – Required dependency for the plugin.
  • idleTimer.js – We will talk about this little later.

Add the IdleTimeout plug-in and store.js at following paths:

custom/include/javascript/jquery-idleTimeout.min.js
custom/include/javascript/store.min.js

Step 2) Start the idle timer

We will start tracking users inactivity time once the app:sync:complete event is triggered. The JQuery Idle Timeout plugin comes with a set of configurable parameters which will allow us to define the maximum idle time, callback to execute when the idle time reaches the maximum limit, etc. Please click here to view more public configuration variables.

custom/include/javascript/idleTimer.js

/**
* Idle time logout
* Copyright Shijin Krishna. This work is licensed under an Apache 2.0 license.
* Date 11/29/2016
*
* */
(function(app){
app.events.on('app:sync:complete',function(){
$(document).idleTimeout({
redirectUrl:'#logout', //redirect url
idleTimeLimit: app.config.max_idle_time || 600, // 'No activity' time limit in seconds. 600 = 10 Minutes
idleCheckHeartbeat: 10, // Frequency to check for idle timeouts in seconds
// optional custom callback to perform before logout
customCallback: function(){
app.logger.error("Logging out user after maximum idle time:" + app.config.max_idle_time); // this method will destroy user's session and log user out
// Due to bug with customCallbacks with idleTimeout jQuery plug-in,
// We must reload document to remove idleTimeout from page until user logs in again
window.location.reload();
},
enableDialog: false
});
});
})(SUGAR.App);

Step 3) Configuring the max idle time

By default our timer will consider ten minutes as the maximum idle time. But this can be configured by adding a new parameter ‘max_idle_time‘ to the config_override.php file which is available under sugar root directory.

config_override.php

<?php
// Copyright Shijin Krishna. This work is licensed under an Apache 2.0 license.
$sugar_config['additional_js_config']['max_idle_time'] = 1800;

Step 4) Rebuild Extensions & Configuration

Finally, you will need to run Quick Repair & Rebuild, Rebuild GS Grouping Files and Rebuild Config File in order to build your new extensions and configuration. You will also need to do a hard refresh of the browser page in order to load the updated JavaScript files.

screen-shot-2016-12-02-at-11-15-41-am

Nothing fancy but you can see the logout occurring in the console log.


How to build Chart layouts for Sugar 7

$
0
0

This blog will the first in a two part series on building Charts components for Sugar 7. This post is targeted at beginner to intermediate Sugar Developers who want to learn how to build their first Chart component.

This post assumes some basic knowledge of Sugar 7 development, Sugar 7 administration, JavaScript, and PHP.  This information should be useful to anyone who has an interest in Sugar 7 development.

The examples in this post were created on an out-of-the-box installation of Sugar Professional 7.8.0.0.  But this technique should work on any on-premise Sugar 7 instance.

Introduction

You may have noticed that a number of out of the box dashlets and views contain various fancy charts and visualizations.  This is possible because Sugar has a charting component build into it.  You can make use of this to display charts within your own custom dashlets, views or layouts.

In this post, we will focus on the “LineChart” type. There are other chart types that use different data formats and chart options but the general techniques covered here will work for all chart types.  These examples were implemented in a basic custom view but they will also work within dashlets.

NVD3 and Sucrose Charts

To provide the ability to display charts easily, Sugar uses Sucrose Charts which is SugarCRM’s fork of the NVD3 libraries. Take some time to explore that Sucrose Charts site. It includes a number of detailed examples of what charts are possible as well as the data models used to produce them.

Basic Requirements for a Sugar Chart

Within a Sugar view, all charts have the same general requirements. The examples here use the LineChart chart type but these steps apply to any chart you may want to create.

  1. Create the container for your Chart in your Handlebars template
    • Create a DIV element with a child SVG element inside your Handlebars template
  2. Implement the Chart plug-in within your JavaScript controller
    • Include the “Chart” Sidecar plug-in
    • Instantiate the chart model for your view in the initialize() method
    • Collect data in the right format for your chart type
    • Invoke the chart model via the chart call() method

The example below will make this clear.

A Basic Line Chart Example

We can create a custom Sidecar layout under the Accounts module called single-chart-layout which, in turn, includes a custom Sidecar view for Accounts module called single-chart-view. While we are using Accounts module here, this example could be adapted to run on any module.

First, we will create our custom layout.

custom/modules/Accounts/clients/base/layouts/single-chart-layout/single-chart-layout.php

<?php
$viewdefs['Accounts']['base']['layout']['single-chart-layout'] = array(
'type' => 'simple',
    'components' => array(
        array(
            'view' => 'single-chart-view',
        ),
    ),
);

As you can see from the layout, we reference “single-chart-view” so we should create this next.

custom/modules/Accounts/clients/base/views/single-chart-view/single-chart-view.php

<?php
$viewdefs['Accounts']['base']['view']['single-chart-view'] = array(
'title' => 'Chart example',
    'config' => "",
);

Now we can set up our chart within the single-chart-view component.

Set up your Handlebars template

Next, we create our Handlebars template.

single-chart-view.hbs

<div class="single-chart-view">
<svg></svg></div>

Include the “Chart” plugin

When creating single-chart-view.js the first thing to do is to include the “Chart” plugin:

({
    plugins: ['Chart'],
    className: 'single-chart-view',
    chartData: {},
    total: 0,
    initialize: function (options) {
    ...
})

Instantiate the chart model for your View in initialize() method

The Chart plug-in will rely on the view’s “chart” property.  We need to set up that property to point to a chart object.  In our example, we will tell it to point to a Line Chart.  To do that we can call the nv.models.lineChart() method which returns a line chart object.  For the options available for the Line Chart and others, you will need to consult NVD3 and Sucrose Charts documentation listed above.

For our first example, we will create a very simple line chart that displays a title.  You will notice that we attach “x” and “y” callbacks to the new Line Chart object.  These methods tell the chart object where to get the X and Y axis data for the line chart.  In our case the “x” callback retrieves from the “widget_points” property and the “y” callback retrieves from the “num_widgets” property. There is nothing magical about those names, of course. I made them deliberately arbitrary to illustrate that they can be anything you want.

({
    ...
    initialize: function (options) {
        this._super('initialize', [options]);
        this.chart = nv.models.lineChart()
            .x(function (d) {
                return d.widget_points;  // We get the X data points from 'widget_points'
            })
            .y(function (d) {
                return d.num_widgets;  // We get the Y data points from 'num_widgets'
            })
            .showTitle(true)
            .tooltips(false);
    },
    ...
})

Populate your data

If a loadData() method exists, then it will be executed when the View is rendered. This is when you retrieve data to be used in a chart. If you attach the data object to the View controller then it will be available whenever needed. Here we set it to a property called “chartData“.

The format of the object is crucial. Use this format to working with Line Charts.

({
  ...
    loadData: function() {
        this.chartData = {
                data: [
                    {
                        key: "Blue Stuff",
                        values: [
                            {
                                widget_points: 1, num_widgets: 10
                            },
                            {
                                widget_points: 2, num_widgets: 9
                            },
                            {
                                widget_points: 3, num_widgets: 8
                            },
                            {
                                widget_points: 4, num_widgets: 7
                            },
                            {
                                widget_points: 5, num_widgets: 6
                            },
                        ],
                        color: "#0000ff"
                    },
                    {
                        key: "Red Stuff",
                        values: [
                            {
                                widget_points: 1, num_widgets: 1
                            },
                            {
                                widget_points: 2, num_widgets: 2
                            },
                            {
                                widget_points: 3, num_widgets: 3
                            },
                            {
                                widget_points: 4, num_widgets: 4
                            },
                            {
                                widget_points: 5, num_widgets: 5
                            },
                        ],
                        color: "#ff0000"
                    },
                ],
                properties: {
                    title: 'Example Chart Data'
                }
            };

        this.total = 1;
    }
 ...
 })

You should also notice that at the end of the method is the line:

this.total = 1;

When rendering the view, this value is tested to see if data is available prior to rendering chart. With a hard coded example this does not matter much but when you are making an AJAX call for data then it matters since the response returns asynchronously. Other chart types use “total” differently but line charts simply evaluate if it is set to a non-zero value.

Invoke the chart model via the chart call() method

When rendering, the loadData() method is called and then later the renderChart() method is called, if it is defined. This method is used to draw the chart on the screen. We do this by using the built-in d3 call() method.

({
  ...
      renderChart: function () {
        if (!this.isChartReady()) {
            return;
        }

        d3.select(this.el).select('.single-chart-view svg')
            .datum(this.chartData)
            .transition().duration(500)
            .call(this.chart);

        this.chart_loaded = _.isFunction(this.chart.update);
    }
  ...
})

This method first calls isChartReady() to see whether the chart is ready to display or not. Recall the this.total value we set in loadData()?  That’s what this method is examining, among other things, to figure out whether it is appropriate to now draw the chart.

At the end of this method we set this.chart_loaded, indicating that the chart has indeed been drawn.

There are several parts to that long call() method chain that are important to understand. First, the string argument to select() is a CSS selector which points to the SVG element in our HTML where the chart will be drawn.

Second, the argument to the datum() method is the object we populated in the loadData() method. It is looking for the appropriately formatted data object.

Third, we attach whatever features of the d3 object we want. In this case, we set transitions to half a second (500 milliseconds). You can experiment with different values there to see what happens.

Finally, we pass the chart property of the view to the call() method. At that point the chart will be drawn.

To put it all together, here is the complete JavaScript controller:

({
    plugins: ['Chart'],
    className: 'single-chart-view',
    chartData: {},
    total: 0,
    initialize: function (options) {
        this._super('initialize', [options]);
        this.chart = nv.models.lineChart()
            .x(function (d) {
                return d.widget_points;
            })
            .y(function (d) {
                return d.num_widgets;
            })
            .showTitle(true)
            .tooltips(false);
    },
    loadData: function() {
        this.chartData = {
                data: [
                    {
                        key: "Blue Stuff",
                        values: [
                            {
                                widget_points: 1, num_widgets: 10
                            },
                            {
                                widget_points: 2, num_widgets: 9
                            },
                            {
                                widget_points: 3, num_widgets: 8
                            },
                            {
                                widget_points: 4, num_widgets: 7
                            },
                            {
                                widget_points: 5, num_widgets: 6
                            },
                        ],
                        color: "#0000ff"
                    },
                    {
                        key: "Red Stuff",
                        values: [
                            {
                                widget_points: 1, num_widgets: 1
                            },
                            {
                                widget_points: 2, num_widgets: 2
                            },
                            {
                                widget_points: 3, num_widgets: 3
                            },
                            {
                                widget_points: 4, num_widgets: 4
                            },
                            {
                                widget_points: 5, num_widgets: 5
                            },
                        ],
                        color: "#ff0000"
                    },
                ],
                properties: {
                    title: 'Example Chart Data'
                }
            };

        this.total = 1;
    },
    renderChart: function () {
        if (!this.isChartReady()) {
            return;
        }

        d3.select(this.el).select('#chart-section svg')
            .datum(this.chartData)
            .transition().duration(500)
            .call(this.chart);

        this.chart_loaded = _.isFunction(this.chart.update);
    }
})

Run a Quick Repair & Rebuild when finished. If you put the view in the layout described above, then you can see it by navigating to the following URL.

http://{your sugar server}/#Accounts/layout/single-chart-layout

and something like the following will appear:

single-chart-view.png

Look at that beautiful chart!

You, of course, are not limited in number of lines, colors that are used, or even to line charts alone. Users love charts, so I recommend spending some time to experiment with Sugar’s chart engine to see what they can do for you.


How to build Chart Layouts for Sugar 7 – Part 2

$
0
0

In our last post we covered the basics of adding a custom chart layout. Today’s post will build on that example to cover some of the more advanced configurations that are possible using charts in Sugar.  Like the previous post, this is targeted at a beginner to intermediate skilled Sugar Developer who is interested in building custom charts.

Multiple Charts On the Same View

Previously we explored how to display a single chart on a view. Displaying more than a single chart on the view is also very easy too.

In order to add a second chart, you may be tempted to create another chart object in the initialize() method but that is not necessarily how Sugar Charts works.  The “chart” property in the view controller is a Chart factory. Chart factories will not affect how your view is rendered unless you do something with them. You can use the same factory’s call() method to construct the same style of chart multiple times.

Example

We will create a custom layout and view exactly the way we did as before in the single chart example.  We will call this new example the “multi-chart-view”.

Set up the Handlebars template

To display multiple charts you need to provide multiple locations for the charts to exist. These locations are represented below by the div and the enclosed svg elements below. It is convenient to allow each chart div to have a unique id.

<div id="example-chart1">
    <svg></svg></div>
<div id="example-chart2">
    <svg></svg></div>
<div id="example-chart3">
    <svg></svg></div>

Populate data for multiple views

In our JavaScript code, we will obviously need to provide data for each chart. Here we create three separate properties on the view controller:  “bluered”, “purpleteal”, and “yellowgray”.  You’ll notice that each chart uses the same format as before.

({
...
    loadData: function() {
        this.bluered = {
            data: [
                {
                    key: "Blue Stuff",
                    values: [
                        {
                            x: 1, y: 10
                        },
                        {
                            x: 2, y: 9
                        },
                        {
                            x: 3, y: 8
                        },
                        {
                            x: 4, y: 7
                        },
                        {
                            x: 5, y: 6
                        },
                    ],
                    color: "#0000ff"
                },
                {
                    key: "Red Stuff",
                    values: [
                        {
                            x: 1, y: 1
                        },
                        {
                            x: 2, y: 2
                        },
                        {
                            x: 3, y: 3
                        },
                        {
                            x: 4, y: 4
                        },
                        {
                            x: 5, y: 5
                        },
                    ],
                    color: "#ff0000"
                },
            ],
            properties: {
                title: 'First Chart Data'
            }
        };
        this.purpleteal =             {
            data: [
                {
                    key: "Purple Stuff",
                    values: [
                        {
                            x: 1, y: 10
                        },
                        {
                            x: 2, y: 9
                        },
                        {
                            x: 3, y: 8
                        },
                        {
                            x: 4, y: 7
                        },
                        {
                            x: 5, y: 6
                        },
                    ],
                    color: "#ff00ff"
                },
                {
                    key: "Teal Stuff",
                    values: [
                        {
                            x: 1, y: 1
                        },
                        {
                            x: 2, y: 2
                        },
                        {
                            x: 3, y: 3
                        },
                        {
                            x: 4, y: 4
                        },
                        {
                            x: 5, y: 5
                        },
                    ],
                    color: "#00ffff"
                },
            ],
            properties: {
                title: 'Second Chart Data'
            }
        };
        this.yellowgray = {
            data: [
                {
                    key: "Yellow Stuff",
                    values: [
                        {
                            x: 1, y: 10
                        },
                        {
                            x: 2, y: 9
                        },
                        {
                            x: 3, y: 8
                        },
                        {
                            x: 4, y: 7
                        },
                        {
                            x: 5, y: 6
                        },
                    ],
                    color: "#ffff00"
                },
                {
                    key: "Gray Stuff",
                    values: [
                        {
                            x: 1, y: 1
                        },
                        {
                            x: 2, y: 2
                        },
                        {
                            x: 3, y: 3
                        },
                        {
                            x: 4, y: 4
                        },
                        {
                            x: 5, y: 5
                        },
                    ],
                    color: "#888888"
                },
            ],
            properties: {
                title: 'Third Chart Data'
            }
        }

        this.total = 1;
    },
 ...
 })

Call the Chart factory to display each chart

Now we make three separate calls to display each of the charts we want to display. You will notice that the unique ids we added to the div elements in the Handlebars template allow us to easily select each chart location. We also pass each a data object that we created above.

({
...
    renderChart: function () {
        if (!this.isChartReady()) {
            return;
        }

        d3.select(this.el).select('#example-chart1 svg')
            .datum(this.bluered)
            .transition().duration(500)
            .call(this.chart);
        d3.select(this.el).select('#example-chart2 svg')
            .datum(this.purpleteal)
            .transition().duration(500)
            .call(this.chart);
        d3.select(this.el).select('#example-chart3 svg')
            .datum(this.yellowgray)
            .transition().duration(500)
            .call(this.chart);

        this.chart_loaded = _.isFunction(this.chart.update);
    }
})

With this code in place, you can run a Quick Repair & Rebuild and navigate to the following URL.

http://{your sugar server}/#Accounts/layout/multi-chart-layout

You should then see each of your charts in the layout:

multi-chart-view.png

Any Number of Charts from an API Call

While we have shown how to get basic charts working, what we have done so far does not reflect the real world implementations very well.  You do not always know how many charts you need to display and you pretty much never are working with static data. Typically you will make a HTTP call to some REST API to provide data to display. Depending on the nature of that data you may want to display a variable number of charts.

In this example, we provide an example that makes a call to an API which returns a variable amount of data, therefore requiring a variable number of charts, so we can explore how to handle this.

Set up an API to call

For our example we will create a toy API for us to call.  It is simplistic in that it returns data in the exact format the LineChart expects.  In the real world you might have to transform the data in JavaScript a bit first. However, this will illustrate the important principles involved.

This API will be invoked using a simple HTTP GET to

http://{your sugar server}/rest/v10/Accounts/get_line_chart_data

which will return a randomly generated set of data for multiple charts with multiple lines. You can play with the numbers at the top of the method to give you different results.

custom/modules/Accounts/clients/base/api/chartInfoAPI.php

<?php if (!defined('sugarEntry') || !sugarEntry) {
die('Not A Valid Entry Point'); }
require_once 'clients/base/api/ModuleApi.php';
class chartInfoAPI extends ModuleApi {
public function registerApiRest() {
return array(
'webhook' => array(
                'reqType' => 'GET',
                'path' => array('Accounts', 'get_line_chart_data'),
                'pathVars' => array('module', 'action'),
                'method' => 'getLineChartInfo',
                'shortHelp' => 'This endpoint retrieves line chart information in the proper format',
                'longHelp' => '',
            ),
        );
    }

    /**
     * This method generates data for a line chart example and returns it in the following LineChart format:
     * array(
     *     array(
     *         'data' => array(
     *             array(
     *                 'key' => "Data set the first", // Title of this data set
     *                 'values' => array(
     *                     array(
     *                         'x' => {some number},
     *                         'y' => {some other number},
     *                     ),
     *                     ...
     *                 ),
     *                 'color' => "#ff0000", // Any color you want, of course
     *             ),
     *             ...
     *         ),
     *         'properties' => array(
     *             'title' => "Title of this chart",
     *         ),
     *     ),
     *     ...
     * )
     */
    public function getLineChartInfo($api, $args) {
        $out_data_arr = array();

        $num_charts = 3; // Total number of charts to display
        $num_lines = 2; // Number of lines per chart to display
        $num_data_points = 5; // Number of data points per line to display

        $color_map = array(
            'silver',
            'gray',
            'black',
            'red',
            'maroon',
            'yellow',
            'olive',
            'lime',
            'green',
            'aqua',
            'teal',
            'blue',
            'navy',
            'fuchsia',
            'purple',
        );

        for ($i = 0; $i < $num_charts; $i++) {             $tmp_chart_arr = array(                 'data' => array(),
                'properties' => array(
                    'title' => "Chart #" . ($i + 1),
                ),
            );
            for ($j = 0; $j < $num_lines; $j++) {                 // Pick a color for the line                 $line_color = $color_map[rand(0, count($color_map) - 1)];                 $tmp_line_arr = array(                     'key' => ucfirst($line_color) . " Data",
                    'values' => array(),
                    'color' => $line_color,
                );
                for ($k = 1; $k <= $num_data_points; $k++) {                     $tmp_data_point = array(                         'x' => $k,
                        'y' => rand(0, 10),
                    );
                    $tmp_line_arr['values'][] = $tmp_data_point;
                }
                $tmp_chart_arr['data'][] = $tmp_line_arr;
            }
            $out_data_arr[] = $tmp_chart_arr;
        }

        return $out_data_arr;
    }
}

Implement the Handlebars template

Since we have no idea how many charts we are going to need to display we cannot just set up div and svg elements ahead of time like we did for the previous example. Instead we will just have a generic div into which we’ll  add further div and svg elements dynamically within the Javascript below.

<div id="chart-section"></div>

Implement the loadData() method

Now we need to actually call our custom API. Here we call the API via Sugar’s built in app.api.call() method which takes care of access token management automatically. We take the results from the API call and assign them to the chartDataArr array for use during render. Take note of the assignment to self.total. Here it matters where this happens because it should not be set until the data is available and charts can be rendered.

({
    ...
    loadData: function() {
        var self = this;
        var url = app.api.buildURL('Accounts/get_line_chart_data');
        app.api.call('read', url, null, {
            success: function (response) {
                _.each(response, function (data, key) {
                    self.chartDataArr.push(data);
                });
                if (self.chartDataArr.length > 0) {
                    self.total = 1;
                } else {
                    self.errorMsg = "There is no chart information available";
                }

                self.render();
            },
            error: _.bind(function () {
                this.errorMsg = "Error encountered retrieving chart information";
            }, this)
        });
    },
    ...
})

Implement the renderChart() method

The renderChart() method gets a little more complex here. We iterate through the set of arrays we stored in chartDataArr in the loadData() method, each of which represents a chart to display. First we add a new div and svg element, with a unique identifier, in which to display the chart. Then we make the call to the call() method to actually display the chart to the new elements.

({
...
      renderChart: function () {
        if (!this.isChartReady()) {
            return;
        }

        var self = this;
        var id_val = "";
        var selector_str = "";
        _.each(this.chartDataArr, function (data, key) {
            id_val = 'example_chart' + key;
            selector_str = '#' + id_val + ' svg';
            $("#chart-section").append('
<div id="' + id_val + '"><svg></svg></div>
');
            d3.select(self.el).select(selector_str)
                .datum(data)
                .transition().duration(500)
                .call(self.chart);
        });

        this.chart_loaded = _.isFunction(this.chart.update);
    }
})

Here’s the JavaScript when you put it all together

({
    plugins: ['Chart'],
    className: 'api-chart-view',
    chartDataArr: [],
    total: 0,
    initialize: function (options) {
        this._super('initialize', [options]);
        this.chart = nv.models.lineChart()
            .x(function (d) {
                return d.x;
            })
            .y(function (d) {
                return d.y;
            })
            .showTitle(true)
            .tooltips(false);
    },
    loadData: function() {
        var self = this;
        var url = app.api.buildURL('Accounts/get_line_chart_data');
        app.api.call('read', url, null, {
            success: function (response) {
                _.each(response, function (data, key) {
                    self.chartDataArr.push(data);
                });
                if (self.chartDataArr.length > 0) {
                    self.total = 1;
                } else {
                    self.errorMsg = "There is no chart information available";
                }

                self.render();
            },
            error: _.bind(function () {
                this.errorMsg = "Error encountered retrieving chart information";
            }, this)
        });
    },
    renderChart: function () {
        if (!this.isChartReady()) {
            return;
        }

        var self = this;
        var id_val = "";
        var selector_str = "";
        _.each(this.chartDataArr, function (data, key) {
            id_val = 'example_chart' + key;
            selector_str = '#' + id_val + ' svg';
            $("#chart-section").append('
<div id="' + id_val + '"><svg></svg></div>
');
            d3.select(self.el).select(selector_str)
                .datum(data)
                .transition().duration(500)
                .call(self.chart);
        });

        this.chart_loaded = _.isFunction(this.chart.update);
    }
})

When everything is in place, do a Quick Repair & Rebuild and go to

http://{your sugar server}/#Accounts/layout/api-chart-layout

That should show you something like this

api-chart-view.png

Final Thoughts

The examples here are very simple. There are many chart types to choose from and many options for each chart type. Hopefully using these tutorials, the resources mentioned above, and some trial and error, you will then be able to build custom charts in your Sugar customizations.


Using e-mail fields correctly in Sugar

$
0
0

Here is an important message from David Wheeler, a long time Software Engineer and Architect at SugarCRM, about using e-mail fields correctly.

E-mail handling is core to CRM software. Almost everyone we know uses multiple e-mail addresses every single day for both personal or work purposes. So it goes without saying that managing a person’s multiple e-mail addresses correctly is essential in your Sugar customizations and integrations.

History of Sugar E-Mail fields

Several years ago, Sugar changed from using email# named text fields (like email1, email2, etc.) to using an e-mail relationship. This was done to better handle multiple e-mails, multiple relationships, and e-mail attributes like opt in or invalid.

However, use of the email1 field remains particularly persistent. We observe many examples of custom code (and some core code) that still use the old email# fields. This is probably because it is convenient to use the email1 field like a regular text field.

But this is out of date, inaccurate, deprecated, and subject to removal in upcoming Sugar releases.

Below we will describe the proper method for using e-mail fields within Sugar customizations and integrations.

Sugar Metadata

You should reference the “email” field instead of “email#”.

For record views, this will load a “email” type field widget with all related e-mail addresses included.

Email field example

Example of e-mail field in Record view

For list views, instead of all e-mail addresses only the primary e-mail address will be displayed.

e-mail list view screenshot

Example of e-mail field in List view

Sugar PHP code

Instead of

$bean->email#

use

$bean->emailAddress->addresses

which references an array of e-mail addresses.

To determine the primary e-mail, you can iterate over the addresses array to find where the primary_address attribute is true.

foreach ($bean->emailAddresses->addresses as $address) {
    if ($address->primary_address == true) {
        // Found primary e-mail
    }
}

v10 REST API

When selecting a record’s email field in a GET request, it will return a JSON array of all associated e-mail addresses.

When using a PUT request to update an e-mail address on a record, provide the complete e-mail address array.

For example,

"email": [
    {
        "email_address": "judith.banks@yahoo.com",
        "primary_address": true,
        "reply_to_address": false,
        "invalid_email": false,
        "opt_out": false
    },
    {
        "email_address": "jbanks@hotmail.com",
        "primary_address": false,
        "reply_to_address": false,
        "invalid_email": false,
        "opt_out": true
    }
],

If you leave off an e-mail address in a PUT request then this will be removed during the update to the record.

Sidecar code

For Sidecar code, you should not reference fields like email1, etc, when working with Beans or other models. You should be referencing the email field instead.

model.get("email1") --> model.get("email")

This will return a JavaScript array in the same format as returned by the REST API (above). This array of e-mails is typically iterated through for display in custom Handlebars templates.

PDF Templates

This change also applies to PDF templates. Here is the correct way to reference the primary e-mail address from a PDF template.

{$field.email1} --> {$fields.email_addresses_primary.email_address}

Other locations

You may still see email1 field referenced in e-mail templates used with Advanced Workflow or Campaigns module. Don’t count on those sticking around. Please, use the email field instead.

 


Get your hot hands on the Sugar Mobile SDK!

$
0
0

Greg Khanlarov, Director of Mobile Development, is so excited about the launch of the new Sugar Mobile SDK that he is speechless!

If you are coming to SugarCon, make sure you get your hot hands on the new Sugar Mobile SDK first! On Tuesday, you can join Greg for his presentation Sugar Mobile SDK deep dive. Next Wednesday, at the UnCon Tutorials by the Experts, you can meet Greg and other folks from our Mobile development team and learn how to build your first custom Sugar Mobile app.

Read on for more details on the Sugar Mobile SDK!

Are you building mobile CRM apps from scratch?  Is your team stuck deciding between Hybrid vs. Native?  Maybe you’re trying to deliver a premium mobile experience, but you don’t have the budget or skills.

STOP.  Take a deep breath and consider what we’ve spent the past two years building.

Sugar Mobile SDK – the first Mobile CRM SDK that favors convention over configuration.  Start with a proven mobile application in use by tens of thousands of users today, and add the pieces that are unique to your business.  Focus on creating value for users instead of uncertainty and risk.

What is Mobile SDK?

Mobile SDK enables developers to extend Sugar Mobile functionality beyond what is possible through studio configuration.  Common examples are:

  1. Custom fields, dashlets, views, buttons
  2. Support for mobile device management
  3. Styling, theming, navigation
  4. Native device capability integration (GPS, camera, etc.)

Extending Sugar Mobile using the SDK results in a custom mobile application that the author is responsible for building, maintaining and distributing.  Upgrade-safety and strong API contracts have been designed into the SDK to reduce the risk of upgrades breaking extension functionality.  

Developers should be familiar with:

  1. Sidecar and Sugar customization
  2. Mobile application development with javascript
  3. iOS and Android application publishing processes

A custom mobile app is a mobile app developed on top of the Mobile SDK. Specific configuration and branding makes a given mobile app custom. Additionally, a custom mobile app may contain custom business logic developed in JavaScript, HTML, and CSS.

How do I get access?

You can get access directly from the Mobile Tools Portal. The portal is restricted to partners and customers running Enterprise and above. If you are having problems with access, check out the On-Boarding new Sugar Developers guide in the Sugar Community.

What are the benefits of the Mobile SDK?

The Mobile SDK’s formalized APIs and guidelines will help you extend SugarCRM Mobile in an upgrade-safe manner. Sugar has done the heavy lifting by allowing you to extend the SugarCRM Mobile app that will speed up your development so you do not need to build your own application from scratch.  Developers will also benefit from new features added on ensuing upgrades of the SDK.

What does the SDK allow you to do?

Customization Examples
Custom Menus Ability to add custom main menu items
Ability to add custom right menu items
Custom field types Enable field validation
Custom fields such as signature fields
Custom Actions Geolocation for checkin-checkout
Launch another app (e.g. Skype)
Barcode/QR scanning
Override file actions
Custom Dashlets Add custom actions
Build custom Dashlets
External Source Dashlet
Filtered List Dashlet
Record Summary dashlet
Statistics Dashlet
Custom Views Build custom to-do lists
Modify table formatting
Extend the detail view
Extend the opportunities list view
Native Plug-ins The SDK already supports standard native capabilities such as geolocation and camera access.  In the event you need to add other native capabilities, there is a way to do this.

How do I get help or provide feedback?

All development questions should be directed to the Mobile Developers community.

If you need to file a defect or enhancement request, please submit a case to our support team.


Viewing all 27 articles
Browse latest View live