Second Life of a Hungarian SharePoint Geek

January 4, 2010

Cross field, cross item, cross list or even more complicated validations on SharePoint forms

Filed under: Custom fields, SharePoint — Tags: , — Peter Holpar @ 02:59

Although SharePoint 2010 contains some advanced validation possibilities (see: Field and List Item ValidationField and List Item Validation in SharePoint 2010 Lists), the built-in features in WSS 3.0 / MOSS 2007 are very limited on this are. But it is not so hard to create similar cross field validation or even more complicated ones if you are familiar with the internals of SharePoint and don’t panic if you need to write custom code.

In this post I assume you have read and understood at least the followings of my former posts about playing with custom fields:

I show you first the simplest sample, how to make a cross field validation through creating a basic validation framework, and illustrate that on a standard Tasks list.

The basic of the validation is to get the value of the field to validate. If you get the reference for the field control as illustrated in my previous post, and the field content is not valid, for example, the content of the TextBox of a DateTimeControl of a DateTimeField cannot be interpreted as a date, then the value of the field will be null. If you need information about the value typed by the user you will have to apply some tricks.

  1. BaseFieldControl field = GetFieldControlByName(fieldName);
  2. // fieldValue will be null
  3. Object fieldValue = field.Value;
  4. // textValue will be the typed value in the text box
  5. String textValue = ((TextBox)((DateTimeField)field).Controls[0].Controls[1].Controls[0]).Text;

In this post I will validate the page first, and then apply the custom validation only if the page is valid. You could also check the validity on the control level, by using the IsValid property of the field control instead of the IsValid property of the Page.

So let’s see the example. Tasks have a Start date and a Due date field. In this example we assume that value of the Start date of the task should be less than the value of the Due date of the task. OK, I know life is not always so simple, but it is only an example for validation.

We should first override the OnLoad and UpdateFieldValueInItem methods to intercept the item save mechanism and inject the custom validation.

  1. protected override void OnLoad(EventArgs e)
  2. {
  3.     this.Parent.Parent.Visible = true;
  4.     base.OnLoad(e);
  5.     this.Parent.Parent.Visible = false;
  6. }
  7.  
  8.  
  9. public override void UpdateFieldValueInItem()
  10. {
  11.     Page.Validate();
  12.     if (Page.IsValid)
  13.     {
  14.         Validation();
  15.         base.UpdateFieldValueInItem();
  16.     }
  17. }

To sign that the value specified in a field control is invalid we set its IsValid property to false and its ErrorMessage property to the validation warning text. Be aware, that the other method of the save mechanism interception, attaching event handler to the OnSaveHandler seems to be not adequate for validation. Although the event handler method is called based on my debug sessions as expected, setting the IsValid property has no the expected effect.

If we are in edit or new item mode, then in the Validation method we call the CheckCrossFieldRule method that compares the specified fields and if the comparison fails, display the validation message for the specified field control.

The first two parameters of the CheckCrossFieldRule method are the name of the fields to compare, the third parameter is the excepted relation of the fields. The fourth parameter is the name of the field where the validation error should be displayed and the last parameter is the validation message itself.

The IsRuleValid method contains the core of comparison for the validation.

  1. private void Validation()
  2. {
  3.     if ((ControlMode == SPControlMode.Edit) || (ControlMode == SPControlMode.New))
  4.     {
  5.         CheckCrossFieldRule("StartDate", "DueDate", "<", "DueDate", "Due date should be greater than start date");
  6.     }
  7. }
  8.  
  9. private void CheckCrossFieldRule(String fieldNameComp1, String fieldNameComp2, String relation, String fieldNameError, String errorMessage)
  10. {
  11.     bool isValid = IsRuleValid(fieldNameComp1, fieldNameComp2, relation);
  12.     BaseFieldControl fieldError = GetFieldControlByName(fieldNameError);
  13.     if (fieldError != null)
  14.     {
  15.         if (!isValid)
  16.         {
  17.             fieldError.ErrorMessage = errorMessage;
  18.         }
  19.         fieldError.IsValid = isValid;
  20.  
  21.     }
  22. }
  23.  
  24. private bool IsRuleValid(String fieldName1, String fieldName2, String relation)
  25. {
  26.     BaseFieldControl field1 = GetFieldControlByName(fieldName1);
  27.     BaseFieldControl field2 = GetFieldControlByName(fieldName2);
  28.     if ((field1 != null) && (field2 != null) && (field1.GetType() == field2.GetType()))
  29.     {
  30.         if (field1 is TextField)
  31.         {
  32.             String stringValue1 = (String)((TextField)field1).Value;
  33.             String stringValue2 = (String)((TextField)field2).Value;
  34.             if (relation == "=")
  35.             {
  36.                 return (stringValue1 == stringValue2);
  37.             }
  38.             else if (relation == "<>")
  39.             {
  40.                 return (stringValue1 != stringValue2);
  41.             }
  42.         }
  43.         if (field1 is NumberField)
  44.         {
  45.             float floatValue1 = (float)((NumberField)field1).Value;
  46.             float floatValue2 = (float)((NumberField)field2).Value;
  47.             if (relation == "=")
  48.             {
  49.                 return (floatValue1 == floatValue2);
  50.             }
  51.             else if (relation == "<")
  52.             {
  53.                 return (floatValue1 < floatValue2);
  54.             }
  55.             else if (relation == "<>")
  56.             {
  57.                 return (floatValue1 != floatValue2);
  58.             }
  59.         }
  60.         if (field1 is DateTimeField)
  61.         {
  62.             Object value1 = ((DateTimeField)field1).Value;
  63.             Object value2 = ((DateTimeField)field2).Value;
  64.             if ((value1 != null) && (value2 != null))
  65.             {
  66.                 DateTime dateTime1 = (DateTime)value1;
  67.                 DateTime dateTime2 = (DateTime)value2;
  68.                 if (relation == "=")
  69.                 {
  70.                     return dateTime1 == dateTime2;
  71.                 }
  72.                 else if (relation == "<")
  73.                 {
  74.                     return dateTime1 < dateTime2;
  75.                 }
  76.                 else if (relation == "<>")
  77.                 {
  78.                     return dateTime1 != dateTime2;
  79.                 }
  80.             }
  81.         }
  82.     }
  83.     return true;
  84. }

After we build and deploy our field and add it to a Tasks list, we cannot save tasks that has a due date earlier than start date as shown below:

image

Let’s see what’s about cross item and cross list validation. We build this kind of checks on CAML queries. The first type of this validation is implemented in the CheckCamlCountRule method and  compares the count of the items returned by the CAML query to the predefined number specified for the validation.

  1. private void CheckCamlCountRule(SPList list, SPQuery query, int count, String relation, String fieldNameError, String errorMessage)
  2. {
  3.     SPListItemCollection items = list.GetItems(query);
  4.     int itemCount = items.Count;
  5.  
  6.     bool isValid = true;
  7.  
  8.     switch (relation)
  9.     {
  10.         case "<":
  11.             {
  12.                 isValid = (count < itemCount);
  13.                 break;
  14.             }
  15.         case ">":
  16.             {
  17.                 isValid = (count > itemCount);
  18.                 break;
  19.             }
  20.         case "<>":
  21.             {
  22.                 isValid = (count != itemCount);
  23.                 break;
  24.             }
  25.         case "=":
  26.             {
  27.                 isValid = (count == itemCount);
  28.                 break;
  29.             }
  30.     }
  31.  
  32.     BaseFieldControl fieldError = GetFieldControlByName(fieldNameError);
  33.     if (fieldError != null)
  34.     {
  35.         if (!isValid)
  36.         {
  37.             fieldError.ErrorMessage = errorMessage;
  38.         }
  39.         fieldError.IsValid = isValid;
  40.  
  41.     }
  42. }

The following code block shows the usage of this method in the modified Validation method:

  1. private void Validation()
  2. {
  3.     if ((ControlMode == SPControlMode.Edit) || (ControlMode == SPControlMode.New))
  4.     {
  5.         SPList currentList = SPContext.Current.List;
  6.         SPQuery query = new SPQuery();
  7.         query.Query = "<Where><Eq><FieldRef Name='Status'/><Value Type='Text'>Not Started</Value></Eq></Where>";
  8.         CheckCamlCountRule(currentList, query, 3, ">", "Status", "The count of the not started tasks is too high. You should start a few of them.");
  9.     }
  10. }

If there are three or more items in the Tasks list that have Not started status and – after deploying the modified version of our field – we try to create a new one, we receive the following validation error:

image

Of course, you can specify other lists for the CheckCamlCountRule method as well, you are not restricted to the current list.

The CheckCamlFieldRule method is similar to the CheckCrossFieldRule method but instead of the second field name you can specify the list you want the CAML query to be run on, the SPQuery object for the query and the name of the field from the resulting SPListItemCollection you want the field value on the current form to be compared with.

The IsCamlRulevalid method is a helper to do the comparison with the first item of the collection.

  1. private void CheckCamlFieldRule(String fieldNameComp, SPList list, SPQuery query, String fieldCamlName, String relation, String fieldNameError, String errorMessage)
  2. {
  3.     SPListItemCollection items = list.GetItems(query);
  4.  
  5.     bool isValid = true;
  6.  
  7.     if ((items.Count > 0) && (items[0].Fields.ContainsField(fieldCamlName)))
  8.     {
  9.         Object fieldCamlValue = items[0][fieldCamlName];
  10.         isValid = IsCamlRuleValid(fieldNameComp, fieldCamlValue, relation);
  11.     }
  12.  
  13.     BaseFieldControl fieldError = GetFieldControlByName(fieldNameError);
  14.     if (fieldError != null)
  15.     {
  16.         if (!isValid)
  17.         {
  18.             fieldError.ErrorMessage = errorMessage;
  19.         }
  20.         fieldError.IsValid = isValid;
  21.  
  22.     }
  23. }
  24.  
  25. private bool IsCamlRuleValid(String fieldName, Object fieldCamlValue, String relation)
  26. {
  27.     BaseFieldControl field = GetFieldControlByName(fieldName);
  28.     float dummyFloat;
  29.  
  30.     if (field != null)
  31.     {
  32.         if ((field is TextField) && (fieldCamlValue is String))
  33.         {
  34.             String stringValue = (String)((TextField)field).Value;
  35.             if (relation == "=")
  36.             {
  37.                 return (stringValue == (String)fieldCamlValue);
  38.             }
  39.             else if (relation == "<>")
  40.             {
  41.                 return (stringValue != (String)fieldCamlValue);
  42.             }
  43.         }
  44.         // check if it is a numerical value wrappable to float
  45.         if ((field is NumberField) && (float.TryParse(fieldCamlValue.ToString(), out dummyFloat)))
  46.         {
  47.             float floatValue = (float)((NumberField)field).Value;
  48.             if (relation == "=")
  49.             {
  50.                 return (floatValue == (float)fieldCamlValue);
  51.             }
  52.             else if (relation == "<")
  53.             {
  54.                 return (floatValue < (float)fieldCamlValue);
  55.             }
  56.             else if (relation == "<>")
  57.             {
  58.                 return (floatValue != (float)fieldCamlValue);
  59.             }
  60.         }
  61.         if ((field is DateTimeField) && (fieldCamlValue is DateTime))
  62.         {
  63.             Object fieldValue = ((DateTimeField)field).Value;
  64.             if (fieldValue != null)
  65.             {
  66.                 DateTime dateTime1 = (DateTime)fieldValue;
  67.                 DateTime dateTime2 = (DateTime)fieldCamlValue;
  68.                 if (relation == "=")
  69.                 {
  70.                     return dateTime1 == dateTime2;
  71.                 }
  72.                 else if (relation == "<")
  73.                 {
  74.                     return dateTime1 < dateTime2;
  75.                 }
  76.                 else if (relation == "<>")
  77.                 {
  78.                     return dateTime1 != dateTime2;
  79.                 }
  80.             }
  81.         }
  82.     }
  83.     return true;
  84. }

You don’t need to use constant CAML queries, you can use values on the current form to build the CAML query dynamically.

The next step of the validation is to apply the same technique using special business logic and arbitrary business application, for example, via calling web services or accessing databases. I hope you can do that yourself based on the previous samples if you really need that.

Advertisements

1 Comment »

  1. […] Second Life of a Hungarian SharePoint Geek If your sword is too short, take one step forward « Cross field, cross item, cross list or even more complicated validations on SharePoint forms […]

    Pingback by Creating real multi column fields for SharePoint « Second Life of a Hungarian SharePoint Geek — January 9, 2010 @ 01:36


RSS feed for comments on this post. TrackBack URI

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Blog at WordPress.com.

%d bloggers like this: