Archive for March, 2009

Quick ListView customization

Monday, March 23rd, 2009

As the next installment in the Sugar Tips and Tricks series from the Sugar engineering team, I will perform a quick and simple ListView customization which leverages the Carousel widget from the Yahoo UI (YUI) library. This customization completely changes the look and feel of the Contact ListView from a “rows and columns” view of your search results to a Yahoo UI Carousel view. The YUI library is included in SugarCRM and provides a ton of UI features beyond what we use in the core application.

Carousel Widget on Contacts Listview

I will only need to customize two files:

  1. view.list.php in the Contacts module
  2. ListViewContacts.tpl – a new smarty file which I have added to be used within the view.list.php file

Create a new file view.list.php file in custom/modules/Contacts/views. The beginning of the file including the constructor should look like:

require_once('include/MVC/View/views/view.list.php');
class ContactsViewList extends ViewList {

function ContactsViewList(){
parent::ViewList();
}

You can see that I am extending from the ViewList class as defined in ‘include/MVC/View/views/view.list.php’ so that I can take advantage of the methods which are already implemented and only override what I need. Since I only want to change how the ListView looks, I will do most of that in Smarty so I need to only override the method in ViewList which deals with rendering the template.

listViewProcess()

should do the trick.
Here is the full listing for the ContactsViewList class below:

require_once('include/MVC/View/views/view.list.php');
class ContactsViewList extends ViewList {

function ContactsViewList(){
parent::ViewList();
}
function listViewProcess(){
$this->processSearchForm();
$this->searchColumns = $this->searchColumns;
if(!$this->headers)
return;
if(empty($_REQUEST['search_form_only']) || $_REQUEST['search_form_only'] == false){
$this->lv->setup($this->seed, 'custom/modules/Contacts/tpls/ListViewContacts.tpl', $this->where, $this->params);
$savedSearchName = empty($_REQUEST['saved_search_select_name']) ? '' : (' - ' . $_REQUEST['saved_search_select_name']);
echo get_form_header($GLOBALS['mod_strings']['LBL_LIST_FORM_TITLE'] . $savedSearchName, '', false);
echo $this->lv->display();
}
}
}

Now that we have provided a way to get my own custom smarty template loaded on the Contacts ListView, I will create my Smarty file in custom/modules/Contacts/tpls/ListViewContacts.tpl. My goal here will be to remove most of the table rendering logic defined in ListViewGeneric.tpl and replace it with my own rendering logic to display the carousel.


link rel="stylesheet" type="text/css" href='{sugar_getjspath file='include/javascript/yui/build/fonts/fonts.css'}'>
<link type="text/css" rel="stylesheet" href='{sugar_getjspath file='include/javascript/yui/build/carousel/assets/skins/sam/carousel.css'}'>
<script src='{sugar_getjspath file='include/javascript/yui/build/utilities/utilities.js'}'></script>
<script src='{sugar_getjspath file='include/javascript/yui/build/carousel/carousel-beta-min.js'}'></script>
{literal}

<style type="text/css">
.yui-carousel-element li {
height: 158px;
text-align: left;
}

#container {
font-size: 13px;
margin: 0 auto;
}

#container a {
text-decoration: none;
}

#container .intro {
display: inline;
margin: 0px 14px 0px 4px;
width: 202px;
}

#container .item {
display: inline;
margin: 0 22px 0 12px;
overflow: hidden;
padding-right: 80px;
width: 106px;
}

#container .item .authimg {
bottom: 2px;
margin-left: 61px;
position: absolute;
z-index: 1;
}

#container .item h3 {
line-height: 85%;
margin-top: 4px;
}

#container .item h3 a {
font: 77% Arial, sans-serif;
position: relative;
text-transform: uppercase;
z-index: 2;
}

#container .item h3 a:link {
color:#35a235;
}

#container .item h4 {
margin-top:5px;
}

#container .item h4 a {
font: 100% Georgia, Times, serif;
position: relative;
z-index:2;
}

#container .item h4 a:link {
color:#00639b;
}

#container .item cite {
color: #888;
display: block;
font-size: 77%;
line-height: normal;
margin-bottom: 30px;
}

#container .item p.all {
bottom: 25px;
position: absolute;
z-index: 2;
}

#container .item p.all a {
font-weight: bold;
font-size: 85%;
}
</style>
{/literal}
<div  class="yui-skin-sam">
<div id="container">
<ol id="carousel">
{foreach name=rowIteration from=$data key=id item=rowData}
<li class="item">
<a href='index.php?module={$pageData.bean.moduleDir}&amp;action=detailview&amp;record={$rowData.ID}'>
{foreach from=$displayColumns key=col item=params}
{if $params.link &amp;!$params.customCode}

<{$pageData.tag.$id[$params.ACLTag]|default:$pageData.tag.$id.MAIN} href="#" onMouseOver="javascript:lvg_nav('{if $params.dynamic_module}{$rowData[$params.dynamic_module]}{else}{$params.module|default:$pageData.bean.moduleDir}{/if}', '{$rowData[$params.id]|default:$rowData.ID}', 'd', {$offset}, this)"  onFocus="javascript:lvg_nav('{if $params.dynamic_module}{$rowData[$params.dynamic_module]}{else}{$params.module|default:$pageData.bean.moduleDir}{/if}', '{$rowData[$params.id]|default:$rowData.ID}', 'd', {$offset}, this)">{$rowData.$col}</{$pageData.tag.$id[$params.ACLTag]|default:$pageData.tag.$id.MAIN}>

{elseif $params.customCode}
{sugar_evalcolumn_old var=$params.customCode rowData=$rowData}
{elseif $params.currency_format}
{sugar_currency_format
var=$rowData.$col
round=$params.currency_format.round
decimals=$params.currency_format.decimals
symbol=$params.currency_format.symbol
convert=$params.currency_format.convert
currency_symbol=$params.currency_format.currency_symbol
}
{elseif $params.type == 'bool'}
<input type='checkbox' disabled=disabled class='checkbox'
{if !empty($rowData[$col])}
checked=checked
{/if}
/>
{elseif $params.type == 'multienum'}
{if !empty($rowData.$col)}
{counter name="oCount" assign="oCount" start=0}
{assign var="vals" value='^,^'|explode:$rowData.$col}
{foreach from=$vals item=item}
{counter name="oCount"}
{sugar_translate label=$params.options select=$item}{if $oCount !=  count($vals)},{/if}
{/foreach}
{/if}
{else}
{$rowData.$col}
{/if}
{/foreach}
</a>
</li>
{/foreach}
</ol>
</div>
</div>
{literal}
<script>
(function () {
var carousel;

YAHOO.util.Event.onDOMReady(function (ev) {
var carousel    = new YAHOO.widget.Carousel("container", {
animation: { speed: 0.5 }
});

carousel.render(); // get ready for rendering the widget
carousel.show();   // display the widget
});
})();
</script>
{/literal}

You can see from the above I have stripped out the table HTML and now the meat of the display is done here:


<div id="container">
<ol id="carousel">
{foreach name=rowIteration from=$data key=id item=rowData}
<li class="item">

We simply loop through each of our rows and output it into the <li> tags and then once this has rendered the JavaScript:


(function () {
var carousel;

YAHOO.util.Event.onDOMReady(function (ev) {
var carousel    = new YAHOO.widget.Carousel("container", {
animation: { speed: 0.5 }
});

carousel.render(); // get ready for rendering the widget
carousel.show();   // display the widget
});
})();

will render the widget.

New vCal Updates Setting in 5.2.0c

Monday, March 23rd, 2009

In the Sugar 5.2.0c patch that was released last week, we included a new setting in the System Settings page that allows administrators to set the vCal time period.  Administrators can specify the number of months in advance of the current date that Free/Busy information for calls and meetings is published for each user. 

The new field in the System Settings page is titled “vCal Updates Time Period:”, and the default value is two months. Administrators can now enter the preferred number of months, from one to 12, as well as turn off Free/Busy publishing altogether by entering “0″.

The reason for this setting is to allow administrators to control VCal efficiency on saves of calls and meetings.  When calls and meetings are saved, queries are run to fetch vCal data for the assigned user.  Prior to 5.2.0c, the time period of information that the query was sent to retrieve was set to two months in advance of the current date, and entire VCal data was re-constructed for the next two months for each user/attendee.  The two month time period could not be changed, and vCal could not be disabled through the application UI, even if vCal was not being used.

Using vCal and retrieving data for a longer time period can have an impact on application performance when users have a lot of data to retrieve, or can unncessarily slow down the time to save a record if vCal is not being used and the queries are still run.

$tip = new TipsAndTricks(2, ‘New SugarFields In Studio’);

Monday, March 16th, 2009

In the last tip we showed how to add a new SugarField to the application. This time we’ll show you how to add that SugarField to Studio so administrators can add it as a custom field to layouts. It should be noted that we will be patching some core files. These same patches will be available in the next release of SugarCRM. As a side note empty() is getting rendered as emptyempty in this post. We are looking into the issue, but just make a mental note that if you see !emptyempty it should read just !empty.

Step 1:Patching Dynamic Fields

The first file we need to patch is modules/DynamicFields/FieldCases.php. This file is essentially a switch statement for Module Builder and Studio to retrieve the appropriate Sugar Field class. We’ll be changing to allow for supporting custom SugarFields.

Change the code inside the function get_widget from

default:
    $local_temp = new TemplateText(); break;

to

default:
	$file = false;
	//CHECK IF THERE IS A CUSTOM TEMPLATE
	if(file_exists('custom/modules/DynamicFields/templates/Fields/Template'. $type . '.php')){
		$file  =	'custom/modules/DynamicFields/templates/Fields/Template'. $type . '.php';
	//CHECK IF THERE IS A STANDARD TEMPLATE
	}else if(file_exists('modules/DynamicFields/templates/Fields/Template'. $type . '.php')){
		$file  =	'modules/DynamicFields/templates/Fields/Template'. $type . '.php';
	}
	//IF THERE WAS A CUSTOM OR STANDARD TEMPLATE USE IT
	if(!empty($file)){
		require_once($file);
		$class  = 'Template' . $type ;
		$customClass = 'Custom' . $class;
		//CHECK FOR A CUSTOM CLASS
		if(class_exists($customClass)){
			$local_temp = new $customClass();
		}else{
			//DEFAULT TO THE STANDARD CLASS NAME Template{$type}
			$local_temp = new $class();
		}
		break;
	}else{
		$local_temp = new TemplateText(); break;
	}

Step 2:Creating a YouTube Template Field

Now our code can load custom templates, so let’s create our custom YouTube template. We want it to behave like a normal text field, but when it’s creating it’s vardef it should have the type set to YouTube. To do this we create a new template in custom/modules/DynamicFields/templates/Fields/TemplateYouTube.php and we’ll create a TemplateYouTube class that extends TemplateField. Our code should look something like this

<?php
require_once('modules/DynamicFields/templates/Fields/TemplateField.php');
class TemplateYouTube extends TemplateField{
	//SET TYPE TO YouTube
	var $type='YouTube';

	//THIS FUNCTION GENERATES THE VARDEF
	function get_field_def(){
		//CALL ON PARENT FUNCTION TO RENDER VARDEF
		$def = parent::get_field_def();

		//IF WE HAVE A DEFAULT VALUE SET IT
		$def['default'] = !empty( $this->default) ? $this->default : $this->default_value;

		//LET'S SET THE MAX LENGTH
		$def['len'] = 50;

		//STILL HAVE THE DB THINK OF THE FIELD AS A varchar
		$def['dbType'] = 'varchar';

		return $def;
	}
}

Step 3:Adding YouTube to the custom field Drop Down

Now that we have a template class for our YouTube field we need it to actually show up in Studio and Module Builder. To do this we simply create a file custom/modules/ModuleBuilder/language/en_us.lang.php and put the following code in it

<?php
	$mod_strings['fieldTypes']['YouTube'] = 'YouTube';

You will need to delete the cached language file for Module Builder and Studio which is located in cache/modules/ModuleBuilder/langague/en_us.lang.php
Once you have done this you should be able to go to studio and add a custom field of type YouTube. Now your administrators can take advantage of all the new SugarFields you create.

$tip = new TipsAndTricks(1, ‘Creating a YouTube Sugar Field’);

Monday, March 16th, 2009

We just finished a great SugarCon event in San Francisco, and the engineering team really did a great job conveying a lot of tips and tricks to the community. We wanted to continue this momentum, and after discussing the idea with a few developers we have decided to release a new tip/trick every week up to next year’s SugarCon.  That’s 52 tips and tricks a year for those of you doing the math. As some of you may note SugarCon was actually 2 weeks ago, so we are already a week behind. Don’t fret, we’ll be releasing 2 tips and tricks this week.

Creating A YouTube Field

Sugar ships with several types of fields out-of-the-box including but not limited to – text, iframe, phone, link, enum …, but every now and then you want to have a new field type. In this article, we shall describe how to create a field for displaying YouTube© videos.

To begin with, let’s review the code that drives field behavior.  If you look into the base SugarField directory (include/SugarFields/Fields/Base), you will see the following files

  1. DetailView.tpl
  2. EditView.tpl
  3. SearchForm.tpl
  4. SugarFieldBase.php

The SugarFieldBase class defined in SugarFieldBase.php handles the processing of the SugarField and loads the appropriate Smarty template (.tpl) file for rendering the field.

Now if you open up any of the .tpl files you will notice that we use the double curly braces (e.g. {{ and }} ) instead of standard Smarty template syntax which uses single curly braces (e.g. { and } ). This is because the Smarty code these templates will create is based upon resolving EditView.tpl or DetailView.tpl with the metadata defined in editviewdefs.php and detailviewdefs.php respectively. For example the base DetailView.tpl code looks like the following:


{{sugarvar key='value'}}
{{if !empty($displayParams.enableConnectors)}}
{{sugarvar_connector view='DetailView'}}
{{/if}}

However, the runtime code that this would generate for the Accounts module (cache/modules/Accounts/DetailView.tpl) looks like:


{$fields.employees.value}

If we wanted to get the value of a field that is different from the one currently being rendered we could do the following:


{{sugarvar memberName='name' key='value'}}

And this would generate the following smarty code


{$fields.name.value}

The reason for the double curly brace ( {{ ) versus the single curly brace ( { ) is that this approach allows us to put a certain level of logic in the code so you could do something like changing the color of the text based on the value of a field:

{if {{sugarvar memberName='employees' key='value' stringFormat='false'}}>1000}

<span style='color:green'>

{else}

<span style='color:purple'>

{/if}

{{sugarvar memberName='employees' key='value'}}</span>

This is rendered at runtime as:


{if $fields.employees.value  > 1000}

<span style='color:green'>

{else}

<span style='color:purple'>

{/if}

{$fields.employees.value}</span>

One of the nice things about SugarFields is that they can inherit from parent fields – this is true for both the rendering class and the templates. Even better yet if there is no custom code that needs to be added to our rendering class, the base class can handle the rendering for us and we don’t even need to subclass it. For our YouTube example we will just need to create a custom DetailView.tpl

Step 1: Create the Sugar Field Directory

Create the directory:  include/SugarFields/Fields/YouTube

We could actually use the YouTube field in the code right now, but it would render using the base SugarField which is just a text input box.

Step 2: Create DetailView.tpl

Create the File:  include/SugarFields/Fields/YouTube/DetailView.tpl

Step 3: Edit DetailView.tpl

I went over to YouTube and grabbed the embed code that is displayed next to one of my favorite videos. We will be expecting users to enter in a YouTube video id in the edit view and based on that id we will render the appropriate video.

<object width="425" height="344">
<param name="movie"
value="http://www.youtube.com/v/{{sugarvar key='value'}}">
</param>
<param name="allowFullScreen" value="true">
</param>
<param name="allowscriptaccess" value="always">
</param>
<embed
src="http://www.youtube.com/v/{{sugarvar key='value'}}"
type="application/x-shockwave-flash"
allowscriptaccess="always" allowfullscreen="true" width="425"
height="344">
</embed>
</object>

Step 4: Using the YouTube Field

There are two ways that the YouTube field may be used, first you may specify a specifc field in the detailviewdefs.php of a module with type set to ‘YouTube’ for example in Accounts you can change website to be a YouTube video field:

array('name'=>'website', 'type'=>'YouTube')

The other way is in the vardefs where you need to set two parameters of a fields vardefs, type and dbType.

'website' =>array (
'name' => 'website',
'vname' => 'LBL_WEBSITE',
'type'=>'YouTube',
'dbType' =>'varchar'
),

Step 5: Don’t Render YouTube Fields That Don’t Have IDs


{if !empty({{sugarvar key='value' stringFormat=false}})}
<object width="425" height="344">
<param name="movie"
value="http://www.youtube.com/v/{{sugarvar key='value'}}">
</param>
<param name="allowFullScreen" value="true">
</param>
<param name="allowscriptaccess" value="always">
</param>
<embed
src="http://www.youtube.com/v/{{sugarvar key='value'}}"
type="application/x-shockwave-flash"
allowscriptaccess="always" allowfullscreen="true" width="425"
height="344">
</embed>
</object>

{else}

No YouTube Video Available

{/if}

Now you have a fully functioning “Custom SugarField”.   Stay tuned for this week’s 2nd tip on how to add your new SugarField to Studio.

Note: As of the writing of this article, Sugar 5.2.0a is the current patch version generally available.  In the upcoming Sugar 5.2.0c patch, you will be able to place the SugarField files in the custom directory (custom/include/SugarFields/Fields/) in order to override the default behavior in an upgrade-safe manner. However, it is okay to place these changes in the include/SugarFields/Fields/ for this exercise as long as your field names don’t conflict with default field names.

Have feedback for us? Drop us a line.
Terms & Conditions | Privacy | Trademark Info | Contact Info | FAQs | SugarCRM Inc.© 2004 - 2009 All rights reserved.