Skip navigation
All Places > Developer > Blog > 2016 > January
2016
Here is a guest post from Shijin Krishna from BHEA, an Elite SugarCRM Partner, and active member of the Sugar Developer community.Are you interested in posting on the Sugar Developer Blog? Contact developers@sugarcrm.com with your idea.

 

In this post we are going to see how we can add a quick create pop-up view for related modules in record view. This idea is taken from Sugar Portal which comes with Sugar Enterprise, Ultimate and Corporate editions. Sugar Portal’s detail view for Bugs and Cases modules includes an action to add a note to the bug or case. In this post, we are going to see how we can create a similar quick create pop up on the base Sugar 7 web client which will be shown at the click of a button.

 

We will use Cases and Notes modules as our basis.  We shall place a new ‘Add a Note’ button in Cases record view.  This button will be wired to display a pop-up create view for adding notes on the current case record.

The terms modal and popup are sometimes used interchangeably in Sugar 7 codebase and documentation. A modal dialog requires that a user interacts with it before they can continue. In many frameworks such as Bootstrap, modals are implemented as a child frame that "pops up" on the application screen.  In Sugar 7, Drawers are more commonly used than popups.

 

Adding a new Record View button

 

The following steps will create a new custom button on the Cases module’s Record View and wire it to a JavaScript callback function. These steps has been taken from Matt’s blog post on Leveraging Backbone Events in Sugar.

 

Step 1: Insert Button Metadata

 

We need to add Button metadata to the existing Cases record view using a Sidecar Extension. This defines the Sidecar Event to be triggered as well as the target object for the event.custom/Extension/modules/Cases/Ext/clients/base/views/record/add_note_button.php

 

[gist add_note_button.php

<?php
//Insert our custom button definition into existing Buttons array before the edit button
array_splice($viewdefs['Cases']['base']['view']['record']['buttons'], -2, 0, array(
array(
'name' => 'add_note',
'type' => 'button',
'label' => 'LBL_ADD_NOTE',
'css_class' => 'btn-success',//css class name for the button
'events' => array(
// custom Sidecar Event to trigger on click.  Event name can be anything you want.
'click' => 'button:add_note:click',
        )
    ),
));

]

 

Step 2: Defining the Sidecar Event to be triggered on click of the button

 

To define the event to be triggered on click of our custom button, we will override the record view controller for Cases.custom/modules/Cases/clients/base/views/record/record.js

 

[gist cases_record.js

 /**
  * @extends View.Views.Base.RecordView
*/
({
extendsFrom:'RecordView',
initialize:function(options){
this._super('initialize',[options]);
this.context.on('button:add_note:click',this._openNoteModal,this);
},
/**Function to open the note create pop-up*/
_openNoteModal: function() {
/**add class content-overflow-visible if client has touch feature*/
if (Modernizr.touch) {
app.$contentEl.addClass('content-overflow-visible');
  }
/**check whether the view already exists in the layout.
   * If not we will create a new view and will add to the components list of the record layout
   * */
var quickCreateView = this.layout.getComponent('quick-create');
if (!quickCreateView) {
/** Prepare the context object for the new quick create view*/
var context = this.context.getChildContext({
    module: 'Notes',
    forceNew: true,
    create: true,
    link:'notes', //relationship name
   });
context.prepare();
/** Create a new view object */
   quickCreateView = app.view.createView({
    context:context,
    name: 'quick-create',
    layout: this.layout,
    module: context.module,
   });
/** add the new view to the components list of the record layout*/
this.layout._components.push(quickCreateView);
this.layout.$el.append(quickCreateView.$el);
  }
/**triggers an event to show the pop up quick create view*/
this.layout.trigger("app:view:quick-create");
},
})

]

 

Adding a new Quick Create View for Notes Module

 

Our next step is to create a new custom quick create popup view for Notes module. We will extend our custom view from the Baseeditmodal view.

 

File Structure

 

We’re going to create a folder in custom/modules/Notes/clients/base/views/ called “quick-create“. Inside that folder we will create 3 files:

  • quick-create.php
  • quick-create.js
  • quick-create.hbs

 

Your file system should look something like the following screenshot.

 

Step 1: Implement the Quick Create View Metadata (.php file)

 

The following metadata can be modified according to your needs. If you wish to see more fields in the quick create popup then you can add those fields as well to the fields array.custom/modules/Notes/clients/base/views/quick-create/quick-create.php

 

[gist quick-create.php

<?php
/** Metdata for the add note custom popup view
* The buttons array contains the buttons to be shown in the popu
* The fields array can be modified accordingly to display more number of fields if required
* */
$viewdefs['Notes']['base']['view']['quick-create'] = array(
'buttons' => array(
array(
'name' => 'cancel_button',
'type' => 'button',
'label' => 'LBL_CANCEL_BUTTON_LABEL',
'value' => 'cancel',
'css_class' => 'btn-invisible btn-link',
        ),
array(
'name' => 'save_button',
'type' => 'button',
'label' => 'LBL_SAVE_BUTTON_LABEL',
'value' => 'save',
'css_class' => 'btn-primary',
        ),
    ),
'panels' => array(
array(
'fields' => array(
0 =>
array(
'name' => 'name',
'default' => true,
'enabled' => true,
'width' => 35,
'required' => true //subject is required
                ),
1 =>
array(
'name' => 'description',
'default' => true,
'enabled' => true,
'width' => 35,
'required' => true, //description is required
'rows' => 5,
                ),
2 =>
array(
'name' => 'filename',
'default' => true,
'enabled' => true,
'width' => 35,
                ),
            )
        )
    )
);

]

 

Step 2: Implement the Quick Create View Controller (.js file)

 

Here is the JavaScript controller for our quick create view. We will extend our view from BaseeditmodalView.custom/modules/Notes/clients/base/views/quick-create/quick-create.js

 

[gist quick-create.js

 /**
  * @class View.Views.Base.QuickCreateView
  * @alias SUGAR.App.view.views.BaseQuickCreateView
  * @extends View.Views.Base.BaseeditmodalView
*/
({
    extendsFrom:'BaseeditmodalView',
    fallbackFieldTemplate: 'edit',
initialize: function(options) {
app.view.View.prototype.initialize.call(this, options);
if (this.layout) {
this.layout.on('app:view:quick-create', function() {
this.render();
this.$('.modal').modal({
       backdrop: 'static'
      });
this.$('.modal').modal('show');
$('.datepicker').css('z-index','20000');
app.$contentEl.attr('aria-hidden', true);
$('.modal-backdrop').insertAfter($('.modal'));

/**If any validation error occurs, system will throw error and we need to enable the buttons back*/
this.context.get('model').on('error:validation', function() {
this.disableButtons(false);
      }, this);
     }, this);
    }
this.bindDataChange();
   },
/**Overriding the base saveButton method*/
saveButton: function() {
var createModel = this.context.get('model');

this.$('[name=save_button]').attr('data-loading-text', app.lang.get('LBL_LOADING'));
this.$('[name=save_button]').button('loading');

/** Disable the buttons during save.*/
this.disableButtons(true);
this.processModel(createModel);

/** saves the related note bean*/
createModel.save(null, {
     relate: true,
     fieldsToValidate: this.getFields(this.module),
     success: _.bind(function() {
this.saveComplete();
     }, this),
     error: _.bind(function() {
this.disableButtons(false);
     }, this)

    });
   },
/**Overriding the base cancelButton method*/
cancelButton: function() {
this._super('cancelButton');
app.$contentEl.removeAttr('aria-hidden');
this._disposeView();
   },
/**Overriding the base saveComplete method*/
saveComplete: function() {
this._super('saveComplete');
app.$contentEl.removeAttr('aria-hidden');
this._disposeView();
   },
/**Custom method to dispose the view*/
_disposeView:function(){
/**Find the index of the view in the components list of the layout*/
var index = _.indexOf(this.layout._components,_.findWhere(this.layout._components,{name:'quick-create'}));
if(index > -1){
/** dispose the view so that the evnets, context elements etc created by it will be released*/
this.layout._components[index].dispose();
/**remove the view from the components list**/
this.layout._components.splice(index, 1);
    }
   },
  })

]

 

Step 3: Implement the Quick Create View Template (.hbs file)

 

Below is the Handlebars template for our custom quick create view. We need to add some CSS style for the popup to make it look better. We have added a new CSS class ‘quick-create’ for the modal element so that we can selectively apply new styling to our quick create popup without affecting the style of other modal popups being used elsewhere in Sugar.

 

The template has a form which contains the fields and buttons defined in view metadata. We have used a book icon in the modal header from FontAwesome. If you are trying this example in any sugar version prior to 7.6.x then you may need to make sure you are using a different FontAwesome class name for this icon. Refer to this blog post for more details.

 

You can find the available list of icons in Sugar Styleguide accessible from the Administration panel of your Sugar instance.custom/modules/Notes/clients/base/views/quick-create/quick-create.hbs

 

[gist quick-create.hbs

<div class="modal hide quick-create">
    <div class="modal-header">
        <a class="close" data-dismiss="modal"><i class="fa fa-times"></i></a>
        <h3><i class="fa fa-book"></i>  {{str "LBL_CREATE_NOTE" module}}</h3>
    </div>
    <div class="modal-body">
        <form class="form-horizontal" enctype="multipart/form-data" method="POST">
            <fieldset>
{{#each meta.panels}}
{{#each fields}}
                <div class="row-fluid control-group">
                <label class="span3">{{str this.label ../../this.model.module}}</label>
                <div class="span9">{{field ../../this model=../../context.attributes.createModel template="edit"}}</div>
                </div>
{{/each}}
{{/each}}
            </fieldset>
        </form>
    </div>
    <div class="modal-footer">
{{#each meta.buttons}}
{{field ../this model=../createModel}}
{{/each}}
    </div>
</div>

]

 

Step 4: Add custom CSS style for our Popup (.less file)

 

Feel free to change the style as you wish!custom/themes/custom.less

 

[gist quick_create_popup_custom.less

.quick-create{
border:none;
}
.quick-create .modal-header h3 {
font-size:15px;
font-weight: normal;
}
.quick-create .modal-header{
background: #610319;
color: #FFF;
}
.quick-create .modal-body{
padding: 5px 0 58px 12px;
}
.quick-create .modal-body .row-fluid {
margin-top: 10px;
}
.quick-create label.span3{
font-size: 13px;
color: #797979;
}
.quick-create .fa-book{
color: #FFF;
}

]

 

Step 5: Define the display labels for our new UI

 

Our final step is to define the display label for the custom record view button and for the quick create pop up header.custom/Extension/modules/Cases/Ext/Language/add_note.en_us.lang.php

 

[gist quick_create_view.en_us.lang.php

<?php
$mod_strings['LBL_ADD_NOTE'] = 'Add a Note';
$mod_strings['LBL_CREATE_NOTE'] = 'Create Note';

]

 

Step 6: Quick Repair and Rebuild

 

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

 

After this, you will be ready to use your new quick create popup dialog. Visit the Cases module and click the Add a Note button and you should see a dialog like this one below.

Below is some details from Andreas Sandberg, Director of Engineering, at SugarCRM.  Andreas is on his second tour of duty at SugarCRM.  He first joined SugarCRM back in 2007 as an Engineering manager.  He was also the Head of Engineering at Stitch prior to their acquisition by SugarCRM.

 

Below he shares some information about some changes that have gone into Sugar 7.7 that change how PHP sessions are stored and the underlying implementation.  Sugar Developers need to be aware of these changes so that their interactions with $_SESSION variable do not yield any unpredicted results.

 

What has changed with $_SESSION?

 

We have refactored PHP Session storage so that it is more configurable and allow more control over PHP session handling.  We have introduced a new SessionStorage class at src/Session/SessionStorage.php which extends from the TrackableArray class at src/Util/Arrays/TrackableArray/TrackableArray.php. This means that the $_SESSION variable is now an object instance of a TrackableArray which implements the ArrayAccess interface. This means that most code that treated $_SESSION like an array will continue to work as before.

 

However, there are three changes that Sugar Developers need to be aware of when working with the PHP session objects.  Each change is outlined below. Two of them are API level changes and the last is really just an adherence to best practices but may yield unexpected results if ignored.

 

Session Array Functions

 

In Sugar 7.7, certain array functions will now throw fatal errors when used on the $_SESSION variable.  PHP's native array functions are not safe to use against ArrayAccess objects.  To proactively catch these issues, we have added a health check script to Sugar 7.7 upgrader.  This will warn about use of standard PHP array functions against the $_SESSION variable.

 



 

We have modified all areas in the core Sugar 7.7 codebase where this would be problem and have introduced new array utility functions that must be used instead of the standard PHP array functions.

 

These functions can be found in: src/Util/Arrays/ArrayFunctions/ArrayFunctions.php

These Sugar Functions are safe to use with native PHP Arrays and PHP ArrayAccess implementors such as Sugar's Session object.
Sugar Function
PHP Equivalent
is_array_access
is_array
in_array_access
in_array
array_access_keys
array_keys
array_access_merge
array_merge

 

 

 

(Pre|post)(Increment|Decrement) Operators

 

With the new implementation, pre and post increment or decrement operators will not work as expected and therefore should be avoided.

 

For example instead of doing something like:BAD 

$_SESSION['userStats']['pages']++;

 

Sugar Developers should instead use:GOOD

$_SESSION['userStats']['pages'] += 1;

 

 

 

Assigning unset session references to a variable

 

There are some improper programming practices that can cause problems. Specifically, when trying to access a session value by key that does not exist.  When that happens, an unexpected value will be assigned.

 

For example:BAD

//No session value for key authenticateduser_language exists in this example
$GLOBALS['current_language'] = $SESSION['authenticateduser_language'];

 

In this example our friend the TrackableArray will be assigned to the GLOBAL array instead of a string.

 

To ensure this never happens always proactively guard against possible null values:GOOD

$GLOBALS['current_language'] = !empty($_SESSION['authenticated_user_language']) ? $_SESSION['authenticated_user_language'] : $GLOBALS['sugar_config']['default_language'];

 

 

 

Returning a session value that is an array

 

You can prevent unexpected behavior when you expect a session value to be an array by using array type casting.  This will ensure that an array is always returned.BAD

return $_SESSION['reports_getACLAllowedModules'];
GOOD
return (array) $_SESSION['reports_getACLAllowedModules'];

 

 

 

The "Works On My Machine" Problem.

 

How often have you reported a software bug and had it rejected with a terse response such as "could not reproduce"?

 

You press the issue with your colleague and the conversation usually goes something like this:

You:  Here, look at my screen.  You can see the problem happening right here.

Them: Whatever. It works on my machine. L8tr dude.

 

Doesn't this drive you crazy?

 

Ultimately, even if you are both running the exact same source code there are always factors related to configuration and platform environment that can affect how an application behaves.

 

The flexibility of Sugar that we all love also means that we are often running Sugar in multiple different places and in varying configurations. For example, your team could be running Sugar instances in the cloud or on-premise, on real or virtualized hardware, on different operating systems, database servers, etc.

 

How do you keep track of all these different configurations, let alone try to make them as consistent as possible to avoid the "works on my machine" problem?

 

Introducing Vagrant and Packer

Vagrant is a tool built by Hashicorp.  It is a tool designed to make managing development environments really easy.  If an existing Vagrant box exists, you are never more than two commands away from launching a development environment that you need.  But it is a tool designed specifically for developers.  It is not the right tool for hosting customer instances or for doing QA because it does not alone replicate a real operational environment.  Also, if a Vagrant box doesn't already exist for the platform you want to work with then you need to create one.  This is where Packer comes in.Packer is also built by Hashicorp, the same folks behind Vagrant.  Here is the definition of Packer straight off their website.
Packer is a tool for creating machine and container images for multiple platforms from a single source configuration

 

SugarCRM Engineering uses Packer as the base tool for creating all the images we use for development, continuous integration, and QA.

 

Packer can be used to create Virtualbox or VMWare images, Amazon AMIs, Vagrant boxes, or even Docker containers all from a single source.  Recall Cedric's recent post on using Docker containers to run Sugar.  There is even a Null builder that you could use to run the same provisioners used to build your images on existing machines.

 

Below we will explore how you can use Packer to build a consistent Sugar 7 ready LAMP stack for Vagrant or on any other platform of your choice.  The code from this post is all available in a Sugar 7 Ready template Github repository.

 

Using Packer to create a Sugar 7 LAMP stack

 

As a basis for these templates, you can start by following the Hashicorp tutorial for "Building Vagrant Boxes with Packer and Atlas".  This was a good starting place because it builds an Ubuntu 12.04 machine and publishes Vagrant boxes for it on Atlas which makes them available to anyone.  We just need to modify this example to install supported versions of Apache, MySQL, and Elasticsearch and configure them so they are ready for a Sugar 7 install.

 

You will need Packer installed locally or have an Atlas account in order to run Packer builds.

 

For local builds, you will need to install Packer.  You will also need to have Virtualbox installed to run the Virtualbox builds.  If you have a VMWare license then you need VMWare Desktop or Fusion installed in order to run the VMWare builds.

 

If you are using Atlas to run builds then you only need Git installed locally.

 

This example uses shell scripts to provision the system but Packer supports a variety of provisioning options such as Puppet, Chef or Ansible. It is worth mentioning that SugarCRM Engineering uses Puppet to centrally manage provisioning in our internal environment.  But we will keep things simple by focusing on using shell scripts for now.

 

Shell scripts for provisioning Sugar 7 dependencies

 

Most of the provisioning scripts are going to be boilerplate.  For example, the steps for installing a new operating system or configuring user accounts, etc, are going to be more dependent on the selected Operating System rather than being anything that is Sugar specific.  So we will show two scripts below that focus on installing Supported Platform dependencies.

 

This first script is used to install all the needed Sugar 7 software dependencies on a fresh Ubuntu 12.04 machine.

 

php54.sh

#!/bin/bash
#
# Setup the the box. This runs as root

apt-get -y update

# MySQL default username and password is "root"
echo "mysql-server-5.5 mysql-server/root_password password root" | debconf-set-selections
echo "mysql-server-5.5 mysql-server/root_password_again password root" | debconf-set-selections

apt-get -y install python-software-properties perl curl zip vim

# Add Java, php5.4, and Elasticsearch repos
add-apt-repository ppa:ondrej/php5-oldstable
add-apt-repository ppa:webupd8team/java -y
wget -qO - https://packages.elastic.co/GPG-KEY-elasticsearch | apt-key add -
echo "deb http://packages.elastic.co/elasticsearch/1.4/debian stable main" | tee -a /etc/apt/sources.list
apt-get -y update

# Install Apache+php54 stack
apt-get -y install mysql-server php5-mysql php5-curl php5-gd php5-imap libphp-pclzip php-apc php5 apache2 php5-curl php5-dev php5-xdebug

#Install Elasticsearch and Java

# Auto-accept oracle license
echo debconf shared/accepted-oracle-license-v1-1 select true | debconf-set-selections
# Install Java 8, elasticsearch 1.4, then run it as a service
apt-get -y install oracle-java8-installer
apt-get -y install elasticsearch

# Set up Elasticsearch to run as a service
echo "Setting up Elasticsearch as a service"
update-rc.d elasticsearch defaults 95 10

# Update apache2 php.ini
sed -i 's/memory_limit = 128M/memory_limit = 512M/' /etc/php5/apache2/php.ini
sed -i 's/upload_max_filesize = 2M/upload_max_filesize = 20M/' /etc/php5/apache2/php.ini
sed -i 's/;date.timezone =/date.timezone = UTC/' /etc/php5/apache2/php.ini

# Update cli php.ini for cron
sed -i 's/;date.timezone =/date.timezone = UTC/' /etc/php5/cli/php.ini


 

 

 

This second script is used to configure Apache 2.2 web server on Ubuntu 12.04 to work with Sugar application.

 

apache22.sh

#!/bin/bash

# Add phpinfo to web dir as a convenience
echo "<?php phpinfo();" > /var/www/phpinfo.php

# We will have Apache run as Vagrant user because this will help prevent permissions issues on a dev setup
sed -i "s/export APACHE_RUN_USER=www-data/export APACHE_RUN_USER=vagrant/" /etc/apache2/envvars
chown -R vagrant /var/www/
usermod -a -G www-data vagrant

# Enable some important Apache modules
a2enmod headers expires deflate rewrite

# Enable DEFLATE on application/json - this helps speed up downloads of Sugar data
cat >> /etc/apache2/mods-enabled/deflate.conf <<DELIM
<IfModule mod_deflate.c>
AddOutputFilterByType DEFLATE application/json
</IfModule>
DELIM

# Update Apache config on web server to AllowOverride
sed -i "s/AllowOverride None/AllowOverride All/" /etc/apache2/sites-enabled/000-default

# Set up port 8080 virtualhost config to allow interactive installs of Sugar from Host system when using Vagrant
# Also make sure sugar directory has AllowOverride enabled
cat >> /etc/apache2/sites-available/sugar <<DELIM
Listen 8080
<VirtualHost *:8080>
ServerName localhost
DocumentRoot /var/www
</VirtualHost>
<Directory "/var/www/sugar">
Options Indexes FollowSymLinks MultiViews
AllowOverride All
Order allow,deny
Allow from all
</Directory>
DELIM
a2ensite sugar

# Restart apache22 when done
apachectl restart

 

 

 

Sugar 7 Ready template repository

 

The Sugar 7 Ready Packer templates are set up to build Virtualbox and VMWare images and Vagrant boxes.  But extending it to build Docker containers, Amazon AMIs, or any other container technology is possible and encouraged.

 

Trying it all out

 

Trying it out is easy if you have Virtualbox installed.  Virtualbox is a popular option because it is free.

 

Launching the Vagrant box

 

With Vagrant and Virtualbox installed, navigate to a clean Sugar development directory and run the following command.  By default, these boxes use 3GB of memory but you can customize this within your Vagrantfile.

vagrant init mmarum/sugar7-php54; vagrant up --provider virtualbox

 

When finished, your current directory will be running at http://localhost:8080/sugar/.  Visiting that URL will launch the Sugar installer.

 

If you have VMWare installed then you can launch the VMWare provider instead.

vagrant init mmarum/sugar7-php54; vagrant up --provider vmware_desktop

 

Downloading the Virtualbox Image

 

The same Packer template used to create the above Vagrant boxes was used to install a GUI desktop for running Sugar within a plain Virtualbox machine.  This is a great option for having an easy to use version of Sugar for training purposes.

 

Download it from here.  If you have Virtualbox installed, you can unzip it then import the image using Virtualbox  By default, it uses 4GB of memory but you can adjust this within Virtualbox before you launch it.