Saturday, August 16, 2008

Controlling Data Entry

Disabling and enabling fields that depend on certain logic is a common request. This can also complicate things especially when the form is “crowded” with fields and the number of dependencies is “overwhelming”. Binding to each field separately and wrapping it with specific conditions can make quite a mess. A while back I was searching for a way to control this and came up with a simple approach I call chaining.

Each parent control is chained to a set of children (comma separated list) and is given a String “Condition” or a function pointer that makes the necessary validation checks. If the validation fails the fields are disabled. All fields enjoy a basic and automatic validation which checks that the field “Data Value” does not equal null. All other validations are specified by the user (programmer).








function OnCrmPageLoad()
{
//Chain Account Name to Account number, if the name is not ADI the disable the field
Chain( "name" , "accountnumber" , "crmForm.all.name.DataValue == 'ADI'" );
//The condition is a function pointer that is called when the change event is raised
Chain( "accountnumber", "parentaccountid,primarycontactid" , TestIfAccountNumberHas10Digits );
}

function Chain( parent , children , condition /*optional*/ )
{
//Create an array on children
children = children.split(',');
//reference the parent control
var Parent = document.getElementById( parent );

//if the parent onchange event is not registered, this prevents multiple binds to the same control
if(!Parent.Registered)
{
Parent.attachEvent( "onchange" , OnChainReaction );
Parent.Registered = true;
}

//Each parent holds an array of its child controls
Parent.Sons = (Parent.Sons)?Parent.Sons:[];
//Save the condition on the parent control (using expando)
Parent.Condition = condition;

for( var i = 0 ; i < children.length ; i++ )
{
var Child = Parent.Sons[ i ] = document.getElementById( children[i] );
Child.Disabled = Parent.DataValue == null;
if(!Child.Registered)
{
Child.attachEvent( "onchange" , OnChainReaction );
Child.Registered = true;
}

//Each child holds a reference to its parent. this can be used in code if needed
Child.Parents = (Child.Parents)?Child.Parents:[];
Child.Parents[ i ] = parent;
}


//Start Validating the control
Parent.FireOnChange();
}

function OnChainReaction()
{
//Current field
var Control = event.srcElement;
if( !Control.Sons ) return;

//Basic (internal) validation check
var enabled = Control.DataValue != null;

switch( typeof( Control.Condition ) )
{
case "string": //e.g. 'crmForm.all..DataValue == "TEST"'
enabled = eval(Control.Condition) && enabled;
break;
case "function": //e.g. MyFunction (with out the brackets)
enabled = Control.Condition() && enabled;
break;
}

//Disable or enable the children
for( var i = 0 ; i < Control.Sons.length; i++ )
Control.Sons[i].Disabled = !enabled;
}

//Our test function must return a boolean value - true,false
function TestIfAccountNumberHas10Digits()
{
var accountnumber = crmForm.all.accountnumber;
return (accountnumber.DataValue) ? (accountnumber.DataValue.length == 10):false;
}

OnCrmPageLoad();

3 comments:

Anonymous said...

Hello! than you for you wonderful example. I was wondering where would I past this code

Adi Katz said...

Thanks,

You need to past the example inside the account onload event box.

Adi Katz said...

You may also use an external reference.
Follow this post for more information.