Model-View-Controller (MVC)
From SugarCRM Wiki
Contents |
Introduction
SugarCRM release 5.0 introduces a new implementation of the Model-View-Control (MVC) pattern that is the base of all application interactions. Working closely with the new MVC framework is the Metadata-driven UI framework where the high-level specification of parts of the user interface in the application is described in data structure.
This document describes some details of the design and implementation of release 5.0 MVC and Metadata UI framework, and some guidance on how to customize your application in this environment.
The MVC (Model-View-Controller) Pattern
A model-view-controller or MVC for short is a design pattern that creates a distinct separation between business-logic and display logic.
The Model
This is the data object built by the business/application logic needed to present at the user interface. For SugarCRM it is represented by the SugarBean and all subclasses of the SugarBean.
The View
This is the display layer which is responsible for rendering data from the Model to the end user.
The Controller
This is the layer that handles user events such as "Save" and determines what business logic actions to take to build model, and what view to load to render the data to the end user.
For references on MVC, see http://en.wikipedia.org/wiki/Model-view-controller
The SugarCRM Release 5.0 MVC Implementation:
The Model
The Sugar Model is the SugarBean and any subclass of the SugarBean. For the most part this has not changed from previous releases. There is one significant change in 5.0 when it comes to this area, SugarObjects.
Sugar Object Templates
One of the biggest issues that we have seen time and time again are the small discrepancies that popup between modules in the definitions of their fields. For example let's take the Contacts, Prospects, and Leads modules. If we look into the vardefs we can see each of these have fields of first_name, last_name, phone_home ... etc. In 4.5.1 and prior, in order to share functionality we could have Leads subclass Contacts, but Leads had to have it's own unique vardefs with many of the same fields showing up in both the Leads and Contacts vardefs.
Sugar Objects extend the concept of subclassing a step further and allows you to subclass the vardefs. This includes inheriting of fields, relationships, indexes, and language files, but unlike subclassing you are not limited to a single inheritance. If there were a Sugar Object for fields that are used across every module like id, deleted, date_modified ...etc, you could have your module inherit from both Basic Sugar Object and the Person Sugar Object.
Now let's say that the Basic type has a field 'name' with length 10. and Company has a field 'name' with length 20. If you inherit from Basic first then Company your field will be of length 20. Now let's say you have defined a field 'name' in your module that is of length 60. Your module will always override any values provided by Sugar Objects.
Currently there are 4 types of Sugar Object Templates:
- Person (Contacts, Prospects, Leads)
- Issue (Bugs, Cases)
- Company(Accounts)
- Basic(General Fields).
We can take this a step further and add assignable to the mix. An assignable module would be one that can be assigned to users. Although this isn't used by every module, many modules do let you assign records to users. SugarObject interfaces allow us to add assignable to modules we wish to let users assign records.
SugarObject interfaces and SugarObject templates are very similar to one another, but the main distinction is that templates have a base class you can subclass while interfaces do not. If you look into the file structure you will notice that templates include many additional files including a full metadata directory. This is currently primarily used for ModuleBuilder to get the default layouts from.
File Structure:
include/SugarObjects/interfaces include/SugarObjects/templates
Implementation:
There are two things you need to do to take advantage of SugarObjects:
- Your class needs to subclass the SugarObject class you wish to extend.
class MyClass extends Person{
function MyClass(){
parent::Person();
}
}
- In your vardefs.php file add the following to the end:
VardefManager::createVardef('Contacts','Contact', array('default', 'assignable','team_security', 'person'));
This is telling the VardefManager to create a cache of the Contacts vardefs with the addition of all the default fields, assignable fields, team security fields, and all fields from the person class.
Performance Issues:
VardefManager caches the generated vardefs into a single file which will be the file loaded at run time. Only if that file is not there will it load the vardefs.php file located in your modules directory. The language files also do a similar thing. This caching also includes data for custom fields and any vardef or language extensions that are dropped into the custom/ext framework.
Cache Files:
cache/modules/<mymodule>/<object_name>vardefs.php cache/modules/<mymodule>/langues/en_us.lang.php
The Controller
Release 5.0 introduces a cascading controller concept. There is a main controller called SugarController that handles all the basic actions of a module from Edit and Detail Views to saving a record. Each module can override this SugarController by adding a controller.php file into its directory. This file should extend the SugarController and the naming convention for the classis:
<ModuleName>Controller
Inside the controller you define an action method. The naming convention for the method is:
action_<action_name>
There are more fine grained control mechanisms a developer can use to override the controller processing. For example if a developer wanted to create a new save action there are 3 places where they could possibly override.
- action_save - this would be the broadest specification and would give the user full control over the save process.
- pre_save - a user could override the population of parameters from the form
- post_save - this is where the view is being setup. At this point the developer could set a redirect url, do some post save processing, or set a different view
Upgrade-Safe Implementation:
You can also add a custom Controller that should extend the modules Controller if such controller already exists. For example, if you want to extend the controller for a module that comes with SugarCRM release 5.0, you should check if that module already has a module-specific controller that came with the product. If so, you extend from that controller class otherwise you extend from SugarController class. In both case, you should place the custom controller class file in custom/modules/<MyModule>/Controller.php instead of the module directory. Doing so makes your customization upgrade-safe.
File Structure:
include/MVC/Controller/SugarController.php include/MVC/Controller/ControllerFactory.php modules/<MyModule>/Controller.php custom/modules/<MyModule>/controller.php
Implementation:
class UsersController extends SugarController{
function action_SetTimeZone(){
//Save TimeZone code in here
...
}
}
Mapping actions to files
You can choose not to provide a custom action method as defined above, and instead specify your mappings of actions to files in $action_file_map. Take a look at include/MVC/Controller/action_file_map.php as an example:
$action_file_map['subpanelviewer'] = 'include/SubPanel/SubPanelViewer.php'; $action_file_map['save2'] = 'include/generic/Save2.php'; $action_file_map['deleterelationship'] = 'include/generic/DeleteRelationship.php'; $action_file_map['import'] = 'modules/Import/index.php';
Here the developer has the opportunity to map an action to a file. For example Sugar uses a generic SubPanel file for handling subpanel actions. You can see above that there is an entry mapping the action ‘subpanelviewer' to 'include/SubPanel/SubPanelViewer.php'.
The base SugarController class loads the action mappings in the following path sequence:
- include/MVC/Controller
- modules/<Module-Name>
- custom/modules/<Module-Name>
- custom/include/MVC/Controller
Each one loads and overrides the previous definition if in conflict. You can drop a new action_file_map in the later path sequence that extends or overrides the mappings defined in the previous one.
Upgrade-Safe Implementation:
If you want to add custom action_file_map.php to an existing module that came with the SugarCRM release, you should place the file at custom/modules/<Module-Name>/action_file_map.php
File Structure:
include/MVC/Controller/action_file_map.php modules/<Module-Name>/action_file_map.php custom/modules/<Module-Name>/action_file_map.php
Implementation:
$action_file_map['soapRetrieve'] = 'custom/SoapRetrieve/soap.php';
Classic Support (Not Recommended):
Classic support allows you to have files that represent actions within your module in a manner similar to SugarCRM 4.5.1 and prior. Essentially you can just drop in a PHP file into your module and have that be handled as an action. This is not the recommended, but is considered okay for backwards compatibility. It is a better practice to take advantage of the action_<myaction> structure.
File Structure:
modules/<MyModule>/<MyAction>.php
Controller Flow Overview:
For example if a request comes in for detailview this is how the controller will handle that request
Start in index.php we load the SugarApplication instance
- SugarApplication instantiates SugarControllerFactory
- SugarControllerFactory loads the appropriate Controller
- Check for custom/modules/<MyModule>/controller.php
- if not found, check for modules/<MyModule>/controller.php
- if not found, load SugarController.php
- Call on the appropriate action
- Check for modules/<MyModule>/<MyAction>.php
- if not found, check for the method action_<MyAction> in the controller.
- if not found, check for an action_file_mapping
- if not found, report error "Action is not defined"
The View
The views are for handling the displaying of information to the browser. They are not just limited to html data, you can have it send down JSON encoded data as part of the view or any other structure you wish. As with the controllers there is a default class called SugarView which implements a lot of the basic logic for views such as handling of headers and footers.
As a developer if you want to create a custom view you would place a view.<view_name>.php file in a views/ subdirectory within the module. For example, for the detail view you would create a file name view.detail.php and place this within the views/ subdirectory within the module. If a views subdirectory does not exist, you should create one.
In the file you should create a class named: <Module>View<ViewName>. For example, for a list view within the Contacts module the class would be ContactsViewList. Note the first letter of each word is uppercase and all other letters are lowercase.
You can extend the class from SugarView, the parent class of all views, or you can extend from an existing view. For example extending from the out of the box list view can leverage a lot of the logic that has already been done for displaying a list view.
Methods:
There are two main methods to be overridden within a view:
- preDisplay() - This performs preprocessing within a view. When developing a new view you should not worry about this method. It is only relevant for extending existing views. For example, the include/MVC/View/views/view.edit.php file uses this and allows developers who wishes to extend this view to leverage all of the logic done in preDisplay() and either override the display() method completely or within your own display() method call parent::display().
- display() - This performs the actual displaying of the data to the screen. This is where the logic to display output to the screen should be placed.
Loading the View:
The ViewFactory class tries to load the view for view in this sequence and will use the first one it finds:
- custom/modules/<my_module>/views/view.<my_view>.php
- modules/<my_module>/views/view.<my_view>.php
- custom/include/MVC/View/views/view.<my_view>.php
- include/MVC/View/views/view.<my_view>.php
Implementation:
class ContactsViewList extends SugarView{
function ContactsViewList(){
parent::SugarView();
}
function display(){
echo 'This is my Contacts List View';
}
}
File Structure:
include/MVC/Views/view.<myview>.php custom/include/MVC/Views/view.<myview>.php modules/<mymodule>/views/view.<myview>.php custom/modules/<mymodule>/views/view.<myview>.php include/MVC/Views/SugarView.php
Display Options for Views:
Developers have the opportunity to control how the screen looks when their view is rendered. Prior to release 5.0 this was not possible due to all logic being kept in index.php. Each view could have a config file associated with it. So from the example above a developer would place a view.edit.config.php within the views/ subdirectory and when the edit view is rendered this config file will be picked up. When loading the view ViewFactory class will merge the view config files from the following possible locations with precedence order (high to low):
- customs/modules/<module-name>/views/view.<my_view>.config.php
- modules/<module-name>/views/view.<my_view>.config.php
- custom/include/MVC/View/views/view.<my_view>.config.php
- include/MVC/View/views/view.<my_view>.config.php
With the files at the top being most relevant and so on down the list.
Implementation:
The format of these files is as follows:
$view_config = array('actions' => array('popup' => array('show_header' => false,
'show_subpanels' => false,
'show_search' => false,
'show_footer' => false,
'show_javascript' => true,
),
),
'req_params' => array('to_pdf' => array('param_value' => true,
'config' => array('show_all' => false),
),
),
);
What this means is if the system has the action popup then go to the actions entry within the view_config and determine the proper configuration. Also if the request contains the parameter to_pdf and is set to be true then it will automatically cause the show_all configuration parameter to be set false, which means do not show any of the options.
The Metadata Framework
Background
Metadata is defined as information about data. In SugarCRM, Metadata refers to the framework of using files to abstract the presentation and business logic found in the system. The Metadata framework is described using PHP definition files that are processed by some other PHP files. The processing also usually includes the use of Smarty templates for rendering the presentation and javascript libraries to handle some business logic that affects conditional displays, input validation, etc.
Release 5.0 introduces a new Metadata framework to manage the display of records in the system and their respective edit or detail views. Metadata support for the search form has also been added in 5.0.
The following table lists the Metadata definition files found in the modules/[module]/metadata directory and a brief description of their purpose within the system.
| File | New to 5.0 | Description |
| acldefs.php | No | Used to support access control for Accounts, Opportunities, Products, ProjectTask and Quotes modules. No longer used in 5.0??? |
| popupdefs.php | No | Used to render and handle the search form and list view in popups |
| listviewdefs.php | No | Used to render the list view display for a module |
| subpaneldefs.php | Yes | Used to render a module's subpanels shown when viewing a record's detail view |
| searchdefs.php | Yes | Used to render a module's basic and advanced search form displays |
| sidecreateviewdefs.php | Yes | Used to render a module's quick create form shown in the side shortcut panel |
| editviewdefs.php | Yes | Used to render a record's edit view |
| detailviewdefs.php | Yes | Used to render a record's detail view |
The Metadata SearchForm and MVC in Action
Prior to 5.0, each SugarCRM module contained its own SearchForm.html file to render the search form display. The processing for the SearchForm.html file was controlled by the PHP file in include/SearchForm/SearchForm.php.
In 5.0, the search form layout can be defined in the Metadata file searchdefs.php. A sample of the Account's searchdefs.php appears as:
<?php
$searchdefs['Accounts'] = array(
'templateMeta' => array('maxColumns' => '3', 'widths' => array('label' => '10', 'field' => '30') ),
'layout' => array(
'basic_search' => array(
'name',
'billing_address_city',
'phone_office',
array('name' => 'address_street',
'label' =>'LBL_BILLING_ADDRESS',
'type' => 'name',
'group'=>'billing_address_street'),
array('name'=>'current_user_only',
'label'=>'LBL_CURRENT_USER_FILTER',
'type'=>'bool'),
),
'advanced_search' => array(
'name',
array('name' => 'address_street',
'label' =>'LBL_ANY_ADDRESS',
'type' => 'name'),
array('name' => 'phone',
'label' =>'LBL_ANY_PHONE',
'type' => 'name'),
'website',
array('name' => 'address_city',
'label' =>'LBL_CITY',
'type' => 'name'),
array('name' => 'email',
'label' =>'LBL_ANY_EMAIL',
'type' => 'name'),
'annual_revenue',
array('name' => 'address_state',
'label' =>'LBL_STATE',
'type' => 'name'),
'employees',
array('name' => 'address_postalcode',
'label' =>'LBL_POSTAL_CODE',
'type' => 'name'),
array('name' => 'billing_address_country',
'label' =>'LBL_COUNTRY',
'type' => 'name'),
'ticker_symbol',
'sic_code',
'rating',
'ownership',
array('name' => 'assigned_user_id',
'type' => 'enum',
'label' => 'LBL_ASSIGNED_TO',
'function' => array('name' => 'get_user_array',
'params' => array(false))),
'account_type',
'industry',
),
),
);
?>
The contents of the searchdefs.php file contains an Array variable $searchDefs with one entry. The key is the name of the module as defined in $moduleList Array defined in include/modules.php. The value of the $searchDefs Array is another Array that describes the search form layout and fields.
The 'templateMeta' key points to another Array that controls the maximum number of columns in each row of the search form ('maxColumns') as well as layout spacing attributes as defined by 'widths'. In the above example, the generated search form files will allocate 10% of the width spacing to the labels and 30% for each field respectively.
The 'layout' key points to another nested Array which defines the fields to display in the basic and advanced search form tabs. Each individual field definition maps to a SugarField widget. Please see the SugarField widget section for an explanation about SugarField widgets and how they are rendered for the search form, detail view and edit view.
The searchdefs.php file is invoked from the MVC framework whenever a module's list view is rendered (see include/MVC/View/views/view.list.php). Within view.list.php checks are made to see if the module has defined a SearchForm.html file. If this file exists, the MVC will run in classic mode and use the aforementioned 451 include/SearchForm/SearchForm.php file to process the search form. Otherwise, the new search form processing is invoked using include/SearchForm/SearchForm2.php and the searchdefs.php file is scanned for first under the custom/modules/[module]/metadata directory and then in modules/[module]/metadata.
The processing flow for the search form using the Metadata subpaneldefs.php file is similar to that of edit and detail views.
The Detail/Edit View Metadata framework and MVC in action
Before we begin our discussion into the Detail/Edit View Metadata framework, it is important to understand the motivations behind this design. Prior to Sugar 5.0, the application modules followed a strategy of using XTemplate processing to processing Edit/DetailView.html files against their respective Edit/DetailView.php files. Every module that had a front end user interface would contain these sets of files. XTemplate is an older generation template processing engine for PHP that had limitations in terms of its template scripting, customization and caching abilities. For the Metadata framework we have chosen Smarty as the template engine. The new Metadata framework does a better job in separating the code that is dynamic based on runtime parameters from the static portion of the user interface specified in the Metadata definition. By employing Smarty's extensibility and caching capabilities, the user-interface design is more flexible for customizations and the rendering is more efficient.
We will illustrate the components involved in the Metadata framework via a walkthrough of a detail view rendering. The concepts outlined below also extend to the edit view rendering within the SugarCRM application.
There are several components that make up the new Metadata framework. The following is a diagram of the main components involved.
A high level processing summary of the components for DetailViews would be as follows:
- The MVC framework receives a request to process the DetaiView.php (A) action for a module. For example, a record is selected from the list view shown on the browser with URL:
index.php?action=DetailView&module=Opportunities&record=46af9843-ccdf-f489-8833
- At this point the new MVC framework kicks in and checks to see if there is a DetailView.php (A2) file in the modules/Opportunity directory that will override the default DetailView.php implementation. The presence of a DetailView.php file will trigger the "classic" MVC view. If there isn't a DetailView.php (A2) file in the directory, the MVC will also check if you have defined a custom view to handle the DetailView rendering in MVC (i.e. check if there is a file modules/Opportunity/views/view.detail.php). Please consult the documentation for the MVC architecture for more notes about this. Finally, if neither the DetailView.php (A2) or view.detail.php exist then the MVC will invoke include/DetailView/DetailView.php (A) as the default Metadata action.
Note that the module defined DetailView.php (A2) file that ships with SugarCRM defaults to the old "classic" 4.5.x framework. This article focuses on the new Metadata framework which uses the generic DetailView.php (A) file.
The MVC framework (see views.detail.php in include/MVC/View/views folder) creates an instance of the generic DetailView (A)
// Call DetailView2 constructor $dv = new DetailView2(); // Assign by reference the Sugar_Smarty object created from MVC // We have to explicitly assign by reference to back support PHP 4.x $dv->ss =& $this->ss; // Call the setup function $dv->setup($this->module, $this->bean, $metadataFile, 'include/DetailView/DetailView.tpl'); // Process this view $dv->process(); // Return contents to the buffer echo $dv->display();
- When the setup method is invoked, a TemplateHandler instance (D) will be created. A check is also made to determine which detailviewdefs.php Metadata file to use in creating the resulting detail view. The first check is made to see if a Metadata file was passed in as a parameter. The second check is made against the custom/studio/modules/[Module] directory to see if a Metadata file exists. For the final option, the DetailView constructor will use the module's default detailviewdefs.php Metadata file located under the modules/[Module]/metadata directory. If there is no detailviewdefs.php file in the modules/[Module]/metadata directory, then a "best guess" version is created using the Metadata parser file in include/SugarFields/Parsers/DetailViewMetaParser.php (not shown in diagram).
The TemplateHandler also handles creating the quick search (Ajax code to do look ahead typing) as well as generating the javascript validation rules for the module. Both the quick search and javascript code should remain static based on the definitions of the current definition of the Metadata file. When fields are added/removed to the file via the studio application, this template and the resulting updated quick search and javascript code will be rebuilt.
It should be noted that the generic DetailView (A) defaults to using the generic DetailView.tpl smarty template file (F). This may also be overridden via the constructor parameters. The generic DetailView (A) constructor also retrieves the record according to the record id and populates the $focus bean variable.
- The process() method is invoked on the generic DetailView.php instance:
function process() {
//Format fields first
if($this->formatFields) {
$this->focus->format_all_fields();
}
parent::process();
}
This in turn, calls the EditView->process() method since DetailView extends from EditView. The EditView->process() method will calculate the width spacing for the detail view. The number of columns and the percentage of width to allocate to each column is defined in the Metadata file. For example:
'templateMeta' => array('maxColumns' => '2',
'widths' => array(
array('label' => '10', 'field' => '30'),
array('label' => '10', 'field' => '30')
),
),
Here we have defined two columns and actual layout is as follows:
|---------------------------------------------------------| | Column 1 | Column 2 | |-----------------------------|---------------------------| | Label Value | Label Value | |-----------------------------|---------------------------|
The actual Metadata layout will allow for variable column lengths throughout the displayed table. For example, the data values defined as:
'data' => array(
array('name', 'amount_usdollar'),
array('account_name'),
),
will be okay and the account_name label/value pair will be rendered as a single column on the second row as follows:
|---------------------------------------------------------| | Column 1 | Column 2 | |-----------------------------|---------------------------| | Label Value | Label Value | | ----------------------------|---------------------------| | Label Value | |---------------------------------------------------------|
The second thing the process() method does is populate the $fieldDefs array variable with the vardefs.php file (G) definition and the $focus bean's value. This is done by calling the toArray() method on the $focus bean instance and combining these value with the field definition specificed in the vardefs.php file (G).
- The display() method is invoked on the generic DetailView instance for the final step.
function display() {
$detailView = new DetailView();
global $beanList, $app_strings;
$bean = isset($beanList[$this->module]) ? strtoupper($beanList[$this->module]) : '';
$this->offset=0;
if (isset($_REQUEST['offset']) or isset($_REQUEST['record'])) {
$result = $detailView->processSugarBean($bean, $this->focus, $this->offset);
if($result == null) {
sugar_die($app_strings['ERROR_NO_RECORD']);
}
$this->focus=$result;
} else {
header("Location: index.php?module=Accounts&action=index");
}
$detailView->processListNavigation($this->th->ss, $bean, $this->offset,
$this->focus->is_AuditEnabled());
return parent::display();
}
When the display() method is invoked, variables to the DetailView.tpl Smarty template are assigned and the module's HTML code is sent to the output buffer.
Before HTML code is sent back, the TemplateHandler (D) first makes a check to see if an existing DetailView template already exists in the cache respository (H). In this case, it will look for file cache/modules/Opportunity/DetailView.tpl. The operation of creating the Smarty template is expensive so this operation ensures that the work will not have to be redone. As a side note, edits done to the detail view or edit view via the Studio application will clear the cache file and force the template to be rewritten so that the new changes are reflected.
If the cache file does not exist, the TemplateHandler (D) will create the template file and store it in the cache directory. When the fetch() method is invoked on the Sugar_Smarty class (E) to create the template, the DetailView.tpl file is parsed.
SugarField Widgets
SugarFields are the Objects that render the fields specified in the meta data (e.g., your *viewdefs.php files). They can be found in include/SugarFields/Fields. In the directory include/SugarFields/Fields/Base you will see the files for the base templates for rendering a field for DetailView, EditView, ListView and Search Forms. As well as the base class called SugarFieldBase
File Structure:
include/SugarFields/Fields/<fieldname> include/SugarFields/Fields/<fieldname>/DetailView.tpl modules/MyModule/vardefs.php modules/MyModule/metadata/<view>defs.php
This section describes the SugarFields widgets that are found in the include/SugarFields/Fields directory for release 5.0. Inside this folder you'll find a set of directories that encapsulate the rendering of a field type (i.e. Boolean, Text, Enum etc.). There are also SugarFields directories for grouped display values (e.g. Address, Datetime, Parent, and Relate). What we mean by rendering is that there are user interface paradigms associated with a particular field type. For example, a Boolean field type as defined in a module's vardef.php file can be displayed with a checkbox indicated the boolean nature of the field value (on/off, yes/no, 0/1, etc.). Naturally there are some displays in which the rendered user interface components are very specific to the module's logic. In this case, it is likely that custom code was used in the Metadata file definition.
SugarFields widgets are rendered from the Metadata framework whenever the new 5.0 MVC edit view, detail view or list view actions are invoked for a particular module. Each of the SugarFields will be discussed briefly.
Most SugarFields will contain a set of Smarty files to abstract rendering the field contents and supporting HTML. Some SugarFields will also contain a subclass of SugarFieldBase to override particular methods so as to control additional processing and routing of the corresponding Smarty file to use. The subclass naming convention is defined as:
SugarField[Sugar Field Type]
where the first letter of the Sugar Field Type should be in uppercase. The contents should also be placed in a corresponding .php file. For example, the contents of the enum type SugarField (rendered as <select> in HTML) is defined in include/SugarFields/Fields/Enum/SugarFieldEnum.php as:
class SugarFieldEnum extends SugarFieldBase {
function getDetailViewSmarty($parentFieldArray, $vardef, $displayParams, $tabindex) {
if(!empty($vardef['function']['returns']) && $vardef['function']['returns']== 'html'){
$this->setup($parentFieldArray, $vardef, $displayParams);
return $this->fetch('include/SugarFields/Fields/Enum/DetailViewFunction.tpl');
}else{
return parent::getDetailViewSmarty($parentFieldArray, $vardef, $displayParams, $tabindex);
}
}
function getEditViewSmarty($parentFieldArray, $vardef, $displayParams, $tabindex) {
if(!empty($vardef['function']['returns']) && $vardef['function']['returns']== 'html'){
$this->setup($parentFieldArray, $vardef, $displayParams);
return $this->fetch('include/SugarFields/Fields/Enum/EditViewFunction.tpl');
}else{
return parent::getEditViewSmarty($parentFieldArray, $vardef, $displayParams, $tabindex);
}
}
function getSearchViewSmarty($parentFieldArray, $vardef, $displayParams, $tabindex) {
if(!empty($vardef['function']['returns']) && $vardef['function']['returns']== 'html'){
$this->setup($parentFieldArray, $vardef, $displayParams, $tabindex);
return $this->fetch('include/SugarFields/Fields/Enum/EditViewFunction.tpl');
}else{
$this->setup($parentFieldArray, $vardef, $displayParams, $tabindex);
return $this->fetch('include/SugarFields/Fields/Enum/SearchView.tpl');
}
}
}
Here we see how the enum type will use one of six Smarty template file depending on the view (edit, detail or search) and whether or not the enum vardef definition has a 'function' attribute defined to invoke a PHP function to render the contents of the field.
SugarFields Widgets Glossary
Address
The Address field is responsible for rendering the various fields that together represent an address value. By default SugarCRM renders address values in the United States format:
Street City, State Zip
The Smarty template layout defined in DetailView.tpl reflects this. Should you wish to customize the layout depending on the $current_language global variable, you may add new files [$current_language].DetailView.tpl or [$current_language].EditView.tpl to the Address directory that reflect the language locale's address formatting.
Within the Metadata definition, the Address field can be rendered with the snippet:
array (
'name' => 'billing_address_street',
'hideLabel' => true,
'type' => 'address',
'displayParams'=>array('key'=>'billing', 'rows'=>2, 'cols'=>30, 'maxlength'=>150)
),
|
name |
The vardefs.php entry to key field off of. Though not 100% ideal, we use the street value |
|
hideLabel |
Boolean attribute to hide the label that is rendered by Metadata framework. We hide the billing_address_street label because the Address field already comes with labels for the other fields (city, state). Also, if this is not set to false, the layout will look awkward. |
|
type |
This is the field type override. billing_address_street is defined as a varchar in the vardefs.php file, but since we are interested in rendering an address field, we are overriding this here |
|
displayParams |
|
Note also the presence of file include/SugarFields/Fields/Address/SugarFieldAddress.js. This file is responsible for handling the logic of copying address values (from billing to shipping, primary to alternative, etc.). The javascript code makes assumptions using the key value of the grouped fields.
To customize various address formats for different locales, you may provide a locale specific implementation in the folder include/SugarFields/Fields/Address. There is a default English implementation provided. Locale implementations are system wide specific (you cannot render an address format for one user with an English locale and another format for another with a Japanese locale). SugarCRM locale settings are system wide and the SugarField implementation reflects this. To modify based on a user's locale preferences will require some customizations, but is possible.
Base
The Base field is the default parent field. It simply renders the value as is for DetailViews and an HTML text field
<input type="text">
for EditViews. All SugarFields that have a corresponding PHP file extend from SugarFieldBase.php.
Bool
The Bool field is responsible for rendering a checkbox to reflect the state of the value. In release 5.0 all boolean fields are stored as integer values. The Bool field will render a disabled checkbox for DetailViews. If the field value is "1" then the checkbox field will be checked. There isno special parameter to pass into this field from the Metadata definition. As with any of the fields you have the option to override the label key string.
For example, in the Metadata definition, the Boolean field do_not_call can be specified as:
'do_not_call'
or
array (
array('name'=>'do_not_call',
'label'=>'LBL_DO_NOT_CALL' // Overrides label as defined in vardefs.php
)
),
Datetime
The Datetime field is responsible for rendering an input text field along with an image to invoke the popup calendar picker. The Datetime field is different from the Datetimecombo field in that there is no option to select the time values of a datetime database field.
Within the Metadata definition, the Datetime field can be rendered with the snippet:
'date_quote_expected_closed', // Assumes that date_quote_exected_closed is defined
// as a datetime field in vardefs.php file
or
array('name'=>'date_quote_expected_closed',
'displayParams'=>array('required'=>true, 'showFormats'=>true)
),
|
name |
Standard name definition when Metadata definition is defined as an Array |
|
displayParams |
|
Datetimecombo
The Datetimecombo field is similar to the Datetime field with additional support to render dropdown lists for the hours and minutes values as well as a checkbox to enable/disable the entire field. New in Sugar5.x is the consolidation of the date portion (e.g. 12/25/2007) and time portion (e.g. 23:45) of the database fields. For example, in the 4.5.x versions, the Calls and Meetings module defined two separate vardefs.php entries for the date and time portion. Now that these two fields are consolidated, care must be taken by the module developer to handle input from three HTML field values within the module class code. Take for instance the vardefs.php definition for the date_start field in the Calls module:
'date_start' =>
array (
'name' => 'date_start',
'vname' => 'LBL_DATE',
'type' => 'datetime',
'required' => true,
'comment' => 'Date in which call is schedule to (or did) start'
),
There is one database field, but when the Datetimecombo widget is rendered, it will produce three HTML fields for display- a text box for the date portion and two dropdown lists for the hours and minutes values. The Datetimecombo widget will render the hours and menu dropdown portion in accordance to the user's $timedate preferences. An optional AM/PM meridiem drop down is also displayed should the user have selected a 12 hour base format (e.g. 11:00).
Within the Metadata definition, the Datetimecombo field can be rendered with the snippet:
array('name'=>'date_start',
'type'=>'datetimecombo',
'displayParams'=>array('required' => true,
'updateCallback'=>'SugarWidgetScheduler.update_time();',
'showFormats' => true,
'showNoneCheckbox' => true),
'label'=>'LBL_DATE_TIME'),
|
name |
Standard name definition when Metadata definition is defined as an Array |
|
type |
Metadata type override. By default, the field defaults to Datetime so we need to override it here in the definition. |
|
displayParams |
|
|
label (optional) |
Standard Metadata label override just to highlight this exhaustive example |
Download
The File field renders a link that references the download.php file for the given displayParam['id'] value when in DetailView mode.
Within the Metadata definition, the Download field can be rendered with the snippet:
array (
'name' => 'filename',
'displayParams' => array('link'=>'filename', 'id'=>'document_revision_id')
),
|
name |
Standard name definition when Metadata definition is defined as an Array |
|
displayParams |
|
Enum
The Enum field renders an HTML <select> form element that allows for a single value to be chosen. The size attribute of the <select> element is not defined so the element will render as a dropdown display in the EditView.
This field accepts the optional function override behavior that is defined at the vardefs.php file level. For example, in the Bugs module we have for the found_in_release field:
'found_in_release'=>
array(
'name'=>'found_in_release',
'type' => 'enum',
'function'=>'getReleaseDropDown',
'vname' => 'LBL_FOUND_IN_RELEASE',
'reportable'=>false,
'merge_filter' => 'enabled',
'comment' => 'The software or service release that manifested the bug',
'duplicate_merge' => 'disabled',
'audited' =>true,
),
The function override is NOT handled by the SugarFields library, but by the rendering code in include/EditView/EditView2.php.
Within the Metadata definition, the Download field can be rendered with the snippet:
array (
'name' => 'my_enum_field',
'type' => 'enum',
'displayParams' => array('javascript'=>'onchange="alert(\'hello world!\')";')
),
|
name |
Standard name definition when Metadata definition is defined as an Array |
|
displayParams |
|
File
The File field renders a file upload field in EditView and a hyperlink to invoke download.php in DetailView. Note that you will need to override the HTML form's enctype attribute to be "multipart/form-data" when using this field and handle the upload of the file contents in your code. This form enctype attribute should be set in the editviewdefs.php file. For example, for the Document's module we have the form override:
...
$viewdefs['Documents']['EditView'] = array(
'templateMeta' => array('form' => array(<'enctype'=>'multipart/form-data', // <--- override the enctype
...
Within the metadata file, the File field can be rendered with the snippet:
array (
'name' => 'filename',
'displayParams' => array('link'=>'filename', 'id'=>'document_revision_id'),
),
|
name |
Standard name definition when Metadata definition is defined as an Array |
|
displayParams |
|
Html
The Html field is a simple field that renders readonly content after the contet is run through the from_html method of include/utils/db_utils.php to encode entity references to their HTML characters (i.e. ">" => ">"). The rendering of the Html field type should be handle by the custom field logic within SugarCRM.
Image
Similar to the Html field, the Image field is simply renders a <img> tag where the src attribute points to the value of the field. Image fields are rendered for DetailViews only.
Within the metadata file, the Image field can be rendered with the snippet:
array (
'name' => 'my_image_value', // <-- The value of this is assumed to be some URL to an image file
'type' => 'image',
'displayParams'=>array('width'=>100, 'length'=>100, 'link'=>'http://www.cnn.com', 'border'=>0),
),
|
name |
Standard name definition when Metadata definition is defined as an Array |
|
displayParams |
|
Link
The link field simply generates a <a> tag with a hyperlink to the value of the field for DetailViews. For EditViews, it provides the convenience of prefilling the "http://" value.
Within the metadata file, the Link field can be rendered with the snippet:
array (
'name' => 'my_image_value', // <-- The value of this is assumed to be some URL to an image file
'type' => 'link',
'displayParams'=>array('title'=>'LBL_MY_TITLE'),
),
|
name |
Standard name definition when Metadata definition is defined as an Array |
|
displayParams |
|
Multienum
The Multienum fields renders a bullet list of values for DetailViews and renders a <select> form element for EditViews that allow multiple values to be chosen. Typically, the custom field handling in SugarCRM will map multienum types created via Studio so you would not need to declare metadata code to specify the type override. Nevertheless, within the metadata file, the Multienum field can be rendered with the snippet:
array (
'name' => 'my_multienum_field',
'type' => 'multienum',
'displayParams' => array('javascript'=>'onchange="alert(\'hello world!\')";')
),
|
name |
Standard name definition when Metadata definition is defined as an Array |
|
displayParams |
|
Parent
The parent field combines a blend of a dropdown for the parent module type and a text field with code to allow quicksearch for quicksearch enabled modules (please see include/SugarFields/Fields/Parent/EditView.tpl file contents and the javascript code there for more info on quicksearch enabling). There are also buttons to invoke popups and a button to clear the value. Because the parent field assumes proper relationships within the SugarCRM modules it is not a field you can add via studio nor a field you should attempt to type override in the metadata files.
Password
The password field simply renders an input text field with the type attribute set to "password" to hide user input values. It is available to EditViews only.
|
name |
Standard name definition when Metadata definition is defined as an Array |
|
displayParams |
|
Phone
The phone field simply invokes the newly support callto:// URL references that could trigger Skype or other VOIP applications installed on the user's system. It is rendered for DetailViews only.
Radioenum
The Radioenum field renders a group of radio buttons. Radioenum fields are similar to the enum field, but only one value can be selected from the group.
Readonly
The readonly field simply directs EditView calls to use the SugarField's DetailView display. There are no Smarty .tpl files associated with this field.
Relate
The Relate field combines a blend of a text field with code to allow quicksearch. The quicksearch code is generated for EditViews (see include/TemplateHandler/TemplateHandler.php). There are also buttons to invoke popups and a button to clear the value. For DetailViews, the Relate field creates a hyperlink that will bring up a DetailView request for the field's value.
Within the metadata file, the Relate field can be rendered with the snippet:
array (
array('name'=>'account_name',
'type'=>'relate',
'displayParams'=>array('allowNewValue'=>true)
),
),
This will create a relate field that allows the user to input a value not found in the quicksearch list.
|
name |
Standard name definition when Metadata definition is defined as an Array |
|
displayParams |
|
Text
The Text field renders a <textarea> HTML form element for EditViews and dislpays the field value with newline characters converted to
HTML elements in DetailViews.
|
name |
Standard name definition when Metadata definition is defined as an Array |
|
displayParams |
|
Username
The Username field is a helper field that assumes a salutation, first_name and last_name field exists for the vardefs of the module. It displays the three fields in the format:
[salutation] [first_name] [last_name]
Metadata Framework Summary
In summary, the new Metadata framework simplifies the management of the detail and edit views by reducing the number of individual .tpl or .html files currently used in 4.X versions and prior. The problem was that a change to a module's view required the editing of the module's .html or .tpl file and with that, the extra checks against malformed html or smarty tags as well as proper field type displays (select elements for enum fields, checkboxes for booleans, etc.). By moving to a Metadata driven framework, the fields that are rendered are tied directly to the module's vardefs.php file definition (barring any customization).
UI Customizations in the Metadata framework
While the hope of the Metadata framework is to abstract some of the value retrieval and display logic for the modules, there will inevitably be a need to customize the framework with separate hooks and logic pertaining to the module's unique business needs.
Customizing display to group values together
This is a common scenario especially for address blocks. This can be achieved as follows:
array (
'name' => 'date_modified',
'customCode' => '{$fields.date_modified.value} {$APP.LBL_BY} {$fields.modified_by_name.value}',
'label' => 'LBL_DATE_MODIFIED',
),
This will group the date_modified value and the modified_by_name values together with the $APP.LBL_BY label in between. The 'customCode' key is a direct Smarty inline code replacement. At the time of parsing the $fields Array will be populated with the values populated for the request bean instance.
Customizing buttons that appear on the form
By default, the Metadata framework provides default implementations for CANCEL, DELETE, DUPLICATE, EDIT, FIND DUPLICATES and SAVE buttons. However, you may wish to use some of the default buttons, but add additional buttons. Here's an example:
'templateMeta' => array('form' =>
array('buttons'=>array('EDIT', 'DUPLICATE', 'DELETE',
array('customCode'=>
'<form action="index.php" method="POST" name="Quote2Opp" id="form">
<input title="{$APP.LBL_QUOTE_TO_OPPORTUNITY_TITLE}"
accessKey="{$APP.LBL_QUOTE_TO_OPPORTUNITY_KEY}"
class="button"type="submit"
name="opp_to_quote_button"
value="{$APP.LBL_QUOTE_TO_OPPORTUNITY_LABEL}">
</form>'
),
...
Here we are adding a custom button with the label defined in the Smarty variable {$APP.LBL_QUOTE_TO_OPPORTUNITY_LABEL}. The EDIT, DUPLICATE and DELETE buttons are rendered and then the snippet of code in this 'customCode' block is added.
Customizing to use a hybrid of Metadata framework with other sections
In this scenario, you wish to take advantage of the Metadata framework to do some of the processing for a subset of the fields. However, the provided user interface generated does not meet the needs of your module which requires richer functionality. Typically this scenario occurs when the Edit or Detail views for the module provide a lot more display information that do not fit the frame of a table layout that the Metadata framework produces. For example, in addition to displaying the properties for the bean instance of the module, there is also a lot of information to be displayed for related beans or rich user interface designs that cannot be handled by the Metadata framework.
For example, consider the following layout where the lower portion of the module's view cannot be represented as a table.
|--------------------------------| | | | label value label value | | label value label value | <------ Standard Metadata table format | label value label value | | label value label value | | | |--------------------------------| | | | Tree Widget Images | | |----| | <------ Custom layout using another Smarty template + | |---- A | Custom PHP | | |----B | | |---- C | | | |--------------------------------|
This can be achieved by a combination of overriding the footer.tpl file in the templateMeta section of the Metadata file and creating a view.edit.php file to override the default MVC EditView handling. For example, consider the Quotes module's editviewdefs.php file:
...
'templateMeta' => array('maxColumns' => '2',
'widths' => array(
array('label' => '10', 'field' => '30'),
array('label' => '10', 'field' => '30')
),
'form' => array('footerTpl'=>'modules/Quotes/tpls/EditViewFooter.tpl'),
),
...
* NOTE* You don't have to necessarily create a view.edit.php file, but usually at this point of customization, you will want to add variables to your customized template that is not assigned by the generic EditView handling from the MVC framework. See the next example (Customizing to override Metadata framework completely) for more information about sub-classing the EditView.
Your EditViewFooter.tpl file can now render the necessary user interface code that the generic Metadata framework could not:
...
{$SOME_CRAZY_UI_WIDGET} <----- You can either create this HTML in edit.view.php or place it here
<applet codebase="something"> <---- Let's add an Applet!
</applet>
{{include file='include/EditView/footer.tpl'}} <--- Include the generic footer.tpl file at the end
...
Had you wished to edit the top panel to provide customized widgets then you could override the header.tpl file instead and control the behavior there. In that scenario, the smarty tag to include the generic header.tpl would likely appear at the top of the custom template file.
Customizing to override Metadata framework completely
Using the MVC framework, you define your module's own view.edit.php or view.detail.php file to subclass ViewEdit (for EditView) or ViewDetail (for DetailView). Then you override either the process or display methods to do any intermediary processing as necessary. This technique should be employed when you still want to take advantage of the rendering/layout formatting done by the Metadata framework, but wish to tweak how some of the values are retrieved.
|--------------------------------| | | | label value label value | | label value label value | <------ Standard Metadata table format + | label value label value | Custom PHP | label value label value | | | |--------------------------------|
// Contents of module/[$module]/view/view.edit.php file
require_once('include/json_config.php');
require_once('include/MVC/View/views/view.edit.php');
class CallsViewEdit extends ViewEdit {
function CallsViewEdit(){
parent::ViewEdit();
}
function display() {
global $json;
$json = getJSONobj();
$json_config = new json_config();
if (isset($this->bean->json_id) && !empty ($this->bean->json_id)) {
$javascript = $json_config->get_static_json_server(false, true, 'Calls', $this->bean->json_id);
} else {
$this->bean->json_id = $this->bean->id;
$javascript = $json_config->get_static_json_server(false, true, 'Calls', $this->bean->id);
}
// Assign the Javascript code to Smarty .tpl
$this->ss->assign('JSON_CONFIG_JAVASCRIPT', $javascript);
parent::display();
}
}
In the file modules/Calls/metadata/editviewdefs.php we have the following defined for the templateMeta->javascript value:
...
'javascript' => '<script type="text/javascript">{$JSON_CONFIG_JAVASCRIPT}</script>'
...
Here the $JSON_CONFIG_JAVASCRIPT Smarty variable was the result of a complex operation and not available via the vardefs.php file (i.e. there is no JSON_CONFIG_JAVASCRIPT declaration in the vardefs.php file).
Create A Custom SugarField
In order to add a new SugarField you just need to create a directory in
include/SugarFields/Fields/<fieldname>
Let's try to create a new type of field for rendering YouTube video. In this example, we will use a custom text field in the Contacts module and then override the DetailView of the custom field in the metadata file to link to our YouTube video. Here are the steps:
- Create a custom text field in the Contacts module from the studio editor
- Add this custom text field to both the edit view and detail view layout files
- Save and deploy both of the layout file changes
- Now lets create our YouTube SugarField. Create directory include/SugarFields/Fields/YouTube
- In include/SugarFields/Fields/YouTube directory, create file DetailView.tpl. For the DetailView we will use the "embed" tag to display the video. In order to do this we need to add in the template file:
{if !empty({{sugarvar key='value' string=true}})}
<object width="425" height="350">
<param name="movie" value="http://www.youtube.com/v/{{sugarvar key='value'}}></param>
<param name="wmode" value="transparent"></param>
<embed src="http://www.youtube.com/v/{{sugarvar key='value'}}" type="application/x-shockwave-flash"
wmode="transparent" width="425" height="350">
</embed>
</object>
{/if}
You will notice that we use the "{{" double brackets around our variables. This implies that that section should be evaluated when we are creating the cache file.
Also note that will use the default EditView implementation that is provided by base. This will give us a text field where people can input the YouTube video id so you do not need to create EditView.tpl. Also, we do not need to provide a PHP file to handle the SugarField processing since the defaults will suffice.
Now go to custom/modules/Contacts/metadata/detailview.php and add a type override to your YouTube field and save. In our example, the custom field was named "youtube".
array (
'name' => 'youtube_c',
'type' => 'YouTube',
'label' => 'LBL_YOUTUBE',
),
Your custom field is now ready to be displayed. You can now find an id value of a YouTube video to insert into the EditView and watch the video in the DetailView!
Tips & Pitfalls
This section provides some tips you will find useful in using the new MVC and Metadata framework in release 5.0.; and pitfalls that you may run into from the subtle details of the Metadata framework that sometimes cause frustration and confusion. We attempt to list some common scenarios encountered and how to address them.
Grouping Required Fields Together
To group all required fields together all you need to do is set the config to
$GLOBALS['sugar_config']['forms']['requireFirst'] = true;
Displaying data on edit views when a user has a read only ACL setting
$GLOBALS['sugar_config']['showDetailData'] = true;
The field value I have specified in my Metadata(editviewdefs.php or detailviewdefs.php) does not appear.
This usually happens when the user is attempting to specify a variable name in the module's class definition, but not in the vardefs.php file. For example, consider the Products module. In the Products.php class file there is the variable:
var $quote_name;
If you attempt to retrieve the value in your Metadata file as follows:
...
array (
'quote_name',
'date_purchased',
)
...
You must also make sure that 'quote_name' is specified in the vardefs.php file:
'quote_name' =>
array (
'name' => 'quote_name',
'type' => 'varchar',
'vname' => 'LBL_QUOTE_NAME',
'source'=>'non-db',
'comment' => 'Quote Name'
)
This is because the Metadata framework populates the Smarty variable $fields using the variables listed in the vardefs.php file. If they are not listed, there is no way for the Metadata framework to know for sure which class variables have been set and are to be used. The Metadata framework works in conjunction with ACL (Access Control Level) checks in the system and these are tied to fields defined in the vardefs.php file.
The field value I have specified in my Metadata(editviewdefs.php or detailviewdefs.php) does not appear AND I have also defined it in the vardefs.php file for the module.
This may occur if the variable in the module has not been initialized. The Metadata framework invokes the fill_in_additional_detail_fields() method so be sure the values are either set in the constructor or in the fill_in_additional_detail_fields() method.
