Archives For

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.

Continue Reading…

This blog will be 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.

Continue Reading…

This post is targeted at beginner to intermediate Sugar Developers who want to add a subpanel to a module which returns custom results.

This post assumes some basic knowledge of Sugar 7 development, Sugar 7 administration, PHP, and SQL.  This article will hopefully be useful to those who have done some Sugar 7 development but are not necessarily experts.

The example in this post was created on an out-of-the-box installation of Sugar Professional 7.5.2.1 with the generated demo data.  But this technique should work on any on-premise Sugar 7 instance.

An acknowledgement

I recently needed to add a subpanel to a module which returned rows matching some unusual criteria.  The requirement for this subpanel didn’t match the standard one-to-many or many-to-many relationship with another module that a subpanel usually reflects.  I researched ways to do this and came across an excellent blog post on the subject by Shane Dowling.  Following what he had written I was able to accomplish my task.  This post is an attempt to make this technique accessible to those who are less familiar with Sugar 7 development.

Continue Reading…

This article is aimed at beginning to intermediate SugarCRM developers who want to customize views in SugarCRM version 7.

This does not go into detail about all the ins and out of creating custom views, changing metadata and handlebars, etc.  This article merely points out a single technique for extending the JavaScript for an out of the box view to a custom view.  This technique also applies to layouts, but this article will concentrate on views.

This article assumes some knowledge of JavaScript and PHP.

Creating a Custom View From an Out of the Box View

When you create a custom view in SugarCRM 7 you create a subdirectory under

{SugarCRM base dir}/custom/clients/base/views/{new view name}

or

{SugarCRM base dir}/custom/modules/{module name}/clients/base/views/{new view name}

with the various files within it named for the view name.  An out of the box view is one you can find under

{SugarCRM base dir}/clients/base/views/

If you wanted to override one of these out of the box views you would simply create a custom view with the same name.  You can tweak the functionality of an out of the box view by simply copying its source files to your custom view and then customizing them from there.  Let’s say, for example, that you wanted to alter the behavior of the “record” view for a given module.  You can simply copy the out of the box record view locally

cd {SugarCRM base dir}
cp -r clients/base/views/record custom/modules/{module name}/clients/base/views/record

You’ll then need to alter the metadata file (in this case record.php) to reflect the new location

<?php
....
$module_name = "my_ModuleName";
$viewdefs[$module_name]['base']['view']['record'] = array(
    'buttons' => array(
    array(
        'type' => 'button',
        'name' => 'cancel_button',
        'label' => 'LBL_CANCEL_BUTTON_LABEL',
        'css_class' => 'btn-invisible btn-link',
    ),
    ...

If your customizations involve JavaScript changes you’ll want to use the un-minified version of the JavaScript file to work against.  You can copy it from the jssource directory.  In this case you’d run the following command:

cp jssource/src_files/clients/base/views/record/record.js custom/modules/{module name}/clients/base/views/record

At this point you can change the various local files to your heart’s content, and anytime the “record” view is used within your module, your custom view will be used instead.

Like all changes to views and layouts, you’ll need to do a Repair and Rebuild before you see your changes take effect.  When you change the JavaScript file you’ll also need to clear your browser cache.

The Problem With Overriding An Out of the Box View

That’s wonderful until you upgrade.  The problem is that when you upgrade, the out of the box record view might receive changes but your local version of the “record” view won’t.  Over time your custom view will become more and more out of step with the out of the box view.  It could even result in your view breaking eventually.  This isn’t much of a problem for the metadata file since that’s not likely to change much from version to version.  The JavaScript file, however, can change a lot.  It would be nice if there were a way to get the benefit of the improved JavaScript while still keeping a customized version of the view.

Happily this is possible by extending the JavaScript file rather than completely overriding it.

To extend the JavaScript file you simply use JavaScript’s “extendsFrom” functionality.  In our example, to extend the record view the record.js file would look something like this:

({

extendsFrom: 'RecordView',

initialize: function(options) {
    console.log("We are using my customized record view, Yay!");
    this._super('initialize', [options]);
}

})

This JavaScript file will work exactly like the original except that it will now write a message to the JavaScript console whenever the view is used.

The tricky part of this is figuring out what to put in the “extendsFrom” part.  The name follows the form {Module}{Layout|View}.  If you’re extending the JavaScript for a view the {Layout|View} part will, of course, be “View”.  For the {Module} part you take the name of the view and capitalize it.  If the name has dashes in it, remove the dashes and uppercase the following word.  For example, if you were extending the JavaScript for the “filter-module-dropdown” view, the module part would turn into “FilterModuleDropdown” and the entire class to extend would become “FilterModuleDropdownView”.

If you’re ever unsure about what the class may be for the view or layout you’re extending, you can run one of the following commands in the JavaScript console and look at the results.  They should contain the names of all the view and layout classes recognized by the application:

SUGAR.App.view.views;
SUGAR.App.view.layouts;

Calling the “_super” method within an overridden method is important to get the functionality of the parent method.  You can leave it out, but then your local method must do everything the parent method did.

This technique also works if you create a custom view of a different name based on an out of the box view.  There’s no rule saying that what follows “extendsFrom” must match the name of the local view.  It’s merely the JavaScript class you’re inheriting from.

Since you inherit from the parent JavaScript class rather than completely overriding it, and use the _super() method within overridden methods, when you update you’ll automatically get the improved functionality of the base class.

A Quick Caveat

When you extend the JavaScript for a view in this manner there’s always the risk that the changes made in a given update will clash with the customizations you’ve made in your local view.  It depends on the nature of the customizations you choose to make.  There’s no good way to completely guard against that other than never touching the Javascript at all.  Extending a view this way does minimize this risk because you override only what you must rather than the entire JavaScript file.  You should, however, remain aware of the customizations you make and test them thoroughly when you update before going live.

This article is aimed at beginning to intermediate SugarCRM developers who want to create custom filters in SugarCRM version 7.

This does not cover the custom filters that users can create for themselves.  Those are stored on a per-user basis and are not available to all users.  Instead, this document will cover creating filters that all users of the system will have access to.

This article assumes some knowledge of SQL, PHP, Unix, and general web stuff.

About Filters

In Sugar 7 parlance a filter is used to control what rows of data are displayed to the user in a given view.  Sugar has a number of core filters which come with Sugar out of the box.  When you go to a page with a layout that makes use of the filter view and click on the “Filter” pulldown menu you’ll see them displayed as options:

Image

When you choose one of those filters you’ll notice that the rows that show up in the list view change.  The filter supplies certain characteristics the data must meet before the row is displayed in the view.

Filters can be extremely useful when you want to limit what the user sees and not just present every row in the table to the user.

How to create a very basic filter

The core filters are very convenient since you don’t have to do anything to make them show up. However, it’s not unusual to have a need for a more customized filter that sets criteria specific to your individual needs.  No core filter will be able to supply that.  Fortunately, SugarCRM 7 provides a way to  create such a filter without having to descend into the core functionality of the product and hack something into place.

So let’s dive in and create a very simple filter.

For the purposes of this document I have gone into Studio and created a module called “Random Custom Stuff” with a module name of “flt_RandomCustom”.  I know that’s a weird name, but it’s how we do things where I work, so I went with it.  I also added a custom field to the module to hold a random number.  This created both the “flt_randomcustom” and “flt_randomcustom_cstm” tables (among others) in the database.  That will be more interesting later.

When I go to

http://{my instance}/#flt_RandomCustom

I see the following:

Image

You see all the rows for this module displayed because the default filter is “All Random Custom Stuff” which simply shows everything.  We’ll change that by creating a new filter.

Let’s create a filter to only show rows which have a name of “Blorg” and call it the “Blorg Only” filter.  Let’s say that you installed SugarCRM in

/var/www/sugarcrm

The first thing to do would be to create a directory for your new filter.  Like other components such as layouts and views, you create a custom filter by adding a directory and files under the
“custom/” directory in the SugarCRM instance.  We want this filter to be available only to this module so we’ll create the directory:

/var/www/sugarcrm/custom/modules/flt_RandomCustom/clients/base/filters/blorgonly

At first glance that looks like a crazy path, but if you’ve done any other customization of SugarCRM 7 you’ll recognize that this fits with layouts, views, and other customizations.

In that directory create a file “blorgonly.php” which contains the following text:

<?php
$module_name = "flt_RandomCustom";
$viewdefs[$module_name]['base']['filter']['blorgonly'] = array(
    'create'               => false,
    'filters'              => array(
        array(
            'id'                => 'blorgonly',
            'name'              => 'LBL_BLORG_ONLY_FILTER',
            'filter_definition' => array(
                array(
                    'name' => 'Blorg',
                ),
            ),
            'editable'          => false,
        ),
    ),
);

It’s very important that the name of the PHP file match the name of the directory which needs to match the name of the filter.  You’ll notice in the code above that we specify the name of the filter in two places, once as the element of the “filter” viewdefs array, and again as the “id” of the filter.  Both are important.  For the “name” we’ve put in a placeholder for a label string.  Don’t worry, we’ll get to that in a minute.

The real action takes place in the “filter_definition” array.  You can see that we have a single element that says that the “name” has to be “Blorg”.  The “name” field of the array under “filter_definition” is referring to the name of the actual column in the underlying table (flt_randomcustom in this case).  And, just to make things confusing, the name of that column is “name”.

To make it actually work you have to go to the Admin menu, click on Repair, and then click on “Quick Repair and Rebuild”.

When you reload http://{my instance}/#flt_RandomCustom and click on the filter pulldown you should see something like this:

basic_in_place

When you click on the new filter you should see only the row with “Blorg” as the name appear.  My friend, you’ve just created your first custom filter in SugarCRM 7.  Savor the moment.

Now, if you don’t want a reputation for shoddy work, you’re going to want to make that label look like something real.  To do that we have to set up a string for it.  To do that we create another PHP file:

/var/www/sugarcrm/custom/Extension/modules/flt_RandomCustom/Ext/Language/en_us.RCFilt.php

This filename has to start with “en_us.” and end with “.php” but it can be named anything within that.  The contents of that file should be something like:

<?php
$mod_strings['LBL_BLORG_ONLY_FILTER'] = 'Only Blorg Rows';

Once you have that file in place, do the “Quick Repair and Rebuild” again.  Reloading the page and clicking on the filter pulldown should now look like this:

filter_label_done

And there you have it, a simple custom filter users can employ within a custom module.

Making more complex filters

Filters which restrict rows to those matching a single value are all fine and good, you say, but not much use in the real world.  Reality demands much more complex criteria for filters.  I completely agree.  Let’s see if we can construct a less trivial filter.

Constructing a complex filter is exactly the same as the simple filter above except that the “filter_definition” array contents are more complicated.

Here’s where knowledge of SQL comes in handy.  The way that the filter_definition array works is not unlike the WHERE clause of a SELECT statement.  Let’s consider this example:

'filter_definition' => array(
    array(
        'random_number_c' => '17',
        'date_entered' => '2014-01-22 13:21:01',
    ),
),

By default, things that are grouped together within arrays have an implied AND relationship.  In this case the filter will only show rows with a random number of 17 and the given date_entered value.  By the way, did you notice that the random number field ends with “_c”?  That’s right, you can specify custom fields in these filters as well.  They just get treated like the columns of the main table.

Okay, that’s slightly cooler, but how would you do an OR instead of an AND?

The filter mechanism has these command directives which start with $ that you can use.  In this case we want to make use of the “$or” directive:

'filter_definition' => array(
    array(
        '$or' => array(
            array('random_number_c' => '17'),
            array('random_number_c' => '827'),
        ),
    ),
),

This array groups together two conditions with the $or directive which means that rows with either value for that column should be shown.  You’ve probably noticed that the two conditions need to be wrapped in arrays of their own within the $or array.  That’s important.  Bad stuff happens if you forget to do that.  Also, don’t make the mistake of using double-quotes or PHP will try to interpret the $or as a variable and fail.

So now let’s try to construct a really complex filter.

Let’s say you need to create a filter that’s the equivalent of this WHERE clause:

WHERE
(
    date_entered = "2014-03-01 19:54:47"
    OR (
        random_number_c > 0
        AND random_number_c < 40
    )
)
AND (
    name LIKE “A%"
    OR name LIKE "B%"
    OR name LIKE "R%"
)

Wow.  Okay, so it’s kind of tough to just rattle off the PHP necessary to do this, so let’s break it down logically.  First, we have two big conditions AND’ed together which implies:

'filter_definition' => array(
    array(
        //The thing with ‘date_entered’ and other stuff
    ),
    array(
        //The thing with the three ‘name’ conditions
    ),
),

The second condition is a little more straightforward, so let’s handle that one first.  To reproduce the behavior with the LIKE’s above, SugarCRM filters have a $starts directive which tests for whether the value of a string starts with the given value.  So we would use it as follows:

'filter_definition' => array(
    array(
        //The thing with the ‘date_entered’ and other stuff
    ),
    array(
        '$or' => array(
            array(
                'name' => array(
                    '$starts' => 'A',
                ),
            ),
            array(
                'name' => array(
                    '$starts' => 'B',
                ),
            ),
            array(
                'name' => array(
                    '$starts' => 'R',
                ),
            ),
        ),
    ),
),

While that may look intimidating, actually it makes sense.  It’s just three $starts conditions OR’ed together.

Now let’s attack the first bit.  What if all we had to do was do the two tests for the random number?  It would look like this:

'filter_definition' => array(
    array(
        array(
            'random_number_c' => array(
                '$lt' => '40',
            ),
        ),
        array(
            'random_number_c' => array(
                '$gt' => '0',
            ),
        ),
    ),
    array(
        //The thing with the three ‘name’ conditions
    ),
),

That’s not too bad, right?  Now let’s OR in the date_entered test:

'filter_definition' => array(
    array(
        '$or' => array(
            array(
                '$and' => array(
                    array(
                        'random_number_c' => array(
                            '$lt' => '40'
                        ),
                    ),
                    array(
                        'random_number_c' => array(
                            '$gt' => '0'
                        ),
                    ),
                ),
            ),
            array(
                'date_entered' => '2014-03-01 19:54:47'
            ),
        ),
    ),
    array(
        //The thing with the three ‘name’ conditions
    ),
),

Okay, I admit it, that’s a little weird.  You have to explicitly use the $and directive because in this case it gets confused if you leave the AND implied as before.  So let’s put the whole filter together in all its glory:

'filter_definition' => array(
    array(
        '$or' => array(
            array(
                '$and' => array(
                    array(
                        'random_number_c' => array(
                            '$lt' => '40'
                        ),
                    ),
                    array(
                        'random_number_c' => array(
                            '$gt' => '0'
                        ),
                    ),
                ),
            ),
            array(
                'date_entered' => '2014-03-01 19:54:47'
            ),
        ),
    ),
    array(
        '$or' => array(
            array(
                'name' => array(
                    '$starts' => 'A',
                ),
            ),
            array(
                'name' => array(
                    '$starts' => 'B',
                ),
            ),
            array(
                'name' => array(
                    '$starts' => 'R',
                ),
            ),
        ),
    ),
),

Sadly, the filter mechanism lacks some of the features of an SQL WHERE clause such as NOT, a true LIKE, and a few other things.  But you can handle most conditions with filters.

For a complete list of directives and additional documentation you can go to:

http://{your instance}/rest/v10/help

Search for “/<module>/filter” next to “GET” and click on it.

Making your filter the default filter

Normally with a layout that includes the filter pulldown the filter that shows all rows is the default.  This is usually fine, but sometimes it’s not ideal.  It’s pretty easy to set a custom filter as the default filter for a module.

To do this you need to create a “default/” directory in the same “filters/” directory as your other custom filters.  In this directory you create a “default.php” file.  But instead of the filter code you saw above, the file would instead contain something like this:

<?php
$module_name = 'flt_RandomCustom';
$viewdefs[$module_name]['base']['filter']['default'] = array(
    'default_filter' => 'blorgonly',
);

As I imagine you figured out, this will set the default filter to the blorgonly filter we created earlier.  To see this work you sometimes have to clear your browser cache completely after doing the repair and rebuild, but before reloading the page.

Debugging techniques

Remember the horrible complex filter we saw above?  It was as ugly to write as it is to read.  Developing it took a lot of iterations.  There aren’t a lot of tools which will help you figure things out, but there are a few.

Examining the API Call

When SugarCRM 7 tries to apply a filter it makes a GET call to the appropriate filter API.  If you open the JavaScript console on your browser (or Firebug, or whatever your tool of choice is) you can see the API call go by along with any errors.  It can sometimes be helpful to pull out the URL it calls and examine it to see what it might be trying to do.

Here’s the URL for the API call for the big ugly filter (with the various entities replaced with their original characters, of course):

http://{whatever+your+intance+is}/rest/v10/flt_RandomCustom/filter?fields=name,random_number_c,date_entered,my_favorite&max_num=20&order_by=date_modified:desc&filter[0][$or][0][$and][0][random_number_c][$lt]=40&filter[0][$or][0][$and][1][random_number_c][$gt]=0&filter[0][$or][1][date_entered]=2014-03-01+19:54:47&filter[1][$or][0][name][$starts]=A&filter[1][$or][1][name][$starts]=B&filter[1][$or][2][name][$starts]=R

Yeah, yikes.  But, you can ignore everything up to the first &filter parameter which leaves:

&filter[0][$or][0][$and][0][random_number_c][$lt]=40&filter[0][$or][0][$and][1][random_number_c][$gt]=0&filter[0][$or][1][date_entered]=2014-03-01+19:54:47&filter[1][$or][0][name][$starts]=A&filter[1][$or][1][name][$starts]=B&filter[1][$or][2][name][$starts]=R

That’s still not wonderful, but if you split it out by parameter, you get:

&filter[0][$or][0][$and][0][random_number_c][$lt]=40
&filter[0][$or][0][$and][1][random_number_c][$gt]=0
&filter[0][$or][1][date_entered]=2014-03-01+19:54:47
&filter[1][$or][0][name][$starts]=A
&filter[1][$or][1][name][$starts]=B
&filter[1][$or][2][name][$starts]=R

Which actually starts to make some sense, sort of.  Note, for example, how there are two main arrays, just like the arrays we have in our filter_definition array.

Watching how these parameters change as you change your filter code can shed light on how you need to arrange your arrays and directives.  It’s not a slam dunk by any means, but it can help when you’re stuck.  You’ll often see things grouped together in ways you didn’t expect which can point to a problem.

Actually calling the API

Sometimes it’s helpful to actually make the API call that SugarCRM makes so you can see everything in detail including any errors, warnings, and any returned rows.  You can also mess with the parameters directly to see what effect that has on the results.  Fortunately it’s not that hard to do.  Here’s what you need to do:

Bring up your favorite utility for making API calls.  For example, POSTman in Chrome, or RESTClient in Firefox are good choices.  For the purposes of this explanation we’ll use POSTman in Chrome, but the basic ideas are the same regardless of the tool you choose.

Bring up the JavaScript console in Chrome, select the “Network” tab, and reload your page with your filter selected.  You should see a bunch of calls go by.  Once the page is loaded, from the bottom up, hover over each call until you find a one resembling:

http://{your instance}/rest/v10/{your module name}/filter?...

That’s the API call that Sugar makes to apply the filter to the rows.

Click on that entry and make sure the “Headers” tab is selected.  This should show you, among many other things, the OAuth-Token.  Save that string.

java_console

Copy the “Request URL” into the url line for POSTman, set the method to GET, and add OAuth-Token to the header with a value of the string you just saved.

When you hit “Send” you should get a result with a return status of 200 and a “records” array appropriate for your filter.

This too isn’t necessarily a slam dunk in terms of debugging, but sometimes you’ll see errors or other things which may point to the problem.  Also you can manually change the parameters to experiment with different things to try to solve the problem.