Friday, August 27, 2010

Amazon EBS caveat

When running your EC2 instances on the Amazon cloud with EBS volumes attached you should be aware that mounting EBS volumnes on Linux mount points /dev/sd[a-e]can lead to that the writes to the mounted EBS volume will not be flushed in case of a hardware failure.

You can read more about How to Attach the Volume to an Instance but most important is Instance Storage



In order to ensure that all your data intended to be written to the EBS volumes is indeed written to them and not the instance's local ephemeral storage, you should only use device names in the range /dev/sd[f-p].  More information on recommended device names can be found here

Subpanel save and continue in quick create form

This hack is for the Sugarcrm official release 5.5.1

Inline quick create form in a sub panel is supported by sugar (it is not the default way of creating new sub panel entries). We can add an inline quick create form to a subpanel by adding the widget class SubPanelTopButtonQuickCreate to the subpanel definition.
Now when we press the Create button just above the subpanel in question the form will load just under the subpanel without a full page load. In this quick create form there is a save button which will close the form and show subpanel records. So to add another record the user needs to press the create button again.

This posting describes how to add a new "Save & continue" button that will save the new record & open a new blank form again.



This customization is not upgrade safe.

The following files need to be modified:

1. include/EditView/SubpanelQuickCreate.php
a. function SubpanelQuickCreate modified to support new 'SUBPANELSAVEANDCONTINUE' button.
// HACK : New Widget - Save and Continus Button for inline (subpanel) forms
$this->ev->defs['templateMeta']['form']['buttons'] = array('SUBPANELSAVE', 'SUBPANELCANCEL', 'SUBPANELFULLFORM', 'SUBPANELSAVEANDCONTINUE');
// HACK : New Widget - Save and Continus Button for inline (subpanel) forms


2. include/generic/SugarWidgets/SugarWidgetSubPanelTopButtonQuickCreate.php
a. In function &_get_form code block added to create some hidden variable in quick create form.
// HACK : Inline problem with one-to-many relations
switch ( strtolower( $currentModule ) )
{
 case 'prospects' :
  $name = $defines['focus']->account_name ;
  break ;
 case 'documents' :
  $name = $defines['focus']->document_name ;
  break ;
 case 'kbdocuments' :
  $name = $defines['focus']->kbdocument_name ;
  break ;
 case 'leads' :
 case 'contacts' : 
  $name = $defines['focus']->first_name . " " .$defines['focus']->last_name ;
  break ;
 default :
    $name = (isset($defines['focus']->name)) ? $defines['focus']->name : "";
}
$button .= '<input type="hidden" name="return_name" value="' . $name . "\" />\n";

$relationship_name = $this->get_subpanel_relationship_name($defines);
$button .= '<input type="hidden" name="return_relationship" value="' . $relationship_name . "\" />\n"; 
// HACK : Inline problem with one-to-many relations


3. include/language/en_us.lang.php
// HACK : New Widget - Save and Continus Button for inline (subpanel) forms
$app_strings['LBL_SUBPANEL_SAVE_AND_CONTINUE_KEY'] = '';
$app_strings['LBL_SUBPANEL_SAVE_AND_CONTINUE_LABEL'] = 'Save and Continue';
$app_strings['LBL_SUBPANEL_SAVE_AND_CONTINUE_TITLE'] = 'Save and Continue';
// HACK : New Widget - Save and Continus Button for inline (subpanel) forms


4. include/Smarty/plugins/function.sugar_button.php
a. In function smarty_function_sugar_button new case added for save and continue button.
// HACK : New Widget - Save and Continus Button for inline (subpanel) forms    
case "SUBPANELSAVEANDCONTINUE":
 return '{if $bean->aclAccess("save")}<input title="{$APP.LBL_SUBPANEL_SAVE_AND_CONTINUE_TITLE}" accessKey="{$APP.LBL_SUBPANEL_SAVE_AND_CONTINUE_KEY}" class="button" onclick="this.form.action.value=\'Save\';if(check_form(\''.$view.'\'))return SUGAR.subpanelUtils.inlineSaveAndContinue(this.form.id, \''.$params['module'] . '_subpanel_save_continue_button\');return false;" type="submit" name="' . $params['module'] . '_subpanel_save_continue_button" id="' . $params['module'] . '_subpanel_save_continue_button" value="{$APP.LBL_SUBPANEL_SAVE_AND_CONTINUE_LABEL}">{/if} ';
// HACK : New Widget - Save and Continus Button for inline (subpanel) forms

b. New funcation added to generate subpanel id.
function buttonGetSubanelId($module){
 $subpanel = strtolower($module);
 $activities = array('Calls', 'Meetings', 'Tasks', 'Emails');
 if($module == 'Notes'){
  $subpanel = 'history';
 }
 if(in_array($module, $activities)){
  $subpanel = 'activities';
 }  
 return $subpanel;
}



5. include/SubPanel/SubPanelTiles.js
a. Following 3 function modified to support save and continue code.
// HACK : New Widget - Save and Continus Button for inline (subpanel) forms
inlineSaveAndContinue: function (theForm, buttonName) {
 ajaxStatus.showStatus(SUGAR.language.get('app_strings', 'LBL_SAVING'));
 var success = function (data) {
  
  var element = document.getElementById(buttonName);
  do {
   element = element.parentNode;
  } while (element.className != 'quickcreate' && element.parentNode);
      
  if (element.className == 'quickcreate') {
   var subpanel = element.id.slice(9, -7);
   SUGAR.subpanelUtils.cancelCreate(buttonName);
        
   var module = get_module_name();
   var id = get_record_id();
   var layout_def_key = get_layout_def_key();
   try {
    eval('result = ' + data.responseText);
   } catch (err) {}
   if (typeof(result) != 'undefined' && result != null && typeof(result['status']) != 'undefined' && result['status'] != null && result['status'] == 'dupe') {
    document.location.href = "index.php?" + result['get'].replace(/&amp;/gi, '&').replace(/&lt;/gi, '<').replace(/&gt;/gi, '>').replace(/&#039;/gi, '\'').replace(/&quot;/gi, '"').replace(/\r\n/gi, '\n');
    return;
   } else {

    ajaxStatus.showStatus(SUGAR.language.get('app_strings', 'LBL_SAVED'));
    ajaxStatus.hideStatus();
    document.getElementById(createFormName).onsubmit();
     showSubPanel(subpanel, null, true);
    if (reloadpage) window.location.reload(false);
   }
  }
 }
 var reloadpage = false;
 var createFormName = 'formform' + document.getElementById(theForm).module.value;
 if ((buttonName == 'Meetings_subpanel_save_button' || buttonName == 'Calls_subpanel_save_button') && document.getElementById(theForm).status[document.getElementById(theForm).status.selectedIndex].value == 'Held') {
  reloadpage = true;
 }
 YAHOO.util.Connect.setForm(theForm, true, true);
 var cObj = YAHOO.util.Connect.asyncRequest('POST', 'index.php', {
  success: success,
  failure: success,
  upload: success
 });
 return false;
}, 
// HACK : New Widget - Save and Continus Button for inline (subpanel) forms

sendAndRetrieve:function(theForm,theDiv,loadingStr, subpanel){

function redoTabs(formid) {
 var k = window.topIndex || 0;
 var form = document.getElementById("whole_" + formid);
 form = form.getElementsByTagName("form")[0];
 var elements = form.getElementsByTagName("*");

 // Main script cycle:
 for (var i = 0; i < elements.length; i++) {
  var element = elements[i];
  var tag = element.tagName.toLowerCase();
  var type = "";

  if (tag == "input") {
   try {
    type = element.getAttribute("type");
    if (type) type = type.toLowerCase();
   }
   catch (e) {
    type = "";
   };
  }

  if (((tag == "input") && (type != "hidden")) || (tag == "select") || (tag == "textarea") || (tag == "iframe") || (tag == "button")) {
   try {
    element.setAttribute("tabindex", k);
    element.tabIndex = k;
   }
   catch (e) {}

   ++k;
  }
  else {
   var tindex = false;

   try {
    tindex = element.hasAttribute("tabIndex");
   }
   catch (e) {
    tindex = false;
   };

   try {
    if (tindex) element.setAttribute("tabindex", 999);
   }
   catch (e) {}
  }
 }

 window.topIndex = k;
}

function success(data) {
 theDivObj = document.getElementById(theDiv);
 subpanelContents[theDiv] = new Array();
 subpanelContents[theDiv]['list'] = theDivObj;
 subpanelContents[theDiv]['newDiv'] = document.createElement('div');
 dataToDOMAvail = false;
 subpanelContents[theDiv]['newDiv'].innerHTML = '<script type="text/javascript">dataToDOMAvail=true;</script>' + data.responseText;
 subpanelContents[theDiv]['newDiv'].id = theDiv + '_newDiv';
 subpanelContents[theDiv]['newDiv'].className = 'quickcreate';
 // HACK: Upgrade sugarbase to SugarCRM 5.5
 //theDivObj.style.display = 'none';
 // HACK: Upgrade sugarbase to SugarCRM 5.5
 theDivObj.parentNode.insertBefore(subpanelContents[theDiv]['newDiv'], theDivObj);
 if (!dataToDOMAvail) {
  SUGAR.util.evalScript(data.responseText);
 }
 subpanelLocked[theDiv] = false;
 setTimeout("enableQS(false)", 500);
 ajaxStatus.hideStatus();

 redoTabs(theDiv);
}


6. modules/ModuleBuilder/parsers/relationships/AbstractRelationship.php
a. Following function is modified to add quick create button in subpanel. As SubPanelTopCreateButton already exist as subpanel widget, subpanel will show 2 create button. Remove undesired create button from following code.
protected function getSubpanelDefinition ($relationshipName , $sourceModule , $subpanelName, $titleKeyName = '')
{
 $subpanelDefinition = array ( ) ;
 $subpanelDefinition [ 'order' ] = 100 ;
 $subpanelDefinition [ 'module' ] = $sourceModule ;
 $subpanelDefinition [ 'subpanel_name' ] = $subpanelName ;
 // following two lines are required for the subpanel pagination code in ListView.php->processUnionBeans() to correctly determine the relevant field for sorting
 $subpanelDefinition [ 'sort_order' ] = 'asc' ;
 $subpanelDefinition [ 'sort_by' ] = 'id' ;
 if(!empty($titleKeyName)){
  $subpanelDefinition [ 'title_key' ] = 'LBL_' . strtoupper ( $relationshipName . '_FROM_' . $titleKeyName ) . '_TITLE' ;
 }else{
  $subpanelDefinition [ 'title_key' ] = 'LBL_' . strtoupper ( $relationshipName . '_FROM_' . $sourceModule ) . '_TITLE' ;
 }
 $subpanelDefinition [ 'get_subpanel_data' ] = $relationshipName ;
 $subpanelDefinition [ 'top_buttons' ] = array(
  array('widget_class' => "SubPanelTopCreateButton"),
  array('widget_class' => 'SubPanelTopButtonQuickCreate'),
  array('widget_class' => 'SubPanelTopSelectButton', 'mode'=>'MultiSelect')
 );
 
 return array ( $subpanelDefinition );
}

Wednesday, August 25, 2010

Android developement - first try

I recently purchased a HTC Desire which basically is my first foray into the new generation of smart phones.


I wanted to try some development so I downloaded the SDK and the other bits and piece described here http://developer.android.com/guide/developing/eclipse-adt.html.


The java development part was fairly straight forward, however the layout and how to get that right, that seems a bit harder. Probably something that I am missing.


Anyway my first application is a reverse calculator.




This visualizes the operands as small blue dots and then allows you to pick a choice for the answer.


I would like it to be centered in the screen like this




This has however had me running in circles until now, spending at least 5 hours trying to get this right. I have tried RelativeLayouts, LinearLayouts & TableLayouts. 


I have done a few applications for windows mobile and I must admit that I found layout easier to do in visual Studio.







Module Builder support for Regex validation in textfield - part 2

This is the second part of a posting. You can find the first part here Module Builder support for Regex validation in textfield - part 1.

The code changes needed to be able to do this are listed below.



1. include/javascript/javascript.php
a. function addField is modified to add validation of regex, if field has regex addToValidateRegex is called to add js validation on page.

// HACK : MB support for regular expression validation in textfields
// If "regex","regexlabel" and "modulename" not empty then pass values to function "addFieldRegex".
  
 $regex = $this->sugarbean->field_name_map[$field]['regex'];
 $regexlabel = $this->sugarbean->field_name_map[$field]['regexlabel'];
 $modulename = $this->sugarbean->module_dir;
 
 if($regex!="") { 
  $this->addToValidateRegex($field, $type='regex',$displayName, $required, $prefix='',$regex,$regexlabel,$modulename);
 }
// HACK : MB support for regular expression validation in textfields


b. New function addToValidateRegex added

// HACK : MB support for regular expression validation in textfields
// Function to handle regular expression validation.
function addToValidateRegex($field, $type='regex',$displayName, $required, $prefix='',$regex='',
       $regexlabel='',$modulename='') {
 $this->script .= "addToValidateRegex('".$this->formname."', '".$prefix.$field."', '".$type . 
     "', $required,'".$this->stripEndColon(translate($displayName,
     $this->sugarbean->module_dir)) . "','".$regex . "','".
     $regexlabel . "','".$modulename . "');\n";
}
// HACK : MB support for regular expression validation in textfields



2. include/javascript/sugar_grp1.js
a. 4 new variables added in the beginning of the file.

// HACK : MB support for regular expression validation in textfields
var regexlabelIndex = 5;
var modulenameIndex = 6;
var regexIndex = 4;
var regex;
var regexlabel;
var modulename;
// HACK : MB support for regular expression validation in textfields


b. function validate_form modified to support regex validation

function validate_form(formname, startsWith) {
...
    if (!bail) {
     switch (validate[formname][i][typeIndex]) {
     // HACK : MB support for regular expression validation in textfields
     case 'regex':
      var fieldvalue = form[validate[formname][i][nameIndex]].value;
      if (typeof(fieldvalue) != 'undefined' && fieldvalue != "") {
       var regex = validate[formname][i][regexIndex];
       if (typeof(regex) != 'undefined' && regex != "" && regex != null) {
        var regexlabel = validate[formname][i][regexlabelIndex];
        var modulename = validate[formname][i][modulenameIndex];
        regularExpressionTxt = SUGAR.language.get(modulename, regexlabel);
        var re = new RegExp(regex);
        if (!fieldvalue.match(re)) {
         add_error_style(formname, validate[formname][i][nameIndex], regularExpressionTxt + ' ' + validate[formname][i][msgIndex] + ' ' + validate[formname][i][nameIndex]);
         isError = true;
        }
       }
      }
      break;
     // HACK : MB support for regular expression validation in textfields
     case 'email':
      if (!isValidEmail(trim(form[validate[formname][i][nameIndex]].value))) {
       isError = true;
       add_error_style(formname, validate[formname][i][nameIndex], invalidTxt + " " + validate[formname][i][msgIndex]);
      }
      break;


c. New function addToValidateRegex added.

// HACK : MB support for regular expression validation in textfields
// Function to perform regular expression validation
function addToValidateRegex(formname, name, type, required, msg, regex, regexlabel, modulename) {
 addToValidate(formname, name, type, required, msg);
 validate[formname][validate[formname].length - 1][regexIndex] = regex;
 validate[formname][validate[formname].length - 1][regexlabelIndex] = regexlabel;
 validate[formname][validate[formname].length - 1][modulenameIndex] = modulename;
}
// HACK : MB support for regular expression validation in textfields



3. modules/DynamicFields/DynamicField.php
a. function addFieldObject modified to save regex, regexmessage & regexlabel

// HACK : MB support for regular expression validation in textfields
$fmd->regex = $field->regex;
$fmd->regexmessage = $field->regexmessage;
$fmd->regexlabel = $field->regexlabel;
// HACK : MB support for regular expression validation in textfields


b. 3 new parameter added to function addField

function addField($name,$label='', $type='Text',$max_size='255', $regex='',$regexmessage='', $regexlabel,$required_option='optional', $default_value='', $ext1='', $ext2='', $ext3='',$audited=0, $mass_update = 0 , $ext4='', $help='',$duplicate_merge=0, $comment=''){
  require_once('modules/DynamicFields/templates/Fields/TemplateField.php');
     $field = new TemplateField();
     $field->label = $label;
        if(empty($field->label)){
            $field->label = $name;
        }
        $field->name = $name;
  $field->type = $type;
  $field->len = $max_size;
  $field->required = (!empty($required_option) && $required_option != 'optional');
  $field->default = $default_value;
  $field->ext1 = $ext1;
  $field->ext2 = $ext2;
  $field->ext3 = $ext3;
  $field->ext4 = $ext4;
  $field->help = $help;

  // HACK : MB support for regular expression validation in textfields
  $field->regex = $regex;
  $field->regexmessage = $regexmessage;
  $field->regexlabel = $regexlabel;
  // HACK : MB support for regular expression validation in textfields

  $field->comments = $comment;
  $field->massupdate = $mass_update;
  $field->duplicate_merge = $duplicate_merge;
  $field->audited = $audited;
  $field->reportable = 1;
        return $this->addFieldObject($field);
    }



4. modules/DynamicFields/templates/Fields/Forms/varchar.tpl
a. Template file is modified to add 2 property of regex validation


{*  HACK : MB support for regular expression validation in textfields *}

 {$MOD.COLUMN_TITLE_REGEX}:{if $hideLevel < 5 }
 {else}

{$vardef.regex}{/if} 








 {$MOD.COLUMN_TITLE_REGEXMESSAGE}:{if $hideLevel < 5 }
 {else}

{$vardef.regex}{/if}




 {$MOD.COLUMN_TITLE_REGEXLABEL}:{if $hideLevel < 5 }
 {else}

{$vardef.regexlabel}{/if}



{*  HACK : MB support for regular expression validation in textfields *}


5. modules/DynamicFields/templates/Fields/TemplateField.php
a. 3 new properties added to class TemplateField


// HACK : MB support for regular expression validation in textfields
var $regex = '';
var $regexmessage = '';
var $regexlabel = '';
// HACK : MB support for regular expression validation in textfields


b. In class variable $vardef_map 3 new property added


var $vardef_map = array(
 'name'=>'name',
 'label'=>'vname',
 // bug 15801 - need to ALWAYS keep default and default_value consistent as some methods/classes use one, some use another...
 'default_value'=>'default',
 'default'=>'default_value',
//  'default_value'=>'default_value',
//  'default'=>'default_value',
 'len'=>'len',
 'required'=>'required',
 'type'=>'type',
 'audited'=>'audited',
 'massupdate'=>'massupdate',
 'options'=>'ext1',
 'help'=>'help',

 // HACK : MB support for regular expression validation in textfields
 'regex'=>'regex',
 'regexmessage'=>'regexmessage',
 'regexlabel'=>'regexlabel',
 // HACK : MB support for regular expression validation in textfields

 'comments'=>'comment',
 'importable'=>'importable',
 'duplicate_merge'=>'duplicate_merge',
 //'duplicate_merge_dom_value'=>'duplicate_merge', //bug #14897
 'reportable' => 'reportable',
 'min'=>'ext1',
 'max'=>'ext2',
 'ext2'=>'ext2',
 'ext4'=>'ext4',
 //'disable_num_format'=>'ext3',
 'ext3'=>'ext3',
 'label_value'=>'label_value',
);


c. function get_db_delete_alter_table modified to generate drop sql statement, value to 3 new property added to call.


function get_db_delete_alter_table($table)
    {
        return $GLOBALS['db']->getHelper()->dropColumnSQL(
            $table,
            array(
                'name' => $this->name,
                'type' => $this->type,
                'size' => $this->size,
    // HACK : MB support for regular expression validation in textfields
       'regex' => $this->regex,
    'regexmessage' => $this->regexmessage,
    'regexlabel' => $this->regexlabel,
       // HACK : MB support for regular expression validation in textfields
    'len'  => $this->len,
                )
            );
    }


d. function get_field_def will return 3 new properties along with other field property.


function get_field_def(){
 $array =  array(
  'required'=>$this->required,
  'source'=>'custom_fields',
  'name'=>$this->name,
  'vname'=>$this->vname,
  'type'=>$this->type,
  'massupdate'=>$this->massupdate,
  'default'=>$this->default,
  'comments'=> (isset($this->comments)) ? $this->comments : '',
  'help'=> (isset($this->help)) ?  $this->help : '',

  // HACK : MB support for regular expression validation in textfields
  'regex'=>$this->regex,
  'regexmessage'=>$this->regexmessage,
  'regexlabel'=>$this->regexlabel,
  // HACK : MB support for regular expression validation in textfields

  'importable'=>$this->importable,
  'duplicate_merge'=>$this->duplicate_merge,
  'duplicate_merge_dom_value'=>$this->duplicate_merge, //nsingh: bug #14897 The UI did not get updated according to $app_list_strings['custom_fields_merge_dup_dom'], so include an extra field for the dom and leave the original duplicate_merge logic intact.
  'audited'=>($this->audited ? 1 : 0),
  'reportable'=>($this->reportable ? 1 : 0),
  );
  if(!empty($this->len)){
   $array['len'] = $this->len;
  }
  $this->get_dup_merge_def($array);
  return $array;
}


e. 3 new property added to function populateFromRow’s array on first line $fmd_to_dyn_map.


$fmd_to_dyn_map = array(
 'comments' => 'comment', 
 'require_option' => 'required', 
 'label' => 'vname',
 'mass_update' => 'massupdate', 
 'max_size' => 'len', 
 'default_value' => 'default',

 // HACK : MB support for regular expression validation in textfields
 'regex' => 'regex',
 'regexmessage' => 'regexmessage',
 'regexlabel' => 'regexlabel'
 // HACK : MB support for regular expression validation in textfields
);



6. modules/EditCustomFields/EditCustomFields.php
a. 3 new parameter added to function add_custom_field, also new parameter values are stored in $fields_meta_data.


function add_custom_field($name, $label, $data_type, $max_size, $regex,$regexmessage,$regexlabel,
 $required_option, $default_value, $deleted, $ext1, $ext2, $ext3, $audited, $mass_update=0, $duplicate_merge=0, $reportable = true)
{
 $module_name = $this->module_name;

 $fields_meta_data = new FieldsMetaData();
 $fields_meta_data->name = $name;
 $fields_meta_data->label = $label;
 $fields_meta_data->module = $module_name;
 $fields_meta_data->data_type = $data_type;
 $fields_meta_data->max_size = $max_size;

 // HACK : MB support for regular expression validation in textfields
 $fields_meta_data->regex = $regex;
 $fields_meta_data->regexmessage = $regexmessage;
 $fields_meta_data->regexlabel = $regexlabel;
 // HACK : MB support for regular expression validation in textfields

 $fields_meta_data->required_option = $required_option;
 $fields_meta_data->default_value = $default_value;
 $fields_meta_data->deleted = $deleted;
 $fields_meta_data->ext1 = $ext1;
 $fields_meta_data->ext2 = $ext2;
 $fields_meta_data->ext3 = $ext3;
 $fields_meta_data->audited = $audited;
 $fields_meta_data->duplicate_merge = $duplicate_merge;
 $fields_meta_data->mass_update = $mass_update;
 $fields_meta_date->reportable = $reportable;  
 $fields_meta_data->insert();

 $custom_table_name = $this->_get_custom_tbl_name();
 $custom_table_exists =
  CustomFieldsTableSchema::custom_table_exists($custom_table_name);

 $custom_fields_table_schema =
  new CustomFieldsTableSchema($custom_table_name);

 if(!$custom_table_exists)
 {
  $custom_fields_table_schema->create_table();
 }

 $result = $custom_fields_table_schema->add_column($name, $data_type,
  $required_option, $default_value);

 return $result;
}


b. 3 new parameter added to function get_custom_field, also new parameter values are retrieved from $fields_meta_data.


function get_custom_field($id, &$name, &$label, &$data_type, &$max_size, &$regex,&$regexmessage,&$regexlabel,
  &$required_option, &$default_value, &$deleted, &$ext1, &$ext2, &$ext3, &$audited,&$duplicate_merge,&$reportable)
{
 $fields_meta_data = new FieldsMetaData($id);
 $name = $fields_meta_data->name;
 $label = $fields_meta_data->label;
 $data_type = $fields_meta_data->data_type;
 $max_size = $fields_meta_data->max_size;

 // HACK : MB support for regular expression validation in textfields
 $regex = $fields_meta_data->regex;
 $regexmessage = $fields_meta_data->regexmessage;
 $regexlabel = $fields_meta_data->regexlabel;
 // HACK : MB support for regular expression validation in textfields

 $required_option = $fields_meta_data->required_option;
 $default_value = $fields_meta_data->default_value;
 $deleted = $fields_meta_data->deleted;
 $ext1 = $fields_meta_data->ext1;
 $ext2 = $fields_meta_data->ext2;
 $ext3 = $fields_meta_data->ext3;
 $audited = $fields_meta_data->audited;  
 $duplicate_merge=$fields_meta_data->duplicate_merge;
 $reportable = $fields_meta_data->reportable;
}


c. 3 new parameter added to function edit_custom_field, also new parameter values are stored in $fields_meta_data.


function edit_custom_field($id, $name, $label, $data_type, $max_size, $regex,$regexmessage,$regexlabel,
 $required_option, $default_value, $deleted, $ext1, $ext2, $ext3, $audited,$duplicate_merge,$reportable)
{
 $module_name = $this->module_name;

 // update the meta data
 $fields_meta_data = new FieldsMetaData();
 $fields_meta_data->id = $id;
 $fields_meta_data->name = $name;
 $fields_meta_data->label = $label;
 $fields_meta_data->module = $module_name;
 $fields_meta_data->data_type = $data_type;
 $fields_meta_data->max_size = $max_size;

 // HACK : MB support for regular expression validation in textfields
 $fields_meta_data->regex = $regex;  
 $fields_meta_data->regexmessage = $regexmessage;    
 $fields_meta_data->regexlabel = $regexlabel; 
 // HACK : MB support for regular expression validation in textfields

 $fields_meta_data->required_option = $required_option;
 $fields_meta_data->default_value = $default_value;
 $fields_meta_data->deleted = $deleted;
 $fields_meta_data->ext1 = $ext1;
 $fields_meta_data->ext2 = $ext2;
 $fields_meta_data->ext3 = $ext3;
 $fields_meta_data->audited=$audited;
 $fields_meta_data->duplicate_merge=$duplicate_merge;   
 $fields_meta_data->reportable = $reportable;     
 $fields_meta_data->update();

 // update the schema of the custom table
 $custom_table_name = $this->_get_custom_tbl_name();
 $custom_fields_table_schema =
  new CustomFieldsTableSchema($custom_table_name);

 $custom_fields_table_schema->modify_column($name, $data_type,
  $required_option, $default_value);
}



7. modules/EditCustomFields/EditView.html
a. 3 new property added to template


{MOD.COLUMN_TITLE_REGEX}:
{MOD.COLUMN_TITLE_REGEXMESSAGE}:
{MOD.COLUMN_TITLE_REGEXLABEL}:




8. modules/EditCustomFields/EditView.php
a. Regex properties assigned to template


// HACK : MB support for regular expression validation in textfields
$xtpl->assign('regex', $focus->regex); 
$xtpl->assign('regexmessage', $focus->regexmessage); 
$xtpl->assign('regexlabel', $focus->regexlabel);
// HACK : MB support for regular expression validation in textfields



9. modules/EditCustomFields/Forms.html
a. 3 new property added to template


{mod.COLUMN_TITLE_REGEX}

{mod.COLUMN_TITLE_REGEXMESSAGE}

{mod.COLUMN_TITLE_REGEXLABEL}





10. modules/EditCustomFields/language/en_us.lang.php
a. Labels at end of file.


// HACK : MB support for regular expression validation in textfields
$mod_strings = array_merge($mod_strings,
 array(
   'COLUMN_TITLE_REGEX' => "Regex Expression",
   'COLUMN_TITLE_REGEXMESSAGE' => "Regex Message",
   'COLUMN_TITLE_REGEXLABEL' => "Regex Label",
 )
);
// HACK : MB support for regular expression validation in textfields



11. modules/EditCustomFields/ListView.html
a. 3 new property added to template


{MOD.COLUMN_TITLE_REGEX}{arrow_start}{regex_arrow}{arrow_end} 
{MOD.COLUMN_TITLE_REGEXMESSAGE}{arrow_start}{regexmessage_arrow}{arrow_end} 
{MOD.COLUMN_TITLE_REGEXLABEL}{arrow_start}{regexlabel_arrow}{arrow_end} 




12. modules/EditCustomFields/Popup.html
a. 3 new property added to template


{mod.COLUMN_TITLE_REGEX}



{mod.COLUMN_TITLE_REGEXMESSAGE}



{mod.COLUMN_TITLE_REGEXLABEL}






13. modules/EditCustomFields/Save.php
a. 3 regex property value from request


// HACK : MB support for regular expression validation in textfields
if(isset($_REQUEST['regex'])){  
 $regex = $_REQUEST['regex'];
}
if(isset($_REQUEST['regexmessage'])){  
 $regexmessage = $_REQUEST['regexmessage'];
}
if(isset($_REQUEST['regexlabel'])){  
 $regexlabel = $_REQUEST['regexlabel'];
}
// HACK : MB support for regular expression validation in textfields

b. $custom_fields->updateField call has added 3 new regex argument
$custom_fields->updateField($id, array('max_size'=>$max_size, 'regex'=>$regex, 'regexmessage'=>$regexmessage,'regexlabel'=>$regexlabel, 'required_option'=>$required_opt, 'default_value'=>$default_value, 'audited'=>$audit_value, 'mass_update'=>$mass_update,'duplicate_merge'=>$_REQUEST['duplicate_merge']));



14. modules/EditCustomFields/Saveold.php
a. Code added to set value of 3 regex variable from $fields_meta_data


// HACK : MB support for regular expression validation in textfields
$regex = $fields_meta_data->regex;
$regexmessage = $fields_meta_data->regexmessage;
$regexlabel = $fields_meta_data->regexlabel;
// HACK : MB support for regular expression validation in textfields



15. modules/EditCustomFields/vardefs.php
a. 3 regex fields added to fields array


'fields' => array (
 'id'=>array('name' =>'id', 'type' =>'varchar', 'len'=>'255', 'reportable'=>false),
 'name'=>array('name' =>'name', 'vname'=>'COLUMN_TITLE_NAME', 'type' =>'varchar', 'len'=>'255'),
 'vname'=>array('name' =>'vname' ,'type' =>'varchar','vname'=>'COLUMN_TITLE_LABEL',  'len'=>'255'),
 'comments'=>array('name' =>'comments' ,'type' =>'varchar','vname'=>'COLUMN_TITLE_LABEL',  'len'=>'255'),
 'help'=>array('name' =>'help' ,'type' =>'varchar','vname'=>'COLUMN_TITLE_LABEL',  'len'=>'255'),
 'custom_module'=>array('name' =>'custom_module',  'type' =>'varchar', 'len'=>'255', ),
 'type'=>array('name' =>'type', 'vname'=>'COLUMN_TITLE_DATA_TYPE',  'type' =>'varchar', 'len'=>'255'),
 'len'=>array('name' =>'len','vname'=>'COLUMN_TITLE_MAX_SIZE', 'type' =>'int', 'len'=>'11', 'required'=>false, 'validation' => array('type' => 'range', 'min' => 1, 'max' => 255),),

 // HACK : MB support for regular expression validation in textfields
 'regex'=>array('name' =>'regex' ,'type' =>'varchar','vname'=>'COLUMN_TITLE_REGEX',  'len'=>'255'),
 'regexmessage'=>array('name' =>'regexmessage' ,'type' =>'varchar','vname'=>'COLUMN_TITLE_REGEXMESSAGE',  'len'=>'255'),
 'regexlabel'=>array('name' =>'regexlabel' ,'type' =>'varchar','vname'=>'COLUMN_TITLE_REGEXLABEL',  'len'=>'255'),
  // HACK : MB support for regular expression validation in textfields

 'required'=>array('name' =>'required', 'type' =>'bool', 'default'=>'0'),
 'default_value'=>array('name' =>'default_value', 'type' =>'varchar', 'len'=>'255', ),
 'date_modified'=>array('name' =>'date_modified', 'type' =>'datetime', 'len'=>'255',),  
 'deleted'=>array('name' =>'deleted', 'type' =>'bool', 'default'=>'0', 'reportable'=>false),
 'audited'=>array('name' =>'audited', 'type' =>'bool', 'default'=>'0'),  
 'massupdate'=>array('name' =>'massupdate', 'type' =>'bool', 'default'=>'0'), 
 'duplicate_merge'=>array('name' =>'duplicate_merge', 'type' =>'short', 'default'=>'0'),  
 'reportable' => array('name'=>'reportable', 'type'=>'bool', 'default'=>'1'),
 'importable' => array('name'=>'importable', 'type'=>'varchar', 'len'=>'255'),
 'ext1'=>array('name' =>'ext1', 'type' =>'varchar', 'len'=>'255', 'default'=>''),
 'ext2'=>array('name' =>'ext2', 'type' =>'varchar', 'len'=>'255', 'default'=>''),
 'ext3'=>array('name' =>'ext3', 'type' =>'varchar', 'len'=>'255', 'default'=>''),
 'ext4'=>array('name' =>'ext4', 'type' =>'text'),
),



16. modules/ModuleBuilder/controller.php
a. function action_SaveLabel modified to set regex validation message in request


function action_SaveLabel ()
{
 if (! empty ( $_REQUEST [ 'view_module' ] ) && !empty($_REQUEST [ 'labelValue' ]))
 {
  $_REQUEST [ "label_" . $_REQUEST [ 'label' ] ] = $_REQUEST [ 'labelValue' ] ;
 
  // HACK : MB support for regular expression validation in textfields
  // To add  'regexlabel' and 'regexmessage' option in language file.
  if($_REQUEST [ 'regexlabel' ]!="") {
   $_REQUEST [ "label_" . $_REQUEST [ 'regexlabel' ] ] = $_REQUEST [ 'regexmessage' ] ;
  } 
   // HACK : MB support for regular expression validation in textfields
 
  require_once 'modules/ModuleBuilder/parsers/parser.label.php' ;
  $parser = new ParserLabel ( $_REQUEST['view_module'] , isset ( $_REQUEST [ 'view_package' ] ) ? $_REQUEST [ 'view_package' ] : null ) ;
  $parser->handleSave ( $_REQUEST, $GLOBALS [ 'current_language' ] ) ;

 }
 $this->view = 'modulefields' ;
}


b. function action_SaveField modified to save regex validation message, before saving module


// HACK : MB support for regular expression validation in textfields
// To add  'regexlabel' and 'regexmessage' option in language file
if($_REQUEST [ 'regexlabel' ]!="") {
 $module->setLabel ( $GLOBALS [ 'current_language' ], $_REQUEST [ 'regexlabel' ], $_REQUEST [ 'regexmessage' ] ) ;
}
// HACK : MB support for regular expression validation in textfields



17. modules/ModuleBuilder/MB/MBField.php
a. 3 new call member variable added to class MBField


// HACK : MB support for regular expression validation in textfields
//To add options "regex","regexmessage" and "regexlabel" in variable defination. 
var $regex = false;
var $regexmessage = false;
var $regexlabel = false;
// HACK : MB support for regular expression validation in textfields


b. In function getFieldVardef, regex, regexmessage & regexlabel set in $vardef array.


// HACK : MB support for regular expression validation in textfields
// To add options "regex","regexmessage" and "regexlabel" in variable defination
 if(!empty($this->regex))$vardef['regex'] = $this->regex;
 if(!empty($this->regexmessage))$vardef['regexmessage'] = $this->regexmessage;
 if(empty($this->regexlabel))$this->regexlabel = 'LBL_'.strtoupper($this->regex);
// HACK : MB support for regular expression validation in textfields



18. modules/ModuleBuilder/MB/MBVardefs.php
a. Function save is modified to unset regex message value, because it will not be save in vardefs, it will be saved in lang file.


function save(){
 $header = file_get_contents('modules/ModuleBuilder/MB/header.php');
  // HACK : MB support for regular expression validation in textfields
 // To remove 'regexmessage' option from variable defination
 foreach($this->vardef['fields'] as $key=>$value)
 {  
  unset($this->vardef['fields'][$key]['regexmessage']);
 }
  // HACK : MB support for regular expression validation in textfields
 write_array_to_file('vardefs', $this->vardef, $this->path . '/vardefs.php','w', $header);
}



19. modules/ModuleBuilder/views/view.labels.php
a. In function display() each field is filtered


// HACK : MB support for regular expression validation in textfields
// To show options "regex","regexmessage" and "regexlabel" in label defination  
$formatted_mod_strings[$name] = htmlentities($label, ENT_QUOTES, 'UTF-8');
// HACK : MB support for regular expression validation in textfields



20. modules/ModuleBuilder/views/view.modulefield.php
a. In function fetch(), regex label is retrevied from lang file & set in $vardefs.


// HACK : MB support for regular expression validation in textfields
// To get regexlabel=>regexmessage value from language file by .
// This is for studio. 
if(isset($vardef['regexlabel'])){
 $language_value='';
 if(!empty($vardef['regexlabel'])){
  $language_value=translate($vardef['regexlabel'], $moduleName);
  if(!empty($language_value))
  $vardef['regexmessage']=$vardef['regexlabel'];
  $vardef['regexmessage']=$language_value;
 }
}   
// HACK : MB support for regular expression validation in textfields



21. Following files are help file for regex. Question mark icon shown in regex field inmodule builder while editing field. Clicking on question mark icon will show help from following file.
a. modules/DynamicFields/language/en_us.regex.index.html
b. modules/DynamicFields/language/regex
c. modules/DynamicFields/language/regex/step1.jpg
d. modules/DynamicFields/language/regex/step2.JPG
e. modules/DynamicFields/language/regex/step3.JPG
f. modules/DynamicFields/language/regex/step4.JPG




22. After applying patch run following SQL, this will create columns for regex metadata in fields_meta_data table.


ALTER TABLE fields_meta_data ADD COLUMN size VARCHAR (255) NULL,
  ADD COLUMN regex VARCHAR (255) NULL,
  ADD COLUMN regexmessage VARCHAR (255) NULL,
  ADD COLUMN regexlabel VARCHAR (255) NULL


The patch file and the modified SugarCrm files can be downloaded here 2010-08-25-RegexFieldValidation.zip

These files are used at your own risk ... (the normal disclaimer goes here)

Friday, August 20, 2010

Module Builder support for Regex validation in textfield - part 1

This post is based on the SugarCrm 5.5.2 codebase.

This hack will allow adding regular expression validation from module builder. It also allows the user to add custom validation error message for the field in question.
Go to Admin -> Module Builder. Open suitable package & module, then select fields in left tree.

Click on the Add field button. It will open a new tab named Edit Field.


  1. Enter field name, label, regular expression, regex message then press save. The new field will be created. Then edit the editview layout and add the newly created field on in editview. Save layout & deploy.
  2. Go to module in question to create an new or edit an existing entry to test the regex validation.
  3. This customization is not upgrade safe.
This is a complement to the hack described in the post located at Custom javascript validation scripts in Module Builder.

I will post the code changes in another posting shortly



The second part of this posting can be found here Module Builder support for Regex validation in textfield - part 2

Monday, August 16, 2010

Windows Console progress bar

For pure nostalgic reasons I was looking into some old code. It is a console application, to tweak the UI (or lack of same) I wanted to provide a progress bar in the console window.



Client code for the screenshot above


using System;
using TMData.Console;
using System.Threading;

namespace ConsoleApplication
{
 /// <summary>
 /// Summary description for Class1.
 /// </summary>
 class Class1
 {
  /// <summary>
  /// The main entry point for the application.
  /// </summary>
  [STAThread]
  static void Main(string[] args)
  {
   // Where in the console should the progress bar be located
   ConsoleCoordinate consoleCoordinate = new ConsoleCoordinate(10,2);

   // Instantiate the progress bar
   ConsoleProgressBar consoleProgressBar = new ConsoleProgressBar(StandardHandle.Output, 0, 120, 50, consoleCoordinate);

   int x = 0;
   while (x < 120)
   {
    consoleProgressBar.ShowProgressInPercent(x);
    x++;
    Thread.Sleep(100);
   }
  }
 }
}

Helper classes and structs


using System;
using System.Runtime.InteropServices;

namespace TMData.Console
{
 /// <summary>
 /// Summary description for ConsoleCoordinate.
 /// </summary>
 [StructLayout(LayoutKind.Sequential)]
 public struct ConsoleCoordinate
 {
  private short _x;
  private short _y;

  public ConsoleCoordinate(short x, short y)
  {
   _x = x;
   _y = y;
  }
 }
}

using System;

namespace TMData.Console
{
 public enum StandardHandle
 {
  Input = -10,
  Output = -11,
  Error = -12
 }
}

And finally the main console progress class


using System;
using System.Runtime.InteropServices; 


namespace TMData.Console
{
 /// <summary>
 /// Summary description for ConsoleProgressBar.
 /// </summary>
 public class ConsoleProgressBar
 {
  private float minimumValue = 0;
  private float maximumValue = 0;
  private IntPtr handle;
  private ConsoleCoordinate consoleCoordinate;
  private int progressBarSize = 50;

  public ConsoleProgressBar(StandardHandle handle, int minimumValue, int maximumValue, int progressBarSize, ConsoleCoordinate consoleCoordinate)
  {
   this.handle = GetStdHandle((int)handle);;
   this.minimumValue = (float)minimumValue;
   this.maximumValue = (float)maximumValue;
   this.consoleCoordinate = consoleCoordinate;
  }
  
  public void ShowProgressInPercent (int currentValue)
  {
   int percentComplete =  Convert.ToInt32((currentValue / maximumValue) * 100);
   ShowProgress(percentComplete, percentComplete, "%", "100");
  }

  private void ShowProgress(int currentValue, int percentComplete, string unitIndicator, string maxDisplayValue)
  {
   // Calculations
   int incrementPercent = (int)100.0 / progressBarSize;
   int completeLength = Convert.ToInt32(percentComplete / incrementPercent);

   // Constructing the full string making up the progressbar
   string statusText = currentValue.ToString().PadLeft(maxDisplayValue.Length, ' ') + unitIndicator + " / " + maxDisplayValue + unitIndicator;
   int startPosOfStatusTextInProgressBar = (int)((float)progressBarSize / 2.0 - (int)(statusText.Length / 2.0 ));
   statusText = statusText.PadLeft(startPosOfStatusTextInProgressBar + statusText.Length, ' ');
   statusText = statusText.PadRight(progressBarSize,' ');
   
   // Preparing the completed/incomplete progressbar text string
   string completeText = statusText.Substring(0, completeLength);
   string incompleteText = statusText.Substring(completeText.Length, statusText.Length - completeText.Length);
   bool bResult = SetConsoleCursorPosition(handle, consoleCoordinate);

   // Printing to console and coloring
   SetConsoleTextAttribute(handle, 31);
   System.Console.Write(completeText);
   SetConsoleTextAttribute(handle, 127);
   System.Console.Write(incompleteText);
  }

  #region DllImports
  [DllImport("kernel32.dll")]
  public static extern IntPtr GetStdHandle(int intHandleType);

  [DllImport("kernel32.dll")]
  public static extern bool SetConsoleTextAttribute(IntPtr handle, int wAttributes);

  [DllImport("kernel32.dll", SetLastError=true)]
  public static extern bool SetConsoleCursorPosition(IntPtr handle, ConsoleCoordinate inCoordCharacterAttributes);
  #endregion
 }
}

Custom javascript validation scripts in Module Builder

Often when creating edit views in any application there is a need to have complex validation rules. Most of the time more than one field is evaluated to validate the data entered. SugarCrm have a lot of built in support for single field validations but if you want multiple fields in a validations rule or interdependencies between fields you will need to code this yourself.

A way to achieve this would be to generate the initial edit view in module builder and then manually change the generated code file. Depending on the way you implement the code changes and validation this file might no longer be module builder compatible.

This posting will explain how you can modify SugarCrm 5.5.2 code to add support for custom JavaScript in the module builder to validate form.  Error messages will be presented to the user like all other validation error messages.
  1. Go to Admin ->  ModuleBuilder. Open desired package/module/fields.
  2. Add new field (select a suitable field type).
  3. In the field edit screen there are 3 new fields:  JS function name, JS function code and JS Error message.
  4. In JS function name only enter the function name without parentheses like check_dates.
  5. In JS function code enter the full JavaScript function code including the function keyword.
  6. In JS error message enter the custom error message.

Please see the image below

There are some restrictions on what the validation function can do:
  • The function will have only one argument passed as a parameter "form". Using this parameter the custom function can access any field value.
  • The function will a boolean (true/false). If validation passed then true else false.
  • Only use javascript multi line comment in function code /* comment */.
When the validation fails then the defined JS Error message will be shown. This message can be localized from SugarCrm "Labels" screen, if you have my patch for built-in translation support in module builder). It can be localized for each installed language.

Here is a sample validation function:

/* use multiline comment only. form is required in parameter as it will contain the form name for  EditView and sidecreateview*/

function check_start_date(form) { /* declaring and initialising with the html form element for reducing redundancy */
 var startDate = document.forms[form].elements['start_date'];
 var endDate = document.forms[form].elements['end_date']; /* declaring the date var for fields*/
 var startDateVal;
 var endDateVal; /* check field exist and must not be empty */
 if (startDate != '' || enddate != '' || startDate.value.length > 0 || endDate.value.length > 0) {
  startDateVal = Date.parse(startDate.value);
  endDateVal = Date.parse(endDate.value);
  if (startDateVal <= endDateVal) {
   return true;
  } else {
   return false;
  }
 } else {
  return false;
 }
}

This patch/hack is not upgrade safe, which means you cannot just upgrade to a newer official SugarCrm release.

The following lists the changed files:



include/EditView/EditView2.php
(function display to create & assign javascript code)

//HACK: MB support for customcode actions/validation (onblur/onsave)
$tempscript='';
//HACK: MB support for customcode actions/validation (onblur/onsave)
if(isset($this->defs['templateMeta']['javascript'])) {
   if(is_array($this->defs['templateMeta']['javascript'])) {
  $this->th->ss->assign('externalJSFile', 'modules/' . $this->module . '/metadata/editvewdefs.js');
   } else {
  //HACK: MB support for customcode actions/validation (onblur/onsave)
  //$this->th->ss->assign('scriptBlocks', $this->defs['templateMeta']['javascript']);
  $tempscript=$this->defs['templateMeta']['javascript'];
  //HACK: MB support for customcode actions/validation (onblur/onsave)
   }
}

$this->th->ss->assign('id', $this->fieldDefs['id']['value']);
$this->th->ss->assign('offset', $this->offset + 1);
$this->th->ss->assign('APP', $app_strings);
$this->th->ss->assign('MOD', $mod_strings);

//HACK: MB support for customcode actions/validation (onblur/onsave)
foreach($this->fieldDefs as $key => $val){
 if(isset($this->fieldDefs[$key]['validation']['functionName']) && !empty($this->fieldDefs[$key]['validation']['functionName'])){
  if(isset($this->fieldDefs[$key]['customjs'])){
     $tempscript.='{literal}<script type="text/javascript" language="Javascript">'.html_entity_decode($this->fieldDefs[$key]['customjs'],ENT_QUOTES).'</script>{/literal}';
  }
  if(isset($this->fieldDefs[$key]['validation'])&& is_array($this->fieldDefs[$key]['validation'])!=true){
     $this->fieldDefs[$key]['validation'] = unserialize(html_entity_decode($this->fieldDefs[$key]['validation']));
  }
 }
}
$this->th->ss->assign('scriptBlocks', $tempscript);
//HACK: MB support for customcode actions/validation (onblur/onsave)



include/javascript/javascript.php
(function addField modified to add new case for custom function)

//HACK: MB support for customcode actions/validation (onblur/onsave)
case 'customFunction':
 $functionName = $this->sugarbean->field_name_map[$field]['validation']['functionName'];
 if(!empty($functionName)){
  $errorMessage = $this->sugarbean->field_name_map[$field]['validation']['errorMessage'];
  $fieldType = $this->sugarbean->field_name_map[$field]['validation']['type'];
  $required = 'true';
  if(!empty($displayField)){
   $dispField = $displayField;
  }
  else{
   $dispField = $field;
  }
  $this->addToValidateCustomFunction($dispField,$fieldType,$vname,$required,$prefix,$functionName,$errorMessage);                 
 }else{
  if(!empty($displayField)){
   $dispField = $displayField;
  }
  else{
   $dispField = $field;
  }
  $type = (!empty($this->sugarbean->field_name_map[$field]['custom_type']))?$this->sugarbean->field_name_map[$field]['custom_type']:$this->sugarbean->field_name_map[$field]['type'];
  $this->addFieldGeneric($dispField,$type,$vname,$required,$prefix );       
 }
 break;
//HACK: MB support for customcode actions/validation (onblur/onsave)

(new function added in class JavaScript.)

//HACK: MB support for customcode actions/validation (onblur/onsave)
function addToValidateCustomFunction($field, $type, $displayName, $required, $prefix='',$functionName='',$errorMessage=''){
 $this->script .= "addToValidateCustomFunction('".$this->formname."', '".$prefix.$field."', '".$type . "', $required,'".$this->stripEndColon(translate($displayName,$this->sugarbean->module_dir))."' , '$functionName','".$this->stripEndColon(translate($errorMessage,$this->sugarbean->module_dir))."' );\n";
}
//HACK: MB support for customcode actions/validation (onblur/onsave)



include/javascript/sugar_grp1.js
(new global variable & function added to add validation to validation queue.)

//HACK: MB support for customcode actions/validation (onblur/onsave)
var customFunctionName = ''; 
//HACK: MB support for customcode actions/validation (onblur/onsave)

//HACK: MB support for customcode actions/validation (onblur/onsave)

function addToValidateCustomFunction(formname, name, type, required, msg, functionName, errorMessage) {
 required = false;
 if (errorMessage != '') {
  msg = msg + " " + errorMessage;
 }
 addToValidate(formname, name, type, required, msg);
 validate[formname][validate[formname].length - 1][jstypeIndex] = 'customFunction';
 validate[formname][validate[formname].length - 1][customFunctionName] = functionName;
} 
//HACK: MB support for customcode actions/validation (onblur/onsave)

(function validate_form modified and new case added for customfunction. This code will call custom js function.)

//HACK: MB support for customcode actions/validation (onblur/onsave)
case 'customFunction':
 var functionName = validate[formname][i][customFunctionName];

 var fn = functionName + '(\'' + formname + '\')';
 var execstatus = true;
 try {
  execstatus = eval(fn);
 } catch (err) {
  alert(err);
  execstatus = false;
 }
 if (execstatus == false) {
  isError = true;
  add_error_style(formname, validate[formname][i][nameIndex], validate[formname][i][msgIndex]);
 }
 break;
//HACK: MB support for customcode actions/validation (onblur/onsave)



modules/DynamicFields/templates/Fields/Forms/coreTop.tpl
(the following code will add 3 new property in EditField window in module builder. (smarty template))

{* HACK: MB support for customcode actions/validation (onblur/onsave)*}
<tr>
 <td class='mbLBL'>{$MOD.COLUMN_TITLE_CUSTOMJS_NAME_TEXT}</td>
 <td>{if $hideLevel < 5 && $MB == 1}
  <input type="text" name="validation" value="{$vardef.validation.functionName}">
  {else}
  <input type="hidden" name="validation" value="{$vardef.validation.functionName}">{$vardef.validation.functionName}
  {/if}
 </td>
</tr>
<tr>
 <td class='mbLBL'>{$MOD.COLUMN_TITLE_CUSTOMJS_TEXT}</td>
 <td>{if $hideLevel < 5 && $MB == 1}
  <textarea name="customjs">{$vardef.customjs}</textarea>
  {else}
  <input type="hidden" name="customjs" value="{$vardef.customjs}">{$vardef.customjs}
  {/if}
 </td>
</tr>
<tr>
 <td class='mbLBL'>{$MOD.COLUMN_TITLE_CUSTOMJS_ERROR_TEXT}</td>
 <td>{if $hideLevel < 5 && $MB == 1}
  <input type="text" name="customerrormsg" value="{$vardef.validation.errorMessage}">
  {else}
  <input type="hidden" name="customerrormsg" value="{$vardef.validation.errorMessage}">{$vardef.validation.errorMessage}
  {/if}
 </td>
</tr>
{* HACK: MB support for customcode actions/validation (onblur/onsave) *}



modules/EditCustomFields/language/en_us.lang.php
(labels for 3 new property in MB EditField screen)

// HACK: MB support for customcode actions/validation (onblur/onsave)
$mod_strings['COLUMN_TITLE_CUSTOMJS_NAME_TEXT']='JS Function Name:';
$mod_strings['COLUMN_TITLE_CUSTOMJS_TEXT']='JS Function Code:';
$mod_strings['COLUMN_TITLE_CUSTOMJS_ERROR_TEXT']='JS Error Message:';
//HACK: MB support for customcode actions/validation (onblur/onsave)



modules/EditCustomFields/vardefs.php
(2 new field in EditCustomFields vardefs)

//HACK: MB support for customcode actions/validation (onblur/onsave)
$dictionary['FieldsMetaData']['fields']['customjs']=array('name' =>'customjs','type' =>'text','default'=>'');
$dictionary['FieldsMetaData']['fields']['validation']=array('name' =>'validation','type' =>'text','default'=>'');
//HACK: MB support for customcode actions/validation (onblur/onsave)



modules/ModuleBuilder/controller.php
(function action_SaveField is modified to set custom js function and function name in vardefs. It will also set custom error message to language file.)

//HACK :MB support for customcode actions/validation (onblur/onsave)
if(isset($_REQUEST['validation']) && isset($_REQUEST['customjs']) && mb_strlen(trim($_REQUEST['validation']))>0 && mb_strlen(trim($_REQUEST['customjs']))>0){
 $module->mbvardefs->vardef['fields'][$_REQUEST['name']]['validation']=array('type' => 'customFunction','functionName'=>$_REQUEST['validation'],'errorMessage'=>'LBL_'.mb_strtoupper($_REQUEST['name']).'_JS_ERR_MSG');
 $module->mbvardefs->vardef['fields'][$_REQUEST['name']]['customjs']=$_REQUEST['customjs'];
}
//HACK: MB support for customcode actions/validation (onblur/onsave)
$module->mbvardefs->save () ;
// get the module again to refresh the labels we might have saved with the $field->save (e.g., for address fields)
$module = & $mb->getPackageModule ( $_REQUEST [ 'view_package' ], $_REQUEST [ 'view_module' ] ) ;
if (isset ( $_REQUEST [ 'label' ] ) && isset ( $_REQUEST [ 'labelValue' ] ))
 $module->setLabel ( $GLOBALS [ 'current_language' ], $_REQUEST [ 'label' ], $_REQUEST [ 'labelValue' ] ) ;
//HACK: MB support for customcode actions/validation (onblur/onsave)
if(isset($_REQUEST['validation']) && isset($_REQUEST['customjs']) && mb_strlen(trim($_REQUEST['validation']))>0 && mb_strlen(trim($_REQUEST['customjs']))>0){
 $module->setLabel($GLOBALS['current_language'],'LBL_'.mb_strtoupper($_REQUEST['name']).'_JS_ERR_MSG',$_REQUEST['customerrormsg']);   }            
//HACK: MB support for customcode actions/validation (onblur/onsave)



modules/ModuleBuilder/views/view.modulefield.php
(custom validation error is retrieved from language file & set in $vardef['validation']['errorMessage'] so it can be shown on EditField screen.)

//HACK: MB support for customcode actions/validation (onblur/onsave)
if(isset($vardef['validation']['errorMessage']) && !empty($vardef['validation']['errorMessage']))
 $vardef['validation']['errorMessage'] = $module->getLabel($GLOBALS['current_language'],$vardef['validation']['errorMessage']);
//HACK: MB support for customcode actions/validation (onblur/onsave)



(the following SQL statement will add 2 new field in fields_meta_data table in the sugarcrm database)

ALTER TABLE fields_meta_data
add column customjs text  NULL ,
add column validation text  NULL

Changes in the vardefs.php & language files generated by module builder when using this hack are not upgrade safe.


And here is the SVN patch/diff file


Index: include/EditView/EditView2.php
===================================================================
--- include/EditView/EditView2.php (revision 1)
+++ include/EditView/EditView2.php (working copy)
@@ -433,13 +433,17 @@
      */
     function display($showTitle = true, $ajaxSave = false) {
         global $mod_strings, $sugar_config, $app_strings, $app_list_strings, $theme, $current_user;
-
-
+  //HACK : MB support for customcode actions/validation (onblur/onsave)
+        $tempscript='';
+  //HACK : MB support for customcode actions/validation (onblur/onsave)
         if(isset($this->defs['templateMeta']['javascript'])) {
            if(is_array($this->defs['templateMeta']['javascript'])) {
              $this->th->ss->assign('externalJSFile', 'modules/' . $this->module . '/metadata/editvewdefs.js');
            } else {
-             $this->th->ss->assign('scriptBlocks', $this->defs['templateMeta']['javascript']);
+             //HACK : MB support for customcode actions/validation (onblur/onsave)
+             //$this->th->ss->assign('scriptBlocks', $this->defs['templateMeta']['javascript']);
+             $tempscript=$this->defs['templateMeta']['javascript'];
+             //HACK : MB support for customcode actions/validation (onblur/onsave)
            }
         }
 
@@ -447,6 +451,21 @@
         $this->th->ss->assign('offset', $this->offset + 1);
         $this->th->ss->assign('APP', $app_strings);
         $this->th->ss->assign('MOD', $mod_strings);
+
+  //HACK : MB support for customcode actions/validation (onblur/onsave)
+  foreach($this->fieldDefs as $key => $val){
+            if(isset($this->fieldDefs[$key]['validation']['functionName']) && !empty($this->fieldDefs[$key]['validation']['functionName'])){
+    if(isset($this->fieldDefs[$key]['customjs'])){
+                $tempscript.='{literal}<script type="text/javascript" language="Javascript">'.html_entity_decode($this->fieldDefs[$key]['customjs'],ENT_QUOTES).'</script>{/literal}';
+             }
+             if(isset($this->fieldDefs[$key]['validation'])&& is_array($this->fieldDefs[$key]['validation'])!=true){
+                $this->fieldDefs[$key]['validation'] = unserialize(html_entity_decode($this->fieldDefs[$key]['validation']));
+             }
+   }
+  }
+  $this->th->ss->assign('scriptBlocks', $tempscript);
+  //HACK : MB support for customcode actions/validation (onblur/onsave)
+
         $this->th->ss->assign('fields', $this->fieldDefs);
         $this->th->ss->assign('sectionPanels', $this->sectionPanels);
         $this->th->ss->assign('returnModule', $this->returnModule);
Index: include/javascript/javascript.php
===================================================================
--- include/javascript/javascript.php (revision 1)
+++ include/javascript/javascript.php (working copy)
@@ -124,6 +124,33 @@
       $this->addFieldDateBeforeAllowBlank($dispField,$this->sugarbean->field_name_map[$field]['type'],$vname,$required,$prefix, $compareTo );
       else $this->addFieldDateBefore($dispField,$this->sugarbean->field_name_map[$field]['type'],$vname,$required,$prefix, $compareTo );
       break;
+
+     //HACK : MB support for customcode actions/validation (onblur/onsave)
+     case 'customFunction':
+      $functionName = $this->sugarbean->field_name_map[$field]['validation']['functionName'];
+      if(!empty($functionName)){
+       $errorMessage = $this->sugarbean->field_name_map[$field]['validation']['errorMessage'];
+       $fieldType = $this->sugarbean->field_name_map[$field]['validation']['type'];
+       $required = 'true';
+       if(!empty($displayField)){
+        $dispField = $displayField;
+       }
+       else{
+        $dispField = $field;
+       }
+       $this->addToValidateCustomFunction($dispField,$fieldType,$vname,$required,$prefix,$functionName,$errorMessage);                 
+      }else{
+       if(!empty($displayField)){
+        $dispField = $displayField;
+       }
+       else{
+        $dispField = $field;
+       }
+       $type = (!empty($this->sugarbean->field_name_map[$field]['custom_type']))?$this->sugarbean->field_name_map[$field]['custom_type']:$this->sugarbean->field_name_map[$field]['type'];
+       $this->addFieldGeneric($dispField,$type,$vname,$required,$prefix );       
+      }
+      break;
+     //HACK : MB support for customcode actions/validation (onblur/onsave)
      default: 
       if(!empty($displayField)){
        $dispField = $displayField;
@@ -208,7 +235,11 @@
   $arr = $json->encode($arr);
   $this->script .= "addToValidateIsInArray('{$this->formname}', '{$name}', '{$type}', {$req}, '".$this->stripEndColon(translate($displayName,$this->sugarbean->module_dir))."', '{$arr}', '{$operator}');\n";
     }
-
+    //HACK : MB support for customcode actions/validation (onblur/onsave)
+ function addToValidateCustomFunction($field, $type, $displayName, $required, $prefix='',$functionName='',$errorMessage=''){
+  $this->script .= "addToValidateCustomFunction('".$this->formname."', '".$prefix.$field."', '".$type . "', $required,'".$this->stripEndColon(translate($displayName,$this->sugarbean->module_dir))."' , '$functionName','".$this->stripEndColon(translate($errorMessage,$this->sugarbean->module_dir))."' );\n";
+ }
+ //HACK : MB support for customcode actions/validation (onblur/onsave)
  function addAllFields($prefix,$skip_fields=null, $translate = false){
   if (!isset($skip_fields))
   {
Index: include/javascript/sugar_grp1.js
===================================================================
--- include/javascript/sugar_grp1.js (revision 1)
+++ include/javascript/sugar_grp1.js (working copy)
@@ -32,6 +32,24 @@
  * technical reasons, the Appropriate Legal Notices must display the words
  * "Powered by SugarCRM".
  ********************************************************************************/
+ 
+/*HACK : MB support for customcode actions/validation (onblur/onsave)*/
+var customFunctionName = ''; 
+/*HACK : MB support for customcode actions/validation (onblur/onsave)*/
+
+/*HACK : MB support for customcode actions/validation (onblur/onsave)*/
+
+function addToValidateCustomFunction(formname, name, type, required, msg, functionName, errorMessage) {
+ required = false;
+ if (errorMessage != '') {
+  msg = msg + " " + errorMessage;
+ }
+ addToValidate(formname, name, type, required, msg);
+ validate[formname][validate[formname].length - 1][jstypeIndex] = 'customFunction';
+ validate[formname][validate[formname].length - 1][customFunctionName] = functionName;
+} 
+/*HACK : MB support for customcode actions/validation (onblur/onsave)*/
+
 if(typeof(SUGAR)=="undefined")SUGAR={};if(typeof(SUGAR.themes)=="undefined")SUGAR.themes={};SUGAR.sugarHome={};SUGAR.subpanelUtils={};SUGAR.ajaxStatusClass={};SUGAR.tabChooser={};SUGAR.util={};SUGAR.savedViews={};SUGAR.dashlets={};SUGAR.unifiedSearchAdvanced={};SUGAR.searchForm={};SUGAR.language={};SUGAR.Studio={};SUGAR.contextMenu={};var dtCh="-";var minYear=1900;var maxYear=2100;var nameIndex=0;var typeIndex=1;var requiredIndex=2;var msgIndex=3;var jstypeIndex=5;var minIndex=10;var maxIndex=11;var altMsgIndex=15;var compareToIndex=7;var arrIndex=12;var operatorIndex=13;var allowblank=8;var validate=new Array();var maxHours=24;var requiredTxt='Missing Required Field:'
 var invalidTxt='Invalid Value:'
 var secondsSinceLoad=0;var inputsWithErrors=new Array();var lastSubmitTime=0;var alertList=new Array();var oldStartsWith='';function isSupportedIE(){var userAgent=navigator.userAgent.toLowerCase();if(userAgent.indexOf("msie")!=-1&&userAgent.indexOf("mac")==-1&&userAgent.indexOf("opera")==-1){var version=navigator.appVersion.match(/MSIE (.\..)/)[1];if(version>=5.5){return true;}else{return false;}}}
@@ -161,53 +179,356 @@
 function hex2dec(hex){return(parseInt(hex,16));}
 var hexDigit=new Array("0","1","2","3","4","5","6","7","8","9","A","B","C","D","E","F");function dec2hex(dec){return(hexDigit[dec>>4]+hexDigit[dec&15]);}
 function fade_error_style(normalStyle,percent){errorStyle='c60c30';var r1=hex2dec(errorStyle.slice(0,2));var g1=hex2dec(errorStyle.slice(2,4));var b1=hex2dec(errorStyle.slice(4,6));var r2=hex2dec(normalStyle.slice(0,2));var g2=hex2dec(normalStyle.slice(2,4));var b2=hex2dec(normalStyle.slice(4,6));var pc=percent/100;r=Math.floor(r1+(pc*(r2-r1))+.5);g=Math.floor(g1+(pc*(g2-g1))+.5);b=Math.floor(b1+(pc*(b2-b1))+.5);for(var wp=0;wp<inputsWithErrors.length;wp++){inputsWithErrors[wp].style.backgroundColor="#"+dec2hex(r)+dec2hex(g)+dec2hex(b);}}
-function validate_form(formname,startsWith){requiredTxt=SUGAR.language.get('app_strings','ERR_MISSING_REQUIRED_FIELDS');invalidTxt=SUGAR.language.get('app_strings','ERR_INVALID_VALUE');if(typeof(formname)=='undefined')
-{return false;}
-if(typeof(validate[formname])=='undefined')
-{return true;}
-var form=document.forms[formname];var isError=false;var errorMsg="";var _date=new Date();if(_date.getTime()<(lastSubmitTime+2000)&&startsWith==oldStartsWith){return false;}
-lastSubmitTime=_date.getTime();oldStartsWith=startsWith;clear_all_errors();inputsWithErrors=new Array();for(var i=0;i<validate[formname].length;i++){if(validate[formname][i][nameIndex].indexOf(startsWith)==0){if(typeof form[validate[formname][i][nameIndex]]!='undefined'){var bail=false;if(validate[formname][i][requiredIndex]&&validate[formname][i][typeIndex]!='bool'){if(typeof form[validate[formname][i][nameIndex]]=='undefined'||trim(form[validate[formname][i][nameIndex]].value)==""){add_error_style(formname,validate[formname][i][nameIndex],requiredTxt+' '+validate[formname][i][msgIndex]);isError=true;}}
-if(!bail){switch(validate[formname][i][typeIndex]){case'email':if(!isValidEmail(trim(form[validate[formname][i][nameIndex]].value))){isError=true;add_error_style(formname,validate[formname][i][nameIndex],invalidTxt+" "+validate[formname][i][msgIndex]);}
-break;case'time':if(!isTime(trim(form[validate[formname][i][nameIndex]].value))){isError=true;add_error_style(formname,validate[formname][i][nameIndex],invalidTxt+" "+validate[formname][i][msgIndex]);}break;case'date':if(!isDate(trim(form[validate[formname][i][nameIndex]].value))){isError=true;add_error_style(formname,validate[formname][i][nameIndex],invalidTxt+" "+validate[formname][i][msgIndex]);}break;case'alpha':break;case'DBName':if(!isDBName(trim(form[validate[formname][i][nameIndex]].value))){isError=true;add_error_style(formname,validate[formname][i][nameIndex],invalidTxt+" "+validate[formname][i][msgIndex]);}
-break;case'alphanumeric':break;case'int':if(!isInteger(trim(form[validate[formname][i][nameIndex]].value))){isError=true;add_error_style(formname,validate[formname][i][nameIndex],invalidTxt+" "+validate[formname][i][msgIndex]);}
-break;case'currency':case'float':if(!isFloat(trim(form[validate[formname][i][nameIndex]].value))){isError=true;add_error_style(formname,validate[formname][i][nameIndex],invalidTxt+" "+validate[formname][i][msgIndex]);}
-break;case'teamset_mass':div_element_id=formname+'_'+form[validate[formname][i][nameIndex]].name+'_operation_div';input_elements=YAHOO.util.Selector.query('input',document.getElementById(div_element_id));primary_field_id='';validation_passed=false;replace_selected=false;for(t in input_elements){if(input_elements[t].type&&input_elements[t].type=='radio'&&input_elements[t].checked==true&&input_elements[t].value=='replace'){radio_elements=YAHOO.util.Selector.query('input[type=radio]',document.getElementById(formname+'_team_name_table'));for(x in radio_elements){if(radio_elements[x].name!='team_name_type'){primary_field_id='team_name_collection_'+radio_elements[x].value;if(radio_elements[x].checked){replace_selected=true;if(trim(document.forms[formname].elements[primary_field_id].value)!=''){validation_passed=true;break;}}else if(trim(document.forms[formname].elements[primary_field_id].value)!=''){replace_selected=true;}}}}}
-if(replace_selected&&!validation_passed){add_error_style(formname,primary_field_id,SUGAR.language.get('app_strings','ERR_NO_PRIMARY_TEAM_SPECIFIED'));isError=true;}
-break;case'teamset':table_element_id=formname+'_'+form[validate[formname][i][nameIndex]].name+'_table';if(document.getElementById(table_element_id)){input_elements=YAHOO.util.Selector.query('input[type=radio]',document.getElementById(table_element_id));has_primary=false;primary_field_id=form[validate[formname][i][nameIndex]].name+'_collection_0';for(t in input_elements){primary_field_id=form[validate[formname][i][nameIndex]].name+'_collection_'+input_elements[t].value;if(input_elements[t].type&&input_elements[t].type=='radio'&&input_elements[t].checked==true){if(document.forms[formname].elements[primary_field_id].value!=''){has_primary=true;}
-break;}}
-if(!has_primary){isError=true;field_id=form[validate[formname][i][nameIndex]].name+'_collection_'+input_elements[0].value;add_error_style(formname,field_id,SUGAR.language.get('app_strings','ERR_NO_PRIMARY_TEAM_SPECIFIED'));}}
-break;case'error':isError=true;add_error_style(formname,validate[formname][i][nameIndex],validate[formname][i][msgIndex]);break;}
-if(typeof validate[formname][i][jstypeIndex]!='undefined'){switch(validate[formname][i][jstypeIndex]){case'range':if(!inRange(trim(form[validate[formname][i][nameIndex]].value),validate[formname][i][minIndex],validate[formname][i][maxIndex])){isError=true;var lbl_validate_range=SUGAR.language.get('app_strings','LBL_VALIDATE_RANGE');add_error_style(formname,validate[formname][i][nameIndex],validate[formname][i][msgIndex]+" value "+form[validate[formname][i][nameIndex]].value+" "+lbl_validate_range+" ("+validate[formname][i][minIndex]+" - "+validate[formname][i][maxIndex]+") ");}
-break;case'isbefore':compareTo=form[validate[formname][i][compareToIndex]];if(typeof compareTo!='undefined'){if(trim(compareTo.value)!=''||(validate[formname][i][allowblank]!='true')){date2=trim(compareTo.value);date1=trim(form[validate[formname][i][nameIndex]].value);if(trim(date1).length!=0&&!isBefore(date1,date2)){isError=true;add_error_style(formname,validate[formname][i][nameIndex],validate[formname][i][msgIndex]+"("+date1+") "+SUGAR.language.get('app_strings','MSG_IS_NOT_BEFORE')+' '+date2);}}}
-break;case'less':value=parseInt(trim(form[validate[formname][i][nameIndex]].value));maximum=parseInt(validate[formname][i][maxIndex]);if(typeof maximum!='undefined'){if(value>maximum){isError=true;add_error_style(formname,validate[formname][i][nameIndex],validate[formname][i][msgIndex]+" "+SUGAR.language.get('app_strings','MSG_IS_MORE_THAN')+' '+validate[formname][i][altMsgIndex]);}}
-break;case'more':value=parseInt(trim(form[validate[formname][i][nameIndex]].value));minimum=parseInt(validate[formname][i][minIndex]);if(typeof minimum!='undefined'){if(value<minimum){isError=true;add_error_style(formname,validate[formname][i][nameIndex],validate[formname][i][msgIndex]+" "+SUGAR.language.get('app_strings','MSG_SHOULD_BE')+' '+minimum+' '+SUGAR.language.get('app_strings','MSG_OR_GREATER'));}}
-break;case'binarydep':compareTo=form[validate[formname][i][compareToIndex]];if(typeof compareTo!='undefined'){item1=trim(form[validate[formname][i][nameIndex]].value);item2=trim(compareTo.value);if(!bothExist(item1,item2)){isError=true;add_error_style(formname,validate[formname][i][nameIndex],validate[formname][i][msgIndex]);}}
-break;case'comparison':compareTo=form[validate[formname][i][compareToIndex]];if(typeof compareTo!='undefined'){item1=trim(form[validate[formname][i][nameIndex]].value);item2=trim(compareTo.value);if(!bothExist(item1,item2)||item1!=item2){isError=true;add_error_style(formname,validate[formname][i][nameIndex],validate[formname][i][msgIndex]);}}
-break;case'in_array':arr=eval(validate[formname][i][arrIndex]);operator=validate[formname][i][operatorIndex];item1=trim(form[validate[formname][i][nameIndex]].value);if(operator.charAt(0)=='u'){item1=item1.toUpperCase();operator=operator.substring(1);}else if(operator.charAt(0)=='l'){item1=item1.toLowerCase();operator=operator.substring(1);}
-for(j=0;j<arr.length;j++){val=arr[j];if((operator=="=="&&val==item1)||(operator=="!="&&val!=item1)){isError=true;add_error_style(formname,validate[formname][i][nameIndex],invalidTxt+" "+validate[formname][i][msgIndex]);}}
-break;case'verified':if(trim(form[validate[formname][i][nameIndex]].value)=='false'){isError=true;}
-break;}}}}}}
-if(formsWithFieldLogic){var invalidLogic=false;if(formsWithFieldLogic.min&&formsWithFieldLogic.max&&formsWithFieldLogic._default){var showErrorsOn={min:{value:'min',show:false,obj:formsWithFieldLogic.min.value},max:{value:'max',show:false,obj:formsWithFieldLogic.max.value},_default:{value:'default',show:false,obj:formsWithFieldLogic._default.value},len:{value:'len',show:false,obj:parseInt(formsWithFieldLogic.len.value)}};var min=(formsWithFieldLogic.min.value!='')?parseInt(formsWithFieldLogic.min.value):'undef';var max=(formsWithFieldLogic.max.value!='')?parseInt(formsWithFieldLogic.max.value):'undef';var _default=(formsWithFieldLogic._default.value!='')?parseInt(formsWithFieldLogic._default.value):'undef';for(var i in showErrorsOn){if(showErrorsOn[i].value!='len'&&showErrorsOn[i].obj.length>showErrorsOn.len.obj){invalidLogic=true;showErrorsOn[i].show=true;showErrorsOn.len.show=true;}}
-if(min!='undef'&&max!='undef'&&_default!='undef'){if(!inRange(_default,min,max)){invalidLogic=true;showErrorsOn.min.show=true;showErrorsOn.max.show=true;showErrorsOn._default.show=true;}}
-if(min!='undef'&&max!='undef'&&min>max){invalidLogic=true;showErrorsOn.min.show=true;showErrorsOn.max.show=true;}
-if(min!='undef'&&_default!='undef'&&_default<min){invalidLogic=true;showErrorsOn.min.show=true;showErrorsOn._default.show=true;}
-if(max!='undef'&&_default!='undef'&&_default>max){invalidLogic=true;showErrorsOn.max.show=true;showErrorsOn._default.show=true;}
-if(invalidLogic){isError=true;for(var error in showErrorsOn)
-if(showErrorsOn[error].show)
-add_error_style(formname,showErrorsOn[error].value,formsWithFieldLogic.msg);}
-else if(!isError)
-formsWithFieldLogic=null;}}
-if(formWithPrecision){if(!isValidPrecision(formWithPrecision.float.value,formWithPrecision.precision.value)){isError=true;add_error_style(formname,'default',SUGAR.language.get('app_strings','ERR_COMPATIBLE_PRECISION_VALUE'));}else if(!isError){isError=false;}}
-if(isError==true){var nw,ne,sw,se;if(self.pageYOffset)
-{nwX=self.pageXOffset;seX=self.innerWidth;nwY=self.pageYOffset;seY=self.innerHeight;}
-else if(document.documentElement&&document.documentElement.scrollTop)
-{nwX=document.documentElement.scrollLeft;seX=document.documentElement.clientWidth;nwY=document.documentElement.scrollTop;seY=document.documentElement.clientHeight;}
-else if(document.body)
-{nwX=document.body.scrollLeft;seX=document.body.clientWidth;nwY=document.body.scrollTop;seY=document.body.clientHeight;}
-var inView=true;for(var wp=0;wp<inputsWithErrors.length;wp++){var elementCoor=findElementPos(inputsWithErrors[wp]);if(!(elementCoor.x>=nwX&&elementCoor.y>=nwY&&elementCoor.x<=seX&&elementCoor.y<=seY)){inView=false;scrollToTop=elementCoor.y-75;scrollToLeft=elementCoor.x-75;}
-else{break;}}
-if(!inView)window.scrollTo(scrollToTop,scrollToLeft);return false;}
-return true;}
+function validate_form(formname, startsWith) {
+ requiredTxt = SUGAR.language.get('app_strings', 'ERR_MISSING_REQUIRED_FIELDS');
+ invalidTxt = SUGAR.language.get('app_strings', 'ERR_INVALID_VALUE');
+ if (typeof(formname) == 'undefined') {
+  return false;
+ }
+ if (typeof(validate[formname]) == 'undefined') {
+  return true;
+ }
+ var form = document.forms[formname];
+ var isError = false;
+ var errorMsg = "";
+ var _date = new Date();
+ if (_date.getTime() < (lastSubmitTime + 2000) && startsWith == oldStartsWith) {
+  return false;
+ }
+ lastSubmitTime = _date.getTime();
+ oldStartsWith = startsWith;
+ clear_all_errors();
+ inputsWithErrors = new Array();
+ for (var i = 0; i < validate[formname].length; i++) {
+  if (validate[formname][i][nameIndex].indexOf(startsWith) == 0) {
+   if (typeof form[validate[formname][i][nameIndex]] != 'undefined') {
+    var bail = false;
+    if (validate[formname][i][requiredIndex] && validate[formname][i][typeIndex] != 'bool') {
+     if (typeof form[validate[formname][i][nameIndex]] == 'undefined' || trim(form[validate[formname][i][nameIndex]].value) == "") {
+      add_error_style(formname, validate[formname][i][nameIndex], requiredTxt + ' ' + validate[formname][i][msgIndex]);
+      isError = true;
+     }
+    }
+    if (!bail) {
+     switch (validate[formname][i][typeIndex]) {
+     case 'email':
+      if (!isValidEmail(trim(form[validate[formname][i][nameIndex]].value))) {
+       isError = true;
+       add_error_style(formname, validate[formname][i][nameIndex], invalidTxt + " " + validate[formname][i][msgIndex]);
+      }
+      break;
+     case 'time':
+      if (!isTime(trim(form[validate[formname][i][nameIndex]].value))) {
+       isError = true;
+       add_error_style(formname, validate[formname][i][nameIndex], invalidTxt + " " + validate[formname][i][msgIndex]);
+      }
+      break;
+     case 'date':
+      if (!isDate(trim(form[validate[formname][i][nameIndex]].value))) {
+       isError = true;
+       add_error_style(formname, validate[formname][i][nameIndex], invalidTxt + " " + validate[formname][i][msgIndex]);
+      }
+      break;
+     case 'alpha':
+      break;
+     case 'DBName':
+      if (!isDBName(trim(form[validate[formname][i][nameIndex]].value))) {
+       isError = true;
+       add_error_style(formname, validate[formname][i][nameIndex], invalidTxt + " " + validate[formname][i][msgIndex]);
+      }
+      break;
+     case 'alphanumeric':
+      break;
+     case 'int':
+      if (!isInteger(trim(form[validate[formname][i][nameIndex]].value))) {
+       isError = true;
+       add_error_style(formname, validate[formname][i][nameIndex], invalidTxt + " " + validate[formname][i][msgIndex]);
+      }
+      break;
+     case 'currency':
+     case 'float':
+      if (!isFloat(trim(form[validate[formname][i][nameIndex]].value))) {
+       isError = true;
+       add_error_style(formname, validate[formname][i][nameIndex], invalidTxt + " " + validate[formname][i][msgIndex]);
+      }
+      break;
+     case 'teamset_mass':
+      div_element_id = formname + '_' + form[validate[formname][i][nameIndex]].name + '_operation_div';
+      input_elements = YAHOO.util.Selector.query('input', document.getElementById(div_element_id));
+      primary_field_id = '';
+      validation_passed = false;
+      replace_selected = false;
+      for (t in input_elements) {
+       if (input_elements[t].type && input_elements[t].type == 'radio' && input_elements[t].checked == true && input_elements[t].value == 'replace') {
+        radio_elements = YAHOO.util.Selector.query('input[type=radio]', document.getElementById(formname + '_team_name_table'));
+        for (x in radio_elements) {
+         if (radio_elements[x].name != 'team_name_type') {
+          primary_field_id = 'team_name_collection_' + radio_elements[x].value;
+          if (radio_elements[x].checked) {
+           replace_selected = true;
+           if (trim(document.forms[formname].elements[primary_field_id].value) != '') {
+            validation_passed = true;
+            break;
+           }
+          } else if (trim(document.forms[formname].elements[primary_field_id].value) != '') {
+           replace_selected = true;
+          }
+         }
+        }
+       }
+      }
+      if (replace_selected && !validation_passed) {
+       add_error_style(formname, primary_field_id, SUGAR.language.get('app_strings', 'ERR_NO_PRIMARY_TEAM_SPECIFIED'));
+       isError = true;
+      }
+      break;
+     case 'teamset':
+      table_element_id = formname + '_' + form[validate[formname][i][nameIndex]].name + '_table';
+      if (document.getElementById(table_element_id)) {
+       input_elements = YAHOO.util.Selector.query('input[type=radio]', document.getElementById(table_element_id));
+       has_primary = false;
+       primary_field_id = form[validate[formname][i][nameIndex]].name + '_collection_0';
+       for (t in input_elements) {
+        primary_field_id = form[validate[formname][i][nameIndex]].name + '_collection_' + input_elements[t].value;
+        if (input_elements[t].type && input_elements[t].type == 'radio' && input_elements[t].checked == true) {
+         if (document.forms[formname].elements[primary_field_id].value != '') {
+          has_primary = true;
+         }
+         break;
+        }
+       }
+       if (!has_primary) {
+        isError = true;
+        field_id = form[validate[formname][i][nameIndex]].name + '_collection_' + input_elements[0].value;
+        add_error_style(formname, field_id, SUGAR.language.get('app_strings', 'ERR_NO_PRIMARY_TEAM_SPECIFIED'));
+       }
+      }
+      break;
+     case 'error':
+      isError = true;
+      add_error_style(formname, validate[formname][i][nameIndex], validate[formname][i][msgIndex]);
+      break;
+     }
+     if (typeof validate[formname][i][jstypeIndex] != 'undefined') {
+      switch (validate[formname][i][jstypeIndex]) {
+      case 'range':
+       if (!inRange(trim(form[validate[formname][i][nameIndex]].value), validate[formname][i][minIndex], validate[formname][i][maxIndex])) {
+        isError = true;
+        var lbl_validate_range = SUGAR.language.get('app_strings', 'LBL_VALIDATE_RANGE');
+        add_error_style(formname, validate[formname][i][nameIndex], validate[formname][i][msgIndex] + " value " + form[validate[formname][i][nameIndex]].value + " " + lbl_validate_range + " (" + validate[formname][i][minIndex] + " - " + validate[formname][i][maxIndex] + ") ");
+       }
+       break;
+      case 'isbefore':
+       compareTo = form[validate[formname][i][compareToIndex]];
+       if (typeof compareTo != 'undefined') {
+        if (trim(compareTo.value) != '' || (validate[formname][i][allowblank] != 'true')) {
+         date2 = trim(compareTo.value);
+         date1 = trim(form[validate[formname][i][nameIndex]].value);
+         if (trim(date1).length != 0 && !isBefore(date1, date2)) {
+          isError = true;
+          add_error_style(formname, validate[formname][i][nameIndex], validate[formname][i][msgIndex] + "(" + date1 + ") " + SUGAR.language.get('app_strings', 'MSG_IS_NOT_BEFORE') + ' ' + date2);
+         }
+        }
+       }
+       break;
+      case 'less':
+       value = parseInt(trim(form[validate[formname][i][nameIndex]].value));
+       maximum = parseInt(validate[formname][i][maxIndex]);
+       if (typeof maximum != 'undefined') {
+        if (value > maximum) {
+         isError = true;
+         add_error_style(formname, validate[formname][i][nameIndex], validate[formname][i][msgIndex] + " " + SUGAR.language.get('app_strings', 'MSG_IS_MORE_THAN') + ' ' + validate[formname][i][altMsgIndex]);
+        }
+       }
+       break;
+      case 'more':
+       value = parseInt(trim(form[validate[formname][i][nameIndex]].value));
+       minimum = parseInt(validate[formname][i][minIndex]);
+       if (typeof minimum != 'undefined') {
+        if (value < minimum) {
+         isError = true;
+         add_error_style(formname, validate[formname][i][nameIndex], validate[formname][i][msgIndex] + " " + SUGAR.language.get('app_strings', 'MSG_SHOULD_BE') + ' ' + minimum + ' ' + SUGAR.language.get('app_strings', 'MSG_OR_GREATER'));
+        }
+       }
+       break;
+      case 'binarydep':
+       compareTo = form[validate[formname][i][compareToIndex]];
+       if (typeof compareTo != 'undefined') {
+        item1 = trim(form[validate[formname][i][nameIndex]].value);
+        item2 = trim(compareTo.value);
+        if (!bothExist(item1, item2)) {
+         isError = true;
+         add_error_style(formname, validate[formname][i][nameIndex], validate[formname][i][msgIndex]);
+        }
+       }
+       break;
+      case 'comparison':
+       compareTo = form[validate[formname][i][compareToIndex]];
+       if (typeof compareTo != 'undefined') {
+        item1 = trim(form[validate[formname][i][nameIndex]].value);
+        item2 = trim(compareTo.value);
+        if (!bothExist(item1, item2) || item1 != item2) {
+         isError = true;
+         add_error_style(formname, validate[formname][i][nameIndex], validate[formname][i][msgIndex]);
+        }
+       }
+       break;
+      case 'in_array':
+       arr = eval(validate[formname][i][arrIndex]);
+       operator = validate[formname][i][operatorIndex];
+       item1 = trim(form[validate[formname][i][nameIndex]].value);
+       if (operator.charAt(0) == 'u') {
+        item1 = item1.toUpperCase();
+        operator = operator.substring(1);
+       } else if (operator.charAt(0) == 'l') {
+        item1 = item1.toLowerCase();
+        operator = operator.substring(1);
+       }
+       for (j = 0; j < arr.length; j++) {
+        val = arr[j];
+        if ((operator == "==" && val == item1) || (operator == "!=" && val != item1)) {
+         isError = true;
+         add_error_style(formname, validate[formname][i][nameIndex], invalidTxt + " " + validate[formname][i][msgIndex]);
+        }
+       }
+       break;
+      case 'verified':
+       if (trim(form[validate[formname][i][nameIndex]].value) == 'false') {
+        isError = true;
+       }
+       break;
+       //HACK : MB support for customcode actions/validation (onblur/onsave)
+      case 'customFunction':
+       var functionName = validate[formname][i][customFunctionName];
+
+       var fn = functionName + '(\'' + formname + '\')';
+       var execstatus = true;
+       try {
+        execstatus = eval(fn);
+       } catch (err) {
+        alert(err);
+        execstatus = false;
+       }
+       if (execstatus == false) {
+        isError = true;
+        add_error_style(formname, validate[formname][i][nameIndex], validate[formname][i][msgIndex]);
+       }
+       break;
+       //HACK : MB support for customcode actions/validation (onblur/onsave)      
+      }
+     }
+    }
+   }
+  }
+ }
+ if (formsWithFieldLogic) {
+  var invalidLogic = false;
+  if (formsWithFieldLogic.min && formsWithFieldLogic.max && formsWithFieldLogic._default) {
+   var showErrorsOn = {
+    min: {
+     value: 'min',
+     show: false,
+     obj: formsWithFieldLogic.min.value
+    }, max: {
+     value: 'max',
+     show: false,
+     obj: formsWithFieldLogic.max.value
+    }, _default: {
+     value: 'default',
+     show: false,
+     obj: formsWithFieldLogic._default.value
+    }, len: {
+     value: 'len',
+     show: false,
+     obj: parseInt(formsWithFieldLogic.len.value)
+    }
+   };
+   var min = (formsWithFieldLogic.min.value != '') ? parseInt(formsWithFieldLogic.min.value) : 'undef';
+   var max = (formsWithFieldLogic.max.value != '') ? parseInt(formsWithFieldLogic.max.value) : 'undef';
+   var _default = (formsWithFieldLogic._default.value != '') ? parseInt(formsWithFieldLogic._default.value) : 'undef';
+   for (var i in showErrorsOn) {
+    if (showErrorsOn[i].value != 'len' && showErrorsOn[i].obj.length > showErrorsOn.len.obj) {
+     invalidLogic = true;
+     showErrorsOn[i].show = true;
+     showErrorsOn.len.show = true;
+    }
+   }
+   if (min != 'undef' && max != 'undef' && _default != 'undef') {
+    if (!inRange(_default, min, max)) {
+     invalidLogic = true;
+     showErrorsOn.min.show = true;
+     showErrorsOn.max.show = true;
+     showErrorsOn._default.show = true;
+    }
+   }
+   if (min != 'undef' && max != 'undef' && min > max) {
+    invalidLogic = true;
+    showErrorsOn.min.show = true;
+    showErrorsOn.max.show = true;
+   }
+   if (min != 'undef' && _default != 'undef' && _default < min) {
+    invalidLogic = true;
+    showErrorsOn.min.show = true;
+    showErrorsOn._default.show = true;
+   }
+   if (max != 'undef' && _default != 'undef' && _default > max) {
+    invalidLogic = true;
+    showErrorsOn.max.show = true;
+    showErrorsOn._default.show = true;
+   }
+   if (invalidLogic) {
+    isError = true;
+    for (var error in showErrorsOn)
+    if (showErrorsOn[error].show) add_error_style(formname, showErrorsOn[error].value, formsWithFieldLogic.msg);
+   }
+   else if (!isError) formsWithFieldLogic = null;
+  }
+ }
+ if (formWithPrecision) {
+  if (!isValidPrecision(formWithPrecision.float.value, formWithPrecision.precision.value)) {
+   isError = true;
+   add_error_style(formname, 'default', SUGAR.language.get('app_strings', 'ERR_COMPATIBLE_PRECISION_VALUE'));
+  } else if (!isError) {
+   isError = false;
+  }
+ }
+ if (isError == true) {
+  var nw, ne, sw, se;
+  if (self.pageYOffset) {
+   nwX = self.pageXOffset;
+   seX = self.innerWidth;
+   nwY = self.pageYOffset;
+   seY = self.innerHeight;
+  }
+  else if (document.documentElement && document.documentElement.scrollTop) {
+   nwX = document.documentElement.scrollLeft;
+   seX = document.documentElement.clientWidth;
+   nwY = document.documentElement.scrollTop;
+   seY = document.documentElement.clientHeight;
+  }
+  else if (document.body) {
+   nwX = document.body.scrollLeft;
+   seX = document.body.clientWidth;
+   nwY = document.body.scrollTop;
+   seY = document.body.clientHeight;
+  }
+  var inView = true;
+  for (var wp = 0; wp < inputsWithErrors.length; wp++) {
+   var elementCoor = findElementPos(inputsWithErrors[wp]);
+   if (!(elementCoor.x >= nwX && elementCoor.y >= nwY && elementCoor.x <= seX && elementCoor.y <= seY)) {
+    inView = false;
+    scrollToTop = elementCoor.y - 75;
+    scrollToLeft = elementCoor.x - 75;
+   }
+   else {
+    break;
+   }
+  }
+  if (!inView) window.scrollTo(scrollToTop, scrollToLeft);
+  return false;
+ }
+ return true;
+}
 var marked_row=new Array;function setPointer(theRow,theRowNum,theAction,theDefaultColor,thePointerColor,theMarkColor){var theCells=null;if((thePointerColor==''&&theMarkColor=='')||typeof(theRow.style)=='undefined'){return false;}
 if(typeof(document.getElementsByTagName)!='undefined'){theCells=theRow.getElementsByTagName('td');}
 else if(typeof(theRow.cells)!='undefined'){theCells=theRow.cells;}
Index: modules/DynamicFields/templates/Fields/Forms/coreTop.tpl
===================================================================
--- modules/DynamicFields/templates/Fields/Forms/coreTop.tpl (revision 1)
+++ modules/DynamicFields/templates/Fields/Forms/coreTop.tpl (working copy)
@@ -83,7 +83,37 @@
  <td class='mbLBL'>{$MOD.COLUMN_TITLE_HELP_TEXT}:</td><td>{if $hideLevel < 5 }<input type="text" name="help" value="{$vardef.help}">{else}<input type="hidden" name="help" value="{$vardef.help}">{$vardef.help}{/if}
  </td>
 </tr>
+{* HACK: MB support for customcode actions/validation (onblur/onsave) -- MB support for customcode actions/validation (onblur/onsave)*}
 <tr>
+ <td class='mbLBL'>{$MOD.COLUMN_TITLE_CUSTOMJS_NAME_TEXT}</td>
+ <td>{if $hideLevel < 5 && $MB == 1}
+  <input type="text" name="validation" value="{$vardef.validation.functionName}">
+  {else}
+  <input type="hidden" name="validation" value="{$vardef.validation.functionName}">{$vardef.validation.functionName}
+  {/if}
+ </td>
+</tr>
+<tr>
+ <td class='mbLBL'>{$MOD.COLUMN_TITLE_CUSTOMJS_TEXT}</td>
+ <td>{if $hideLevel < 5 && $MB == 1}
+  <textarea name="customjs">{$vardef.customjs}</textarea>
+  {else}
+  <input type="hidden" name="customjs" value="{$vardef.customjs}">{$vardef.customjs}
+  {/if}
+ </td>
+</tr>
+<tr>
+ <td class='mbLBL'>{$MOD.COLUMN_TITLE_CUSTOMJS_ERROR_TEXT}</td>
+ <td>{if $hideLevel < 5 && $MB == 1}
+  <input type="text" name="customerrormsg" value="{$vardef.validation.errorMessage}">
+  {else}
+  <input type="hidden" name="customerrormsg" value="{$vardef.validation.errorMessage}">{$vardef.validation.errorMessage}
+  {/if}
+ </td>
+</tr>
+{* HACK:  MB support for customcode actions/validation (onblur/onsave) *}
+
+<tr>
     <td class='mbLBL'>{$MOD.COLUMN_TITLE_COMMENT_TEXT}:</td><td>{if $hideLevel < 5 }<input type="text" name="comments" value="{$vardef.comments}">{else}<input type="hidden" name="comment" value="{$vardef.comment}">{$vardef.comment}{/if}
     </td>
 </tr>
Index: modules/EditCustomFields/language/en_us.lang.php
===================================================================
--- modules/EditCustomFields/language/en_us.lang.php (revision 1)
+++ modules/EditCustomFields/language/en_us.lang.php (working copy)
@@ -92,4 +92,10 @@
  'LBL_DEPENDENT_TRIGGER'=>'Trigger',
  'LBL_BTN_EDIT_VISIBILITY'=>'Edit Visibility',
 );
+
+// HACK: MB support for customcode actions/validation (onblur/onsave) -- MB support for customcode actions/validation (onblur/onsave)
+$mod_strings['COLUMN_TITLE_CUSTOMJS_NAME_TEXT']='JS Function Name:';
+$mod_strings['COLUMN_TITLE_CUSTOMJS_TEXT']='JS Function Code:';
+$mod_strings['COLUMN_TITLE_CUSTOMJS_ERROR_TEXT']='JS Error Message:';
+//HACK: MB support for customcode actions/validation (onblur/onsave)
 ?>
Index: modules/EditCustomFields/vardefs.php
===================================================================
--- modules/EditCustomFields/vardefs.php (revision 1)
+++ modules/EditCustomFields/vardefs.php (working copy)
@@ -66,4 +66,9 @@
   array('name' => 'idx_meta_cm_del', 'type' => 'index', 'fields' => array('custom_module', 'deleted')),
  ),
 );
+
+//HACK : MB support for customcode actions/validation (onblur/onsave)
+$dictionary['FieldsMetaData']['fields']['customjs']=array('name' =>'customjs','type' =>'text','default'=>'');
+$dictionary['FieldsMetaData']['fields']['validation']=array('name' =>'validation','type' =>'text','default'=>'');
+//HACK : MB support for customcode actions/validation (onblur/onsave)
 ?>
Index: modules/ModuleBuilder/controller.php
===================================================================
--- modules/ModuleBuilder/controller.php (revision 1)
+++ modules/ModuleBuilder/controller.php (working copy)
@@ -332,11 +332,21 @@
             $mb = new ModuleBuilder ( ) ;
             $module = & $mb->getPackageModule ( $_REQUEST [ 'view_package' ], $_REQUEST [ 'view_module' ] ) ;
             $field->save ( $module ) ;
+   //HACK : MB support for customcode actions/validation (onblur/onsave)
+   if(isset($_REQUEST['validation']) && isset($_REQUEST['customjs']) && mb_strlen(trim($_REQUEST['validation']))>0 && mb_strlen(trim($_REQUEST['customjs']))>0){
+    $module->mbvardefs->vardef['fields'][$_REQUEST['name']]['validation']=array('type' => 'customFunction','functionName'=>$_REQUEST['validation'],'errorMessage'=>'LBL_'.mb_strtoupper($_REQUEST['name']).'_JS_ERR_MSG');
+    $module->mbvardefs->vardef['fields'][$_REQUEST['name']]['customjs']=$_REQUEST['customjs'];
+   }
+   //HACK : MB support for customcode actions/validation (onblur/onsave) 
             $module->mbvardefs->save () ;
             // get the module again to refresh the labels we might have saved with the $field->save (e.g., for address fields)
             $module = & $mb->getPackageModule ( $_REQUEST [ 'view_package' ], $_REQUEST [ 'view_module' ] ) ;
             if (isset ( $_REQUEST [ 'label' ] ) && isset ( $_REQUEST [ 'labelValue' ] ))
                 $module->setLabel ( $GLOBALS [ 'current_language' ], $_REQUEST [ 'label' ], $_REQUEST [ 'labelValue' ] ) ;
+         //HACK : MB support for customcode actions/validation (onblur/onsave)
+   if(isset($_REQUEST['validation']) && isset($_REQUEST['customjs']) && mb_strlen(trim($_REQUEST['validation']))>0 && mb_strlen(trim($_REQUEST['customjs']))>0){
+    $module->setLabel($GLOBALS['current_language'],'LBL_'.mb_strtoupper($_REQUEST['name']).'_JS_ERR_MSG',$_REQUEST['customerrormsg']);   }            
+            //HACK : MB support for customcode actions/validation (onblur/onsave)   
             $module->save();
         }
         $this->view = 'modulefields' ;
Index: modules/ModuleBuilder/views/view.modulefield.php
===================================================================
--- modules/ModuleBuilder/views/view.modulefield.php (revision 1)
+++ modules/ModuleBuilder/views/view.modulefield.php (working copy)
@@ -186,6 +186,12 @@
             $fv->ss->assign('module', $module);
             $fv->ss->assign('package', $package);
             $fv->ss->assign('MB','1');
+            //HACK : MB support for customcode actions/validation (onblur/onsave)
+            if(isset($vardef['validation']['errorMessage']) && !empty($vardef['validation']['errorMessage']))
+    //HACK :Upgrade sugarbase to SugarCRM 5.5
+             $vardef['validation']['errorMessage'] = $module->getLabel($GLOBALS['current_language'],$vardef['validation']['errorMessage']);
+    //HACK :Upgrade sugarbase to SugarCRM 5.5
+   //HACK : MB support for customcode actions/validation (onblur/onsave)
 
             if(isset($vardef['vname']))
                 $fv->ss->assign('lbl_value', htmlentities($module->getLabel('en_us',$vardef['vname']), ENT_QUOTES, 'UTF-8'));