Adding Field Validation to the Record View
Overview
This page explains how to add additional field validation to the record view. In the following examples, we will extend and override the stock Accounts record view to add custom validation. The custom validation will require the Office Phone field when the account type is set to "Customer" and also require the user to enter at least one email address.
Error Messages
When throwing a validation error, Sugar has several stock messages you may choose to use. They are shown below:
Error Key | Label | Description |
maxValue | ERROR_MAXVALUE | This maximum value of this field |
minValue | ERROR_MINVALUE | This minimum value of this field |
maxLength | ERROR_MAX_FIELD_LENGTH | The max length of this field |
minLength | ERROR_MIN_FIELD_LENGTH | The min length of this field |
datetime | ERROR_DATETIME | This field requires a valid date |
required | ERROR_FIELD_REQUIRED | This field is required |
ERROR_EMAIL | There is an invalid email address | |
primaryEmail | ERROR_PRIMARY_EMAIL | No primary email address is set |
duplicateEmail | ERROR_DUPLICATE_EMAIL | There is a duplicate email address |
number | ERROR_NUMBER | This field requires a valid number |
isBefore | ERROR_IS_BEFORE | The date of this field can not be after another date |
isAfter | ERROR_IS_AFTER | The date of this field can not be before another date |
You also have the option of displaying multiple error messages at a time. The example below would throw an error message notifying the user that the selected field is required and that it is also not a number.
errors['<field name>'] = errors['<field name>'] || {};
errors['<field name>'].required = true;
errors['<field name>'].number = true;
Custom Error Messages
Custom error message can be used by appending custom language keys to app.error.errorName2Keys
when initializing an extended controller. To accomplish this, create a new language key in the $app_strings
. An example of this is shown below:
./custom/Extension/application/Ext/Language/en_us.error_custom_message.php
<?php
$app_strings['ERROR_CUSTOM_MESSAGE'] = 'My custom error message.';
Next, you will need to update your controller to use the new language key. To accomplish this, add your custom language key to the app.error.errorName2Keys
array in the initialize method:
initialize: function (options) {
this._super('initialize', [options]);
//add custom message key
app.error.errorName2Keys['custom_message'] = 'ERROR_CUSTOM_MESSAGE';
....
},
Once completed, you can call your custom message by setting the app.error.errorName2Keys
entry to true as shown below:
errors['phone_office'] = errors['phone_office'] || {};
errors['phone_office'].custom_message = true;
Method 1: Extending the RecordView and CreateView Controllers
One way to validate fields on record view is by creating record
and create
view controllers. This method requires a duplication of code due to the hierarchy design, however, it does organize the code by module and extend the core functionality. To accomplish this, override and extend the create view controller. This handles the validation when a user is creating a new record. Once the controller has been properly extended, define the validation check and use the model.addValidationTask
method to append your function to the save validation.
./custom/modules/Accounts/clients/base/views/create/create.js
({
extendsFrom: 'CreateView',
initialize: function (options) {
this._super('initialize', [options]);
//add validation tasks
this.model.addValidationTask('check_account_type', _.bind(this._doValidateCheckType, this));
this.model.addValidationTask('check_email', _.bind(this._doValidateEmail, this));
},
_doValidateCheckType: function(fields, errors, callback) {
//validate type requirements
if (this.model.get('account_type') == 'Customer' && _.isEmpty(this.model.get('phone_office')))
{
errors['phone_office'] = errors['phone_office'] || {};
errors['phone_office'].required = true;
}
callback(null, fields, errors);
},
_doValidateEmail: function(fields, errors, callback) {
//validate email requirements
if (_.isEmpty(this.model.get('email')))
{
errors['email'] = errors['email'] || {};
errors['email'].required = true;
}
callback(null, fields, errors);
},
})
Next, duplicate the validation logic for the record view. This handles the validation when editing existing records.
./custom/modules/Accounts/clients/base/views/record/record.js
({
/* because 'accounts' already has a record view, we need to extend it */
extendsFrom: 'AccountsRecordView',
initialize: function (options) {
this._super('initialize', [options]);
//add validation tasks
this.model.addValidationTask('check_account_type', _.bind(this._doValidateCheckType, this));
this.model.addValidationTask('check_email', _.bind(this._doValidateEmail, this));
},
_doValidateCheckType: function(fields, errors, callback) {
//validate requirements
if (this.model.get('account_type') == 'Customer' && _.isEmpty(this.model.get('phone_office')))
{
errors['phone_office'] = errors['phone_office'] || {};
errors['phone_office'].required = true;
}
callback(null, fields, errors);
},
_doValidateEmail: function(fields, errors, callback) {
//validate email requirements
if (_.isEmpty(this.model.get('email')))
{
errors['email'] = errors['email'] || {};
errors['email'].required = true;
}
callback(null, fields, errors);
},
})
Once the files are in place, navigate to Admin > Repair > Quick Repair and Rebuild. More information on displaying custom error messages can be found in the Error Messages section.
Method 2: Overriding the RecordView and CreateView Layouts
Another method for defining your own custom validation is to override a module's record
and create
layouts to append a new view with your logic. The benefits of this method are that you can use the single view to house the validation logic, however, this means that you will have to override the layout. When overriding a layout, verify that the layout has not changed when upgrading your instance. Once the layouts are overridden, define the validation check and use the model.addValidationTask
method to append the function to the save validation.
First, create the custom view. For the accounts example, create the view validate-account:
./custom/modules/Accounts/clients/base/views/validate-account/validate-account.js
({
className:"hidden",
_render: function() {
//No-op, Do nothing here
},
bindDataChange: function() {
//add validation tasks
this.model.addValidationTask('check_account_type', _.bind(this._doValidateCheckType, this));
this.model.addValidationTask('check_email', _.bind(this._doValidateEmail, this));
},
_doValidateCheckType: function(fields, errors, callback) {
//validate type requirements
if (this.model.get('account_type') == 'Customer' && _.isEmpty(this.model.get('phone_office')))
{
errors['phone_office'] = errors['phone_office'] || {};
errors['phone_office'].required = true;
}
callback(null, fields, errors);
},
_doValidateEmail: function(fields, errors, callback) {
//validate email requirements
if (_.isEmpty(this.model.get('email')))
{
errors['email'] = errors['email'] || {};
errors['email'].required = true;
}
callback(null, fields, errors);
},
})
More information on displaying custom error messages can be found in the Error Messages section. Next, depending on the selected module, duplicate its create layout to the modules custom folder to handle record creation. In our Accounts example, we have an existing ./modules/Accounts/clients/base/layouts/create/create.php
file so we need to duplicate this file to ./custom/modules/Accounts/clients/base/layouts/create/create.php
. After this has been completed, append the new custom view to the components. append:
array(
'view' => 'validate-account',
),
As shown below:
./custom/modules/Accounts/clients/base/layouts/create/create.php
<?php
$viewdefs['Accounts']['base']['layout']['create'] = array(
'components' =>
array(
array(
'layout' =>
array(
'components' =>
array(
array(
'layout' =>
array(
'components' =>
array(
array(
'view' => 'create',
),
array(
'view' => 'validate-account',
),
),
'type' => 'simple',
'name' => 'main-pane',
'span' => 8,
),
),
array(
'layout' =>
array(
'components' =>
array(),
'type' => 'simple',
'name' => 'side-pane',
'span' => 4,
),
),
array(
'layout' =>
array(
'components' =>
array(
array(
'view' =>
array (
'name' => 'dnb-account-create',
'label' => 'DNB Account Create',
),
'width' => 12,
),
),
'type' => 'simple',
'name' => 'dashboard-pane',
'span' => 4,
),
),
array(
'layout' =>
array(
'components' =>
array(
array(
'layout' => 'preview',
),
),
'type' => 'simple',
'name' => 'preview-pane',
'span' => 8,
),
),
),
'type' => 'default',
'name' => 'sidebar',
'span' => 12,
),
),
),
'type' => 'simple',
'name' => 'base',
'span' => 12,
);
Lastly, depending on the selected module, duplicate its record layout to the modules custom folder to handle editing a record. In the accounts example, we do not have an existing ./modules/Accounts/clients/base/layouts/record/record.php
file so we duplicated the core ./clients/base/layouts/record/record.php
to ./custom/modules/Accounts/clients/base/layouts/record/record.php
. Since we are copying from the ./clients/
core directory, modify:
$viewdefs['base']['layout']['record'] = array(
...
);
To:
$viewdefs['Accounts']['base']['layout']['record'] = array(
...
);
After this has been completed, append the new custom view to the components:
array(
'view' => 'validate-account',
),
The resulting file is shown below:
./custom/modules/Accounts/clients/base/layouts/record/record.php
<?php
$viewdefs['Accounts']['base']['layout']['record'] = array(
'components' => array(
array(
'layout' => array(
'components' => array(
array(
'layout' => array(
'components' => array(
array(
'view' => 'record',
'primary' => true,
),
array(
'view' => 'validate-account',
),
array(
'layout' => 'extra-info',
),
array(
'layout' => array(
'name' => 'filterpanel',
'span' => 12,
'last_state' => array(
'id' => 'record-filterpanel',
'defaults' => array(
'toggle-view' => 'subpanels',
),
),
'availableToggles' => array(
array(
'name' => 'subpanels',
'icon' => 'icon-table',
'label' => 'LBL_DATA_VIEW',
),
array(
'name' => 'list',
'icon' => 'icon-table',
'label' => 'LBL_LISTVIEW',
),
array(
'name' => 'activitystream',
'icon' => 'icon-th-list',
'label' => 'LBL_ACTIVITY_STREAM',
),
),
'components' => array(
array(
'layout' => 'filter',
'targetEl' => '.filter',
'position' => 'prepend'
),
array(
'view' => 'filter-rows',
"targetEl" => '.filter-options'
),
array(
'view' => 'filter-actions',
"targetEl" => '.filter-options'
),
array(
'layout' => 'activitystream',
'context' =>
array(
'module' => 'Activities',
),
),
array(
'layout' => 'subpanels',
),
),
),
),
),
'type' => 'simple',
'name' => 'main-pane',
'span' => 8,
),
),
array(
'layout' => array(
'components' => array(
array(
'layout' => 'sidebar',
),
),
'type' => 'simple',
'name' => 'side-pane',
'span' => 4,
),
),
array(
'layout' => array(
'components' => array(
array(
'layout' => array(
'type' => 'dashboard',
'last_state' => array(
'id' => 'last-visit',
)
),
'context' => array(
'forceNew' => true,
'module' => 'Home',
),
),
),
'type' => 'simple',
'name' => 'dashboard-pane',
'span' => 4,
),
),
array(
'layout' => array(
'components' => array(
array(
'layout' => 'preview',
),
),
'type' => 'simple',
'name' => 'preview-pane',
'span' => 8,
),
),
),
'type' => 'default',
'name' => 'sidebar',
'span' => 12,
),
),
),
'type' => 'simple',
'name' => 'base',
'span' => 12,
);
Once the files are in place, navigate to Admin > Repair > Quick Repair and Rebuild.